tokentracker-cli 1.4.14 → 1.5.1
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/README.md +3 -3
- package/README.zh-CN.md +1 -1
- package/dashboard/dist/.well-known/submitby-ai/6e2fa70e-9eb2-4c97-a9a9-fed567a3d4a9.txt +1 -0
- package/dashboard/dist/assets/AchievementBadge-mB7HZSGE.js +1 -0
- package/dashboard/dist/assets/{AchievementsPage-YvgCj3Tf.js → AchievementsPage-DLzeq_eo.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-C2llp4CQ.js → AchievementsSection-DrXQyDns.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-DH1lqy_t.js → BadgeDetailModal-WrgKRybi.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-BPJ80H6q.js → BotAnimated-DFRaixVK.js} +1 -1
- package/dashboard/dist/assets/{Card-D0lZNo7p.js → Card-bFq2tlO8.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-YCIw4n94.js → CloudCheckoutPage-gwhGxavy.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-D5PNgyOe.js → CloudPage-BD4iBYeZ.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-CfNOZlEo.js → CommandPalette-C_KlPl0U.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-eywEJpBo.js → CommunityStatsModal-Cl1Bgv8M.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-MvUDUW0j.js → ConfirmModal-NWoYevYC.js} +1 -1
- package/dashboard/dist/assets/{Controls-B3s01gQc.js → Controls-D2-D2cfA.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-7zQm3Y95.js → DashboardPage-vITMnPXx.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-C7T4EjRr.js → DateRangePopover-8EMVXuH6.js} +1 -1
- package/dashboard/dist/assets/DevicePage-DbP8d6bM.js +1 -0
- package/dashboard/dist/assets/{DialogClose-DlVXuWgp.js → DialogClose-CEstgLa1.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-0wcygxtH.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-Dc0-pI0j.js → DialogTitle-DP5TLLbz.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-BRp4UZF4.js → ElectricLogo-Uvi3yMum.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DFL15trS.js → FadeIn-Dpzihg6f.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CPWpKT2q.js → HeaderGithubStar-DllqemKr.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-DXIFAD1D.js → HoverTooltip-Dc-TAH6l.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DBElmZxm.js → IpCheckPage-jd1D5ZKX.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-3mpjg69k.js → LandingPage-CB1ZVw-a.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-xIQTyBSj.js → LeaderboardPage-Bt2cRRmE.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-hMzno53J.js → LeaderboardProBadge-BVlyVOFf.js} +1 -1
- package/dashboard/dist/assets/LeaderboardProfileModal-CwQFRJg_.js +73 -0
- package/dashboard/dist/assets/{LeaderboardProfilePage-CIVWObmo.js → LeaderboardProfilePage-CeqcTbUG.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-Bp4ZORmR.js +6 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-Cnzi2tn4.js → LocalOnlyNotice-B2_HoCVD.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-DOO_XdQm.js → LoginCard-nw9JOc2d.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-B5Ec0gTs.js → LoginModal-BHNLTK1D.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BcyuULTm.js → LoginPage-LI2GZKhM.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DeTdSNPI.js → PetPage-CK6sPeI0.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-BqiCJJkn.js → PopoverPopup-BsXchEVa.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-94H7cG7M.js → ProviderIcon-vgCEy0dh.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-BfaaiVWq.js → ResetPasswordPage-B4CPj2vX.js} +1 -1
- package/dashboard/dist/assets/{Select-Dk1g2jS1.js → Select-BkAy6P2r.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-CgY3YTCX.js → SelectItemText-C0RNqDrD.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-CINWSnGe.js → SelfHostPage-B-_w-Z-a.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-B3bZ4RLp.js → ServiceStatusPage-5YevsGGD.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-Bt-zYCLB.js → SessionsPage-CmWXBqD2.js} +2 -2
- package/dashboard/dist/assets/{SettingsPage-DgvNelAH.js → SettingsPage-CA70zP-t.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-BeapUXZg.js → SkillsPage-CNamhfzU.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-BmARBReu.js → TokenGalaxy-CXrVBSk6.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-DMkTj-q1.js → TooltipPopup-CR4MqElX.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-DErfAB4Q.js → TrendMonitor-DQfcrEve.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-D7MRIlIQ.js → WidgetsPage-BBXxtZbP.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-Dwl8J96o.js → WrappedPage-C8i2KlMF.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-Bp0fEMn1.js → __vite-browser-external-BEEzY7mq.js} +3 -3
- package/dashboard/dist/assets/{agent-logos-D5X6WoAY.js → agent-logos-CxQb8vir.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-Bmf0v8yS.js → arrow-left-D6zkcLVt.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-CrY1rjed.js → arrow-right-BGb-XVn_.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CK7lXR6O.js → arrow-up-right-XTStagDJ.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-zge_5pfQ.js → calendar-days-Cz3NGZiS.js} +1 -1
- package/dashboard/dist/assets/{check-Bt-LY4Pn.js → check-1z_vmHPD.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-BOy_MKfd.js → chevron-down-CkSBY3bE.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-4NPL78Jg.js → cloud-checkout-pD5C4r_S.js} +1 -1
- package/dashboard/dist/assets/{composite-UMkd0X7k.js → composite-BtDzx3a2.js} +1 -1
- package/dashboard/dist/assets/{copy-DJTFZc8D.js → copy-Ctv_wxuD.js} +1 -1
- package/dashboard/dist/assets/{download-BJD3ff5y.js → download-DKbKMGSR.js} +1 -1
- package/dashboard/dist/assets/{external-link-D2m7vRGI.js → external-link-g9cU017t.js} +1 -1
- package/dashboard/dist/assets/{flame-D7CLiokj.js → flame-DZq3kXUt.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-BYDaVVT_.js → format-tokens-CXQfx0PC.js} +1 -1
- package/dashboard/dist/assets/{gift-toIl7elE.js → gift-CKlKjGP6.js} +1 -1
- package/dashboard/dist/assets/{icons-DscB4AX2.js → icons-xPGMnBjl.js} +1 -1
- package/dashboard/dist/assets/{index-BGu15oeA.js → index-BwOjMtB2.js} +1 -1
- package/dashboard/dist/assets/{index-6krHCwUe.js → index-CDBOKcDh.js} +1 -1
- package/dashboard/dist/assets/{index-BLS9byMs.js → index-Dl0oFLNr.js} +33 -33
- package/dashboard/dist/assets/{index-CEsURfpC.js → index-fVbP5wOv.js} +1 -1
- package/dashboard/dist/assets/{info-D73-a9a_.js → info-D9xNIuGh.js} +1 -1
- package/dashboard/dist/assets/{link-2-DwpvF9Sr.js → link-2-Bk5wYt2n.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-BTs6Fc8Y.js → loader-circle-CcN3pHWb.js} +1 -1
- package/dashboard/dist/assets/{local-usage-transfer-BXhe_z5f.js → local-usage-transfer-CYcy1ici.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-CFoTDZHy.js → modifiers.esm-DbpEzkJe.js} +1 -1
- package/dashboard/dist/assets/module-ZyVsxBG_.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-4ejjSL5C.js → monitor-smartphone-WkWv1qIS.js} +1 -1
- package/dashboard/dist/assets/{play-Ch1IEQyR.js → play-2yZGWMes.js} +1 -1
- package/dashboard/dist/assets/{plus-Bc8vdvFi.js → plus-mOF8tG1F.js} +1 -1
- package/dashboard/dist/assets/{provider-display-BhKfIayc.js → provider-display-7gaOvgMp.js} +1 -1
- package/dashboard/dist/assets/{react-BhuZeLEc.js → react-CdPiPDhk.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-ag21oDI1.js → safePolygon-C7FJLAlh.js} +1 -1
- package/dashboard/dist/assets/{search-BhM-pD0f.js → search-C63nCrjo.js} +1 -1
- package/dashboard/dist/assets/{server-CWZM_fiB.js → server-BaG1rnuv.js} +1 -1
- package/dashboard/dist/assets/{shield-check-BdF9MoZ4.js → shield-check-D_4-Cm6L.js} +1 -1
- package/dashboard/dist/assets/{skills-api-PC7TzIba.js → skills-api-DfbzwpDv.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-u_yCrBFE.js → sortable.esm-D-v8FRX5.js} +1 -1
- package/dashboard/dist/assets/{store-19qppboP.js → store-Dv_FYrXK.js} +1 -1
- package/dashboard/dist/assets/{terminal-ikPM7Gyt.js → terminal-FQi6MaYt.js} +1 -1
- package/dashboard/dist/assets/{trash-DuS_INdf.js → trash-TbK96dEz.js} +1 -1
- package/dashboard/dist/assets/use-community-stats-DGoWVZuk.js +1 -0
- package/dashboard/dist/assets/{use-latest-request-guard-CClqxkji.js → use-latest-request-guard-Dg6uSaYt.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-C2IK7QqP.js → use-limits-display-prefs-Djbdp7fi.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-wciHeNL2.js → use-native-settings-DIk0AF3b.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-CUnip521.js → use-ordered-list-CUqV3Zgl.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-DMCok-vZ.js +1 -0
- package/dashboard/dist/assets/{use-transform-BFydEHQY.js → use-transform-D_-w22c8.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-BqWWrW6O.js → use-usage-limits-h7aGsIQr.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-sg6d4Wea.js → useCurrency-B_AuLCsL.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-CLPx448B.js → usePositioner-DfxvIif8.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-BOt7YH_Y.js → useTokenFormat-bbO13V4s.js} +1 -1
- package/dashboard/dist/assets/{zap-B6nc-Upg.js → zap-k07-EXTK.js} +1 -1
- package/dashboard/dist/index.html +20 -1
- package/dashboard/dist/indexnow-key.txt +1 -0
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/dashboard/dist/tt6a1c9e2f4b8d.txt +1 -0
- package/package.json +3 -3
- package/src/lib/dsh-session.js +580 -0
- package/src/lib/local-api.js +40 -3
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/qoder-limits.js +178 -38
- package/src/lib/session-analytics.js +89 -11
- package/dashboard/dist/assets/AchievementBadge-BUYKivjx.js +0 -1
- package/dashboard/dist/assets/DevicePage-CZNBMHfk.js +0 -1
- package/dashboard/dist/assets/DialogDescription-C3mLrii8.js +0 -1
- package/dashboard/dist/assets/LeaderboardProfileModal-Cw0_YCXH.js +0 -73
- package/dashboard/dist/assets/LimitsPage-i_AEHRcd.js +0 -6
- package/dashboard/dist/assets/module-C2SOSUuR.js +0 -3
- package/dashboard/dist/assets/use-community-stats-DnRz7Akf.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-D586B7AA.js +0 -1
package/src/lib/qoder-limits.js
CHANGED
|
@@ -40,6 +40,14 @@ const QODER_ACTIVITY_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
|
40
40
|
const QODER_LIMITS_CACHE_FILE = "qoder-usage-limits-cache.json";
|
|
41
41
|
const QODER_LIMITS_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
42
42
|
const QODER_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
|
|
43
|
+
// Qoder Desktop 0.4+ writes its account usage summary to main.log, which can
|
|
44
|
+
// accumulate a whole session before the quota is read. Scan a bounded window
|
|
45
|
+
// backwards instead of assuming the summary is near EOF.
|
|
46
|
+
const QODER_LOG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
47
|
+
const QODER_LOG_SCAN_MAX_BYTES = 8 * 1024 * 1024;
|
|
48
|
+
const QODER_LOG_SCAN_CHUNK_BYTES = 512 * 1024;
|
|
49
|
+
const QODER_LOG_SCAN_OVERLAP_BYTES = 64 * 1024;
|
|
50
|
+
const QODER_LOG_MAX_FILES = 8;
|
|
43
51
|
const QODER_SERVER_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
44
52
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA8iMH5c02LilrsERw9t6Pv5Nc
|
|
45
53
|
4k6Pz1EaDicBMpdpxKduSZu5OANqUq8er4GM95omAGIOPOh+Nx0spthYA2BqGz+l
|
|
@@ -575,7 +583,38 @@ async function fetchQoderUsage(cookieHeader, site, fetchImpl) {
|
|
|
575
583
|
return normalizeQoderUsageResponse(await response.json());
|
|
576
584
|
}
|
|
577
585
|
|
|
578
|
-
function
|
|
586
|
+
function qoderLogRoots({
|
|
587
|
+
home = os.homedir(),
|
|
588
|
+
platform = process.platform,
|
|
589
|
+
env = process.env,
|
|
590
|
+
appDir = "Qoder",
|
|
591
|
+
envPrefix = "QODER",
|
|
592
|
+
} = {}) {
|
|
593
|
+
const explicitLogRoot = env[`${envPrefix}_LOG_ROOT`];
|
|
594
|
+
if (typeof explicitLogRoot === "string" && explicitLogRoot.trim()) {
|
|
595
|
+
return [path.resolve(explicitLogRoot.trim())];
|
|
596
|
+
}
|
|
597
|
+
const configuredHome = env[`${envPrefix}_HOME`];
|
|
598
|
+
if (typeof configuredHome === "string" && configuredHome.trim()) {
|
|
599
|
+
return [path.join(path.resolve(configuredHome.trim()), "logs")];
|
|
600
|
+
}
|
|
601
|
+
const base = platform === "darwin"
|
|
602
|
+
? path.join(home, "Library", "Application Support")
|
|
603
|
+
: platform === "win32"
|
|
604
|
+
? (env.APPDATA || path.join(home, "AppData", "Roaming"))
|
|
605
|
+
: path.join(home, ".config");
|
|
606
|
+
// Qoder Desktop 0.4.x moved its Electron profile from Qoder[CN]/ to the
|
|
607
|
+
// bundle id directory and writes the account usage summary to main.log.
|
|
608
|
+
const bundleId = appDir === "QoderCN" || envPrefix === "QODER_CN"
|
|
609
|
+
? "com.qodercn.app.stable"
|
|
610
|
+
: "com.qoder.app.stable";
|
|
611
|
+
return [
|
|
612
|
+
path.join(base, appDir, "logs"),
|
|
613
|
+
path.join(base, bundleId, "logs"),
|
|
614
|
+
];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function listRecentQoderLogs(logRoot, maxFiles = QODER_LOG_MAX_FILES) {
|
|
579
618
|
if (!fs.existsSync(logRoot)) return [];
|
|
580
619
|
const files = [];
|
|
581
620
|
const stack = [{ dir: logRoot, depth: 0 }];
|
|
@@ -591,19 +630,106 @@ function listRecentQoderRendererLogs(logRoot, maxFiles = 12) {
|
|
|
591
630
|
const full = path.join(current.dir, entry.name);
|
|
592
631
|
if (entry.isDirectory() && current.depth < 4) {
|
|
593
632
|
stack.push({ dir: full, depth: current.depth + 1 });
|
|
594
|
-
} else if (entry.isFile() && entry.name === "renderer.log") {
|
|
633
|
+
} else if (entry.isFile() && (entry.name === "main.log" || entry.name === "renderer.log")) {
|
|
595
634
|
let mtimeMs = 0;
|
|
635
|
+
let size = 0;
|
|
596
636
|
try {
|
|
597
|
-
|
|
637
|
+
const stat = fs.statSync(full);
|
|
638
|
+
mtimeMs = stat.mtimeMs;
|
|
639
|
+
size = stat.size;
|
|
598
640
|
} catch (_error) {}
|
|
599
|
-
files.push({ full, mtimeMs });
|
|
641
|
+
files.push({ full, mtimeMs, size });
|
|
600
642
|
}
|
|
601
643
|
}
|
|
602
644
|
}
|
|
603
645
|
return files
|
|
604
646
|
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
605
|
-
.slice(0, maxFiles)
|
|
606
|
-
|
|
647
|
+
.slice(0, maxFiles);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Returns a parse hit, or null after a completed scan with no hit; undefined
|
|
651
|
+
// means the scan itself failed (open/read) so callers must not treat it as
|
|
652
|
+
// "no summary".
|
|
653
|
+
function scanQoderLogFromEnd(logPath, parse, {
|
|
654
|
+
maxBytes = QODER_LOG_SCAN_MAX_BYTES,
|
|
655
|
+
chunkBytes = QODER_LOG_SCAN_CHUNK_BYTES,
|
|
656
|
+
overlapBytes = QODER_LOG_SCAN_OVERLAP_BYTES,
|
|
657
|
+
} = {}) {
|
|
658
|
+
let fd;
|
|
659
|
+
try {
|
|
660
|
+
fd = fs.openSync(logPath, "r");
|
|
661
|
+
} catch (_error) {
|
|
662
|
+
return undefined;
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
const size = fs.fstatSync(fd).size;
|
|
666
|
+
const floor = Math.max(0, size - maxBytes);
|
|
667
|
+
let end = size;
|
|
668
|
+
let carry = "";
|
|
669
|
+
while (end > floor) {
|
|
670
|
+
const start = Math.max(floor, end - chunkBytes);
|
|
671
|
+
const length = end - start;
|
|
672
|
+
const buffer = Buffer.alloc(length);
|
|
673
|
+
const read = fs.readSync(fd, buffer, 0, length, start);
|
|
674
|
+
const text = buffer.subarray(0, read).toString("utf8") + carry;
|
|
675
|
+
const parsed = parse(text);
|
|
676
|
+
if (parsed) return parsed;
|
|
677
|
+
// Keep this chunk's head so a line split across the boundary parses
|
|
678
|
+
// when the previous chunk is prepended.
|
|
679
|
+
carry = text.slice(0, overlapBytes);
|
|
680
|
+
end = start;
|
|
681
|
+
}
|
|
682
|
+
} catch (_error) {
|
|
683
|
+
return undefined;
|
|
684
|
+
} finally {
|
|
685
|
+
fs.closeSync(fd);
|
|
686
|
+
}
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function parseQoderAccountUsageLog(text, { fallbackObservedAtMs = null } = {}) {
|
|
691
|
+
if (typeof text !== "string" || !text.includes('"qoderUsage"')) return null;
|
|
692
|
+
const lines = text.split(/\r?\n/);
|
|
693
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
694
|
+
const line = lines[index];
|
|
695
|
+
if (!line.includes('"qoderUsage"')) continue;
|
|
696
|
+
const jsonStart = line.indexOf('{"') >= 0 ? line.indexOf('{"') : line.indexOf("{");
|
|
697
|
+
if (jsonStart < 0) continue;
|
|
698
|
+
let payload;
|
|
699
|
+
try {
|
|
700
|
+
payload = JSON.parse(line.slice(jsonStart));
|
|
701
|
+
} catch (_error) {
|
|
702
|
+
try {
|
|
703
|
+
payload = JSON.parse(line.slice(jsonStart, line.lastIndexOf("}") + 1));
|
|
704
|
+
} catch (_error2) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const usage = payload?.qoderUsage;
|
|
709
|
+
if (!usage || typeof usage !== "object") continue;
|
|
710
|
+
let limits;
|
|
711
|
+
try {
|
|
712
|
+
limits = normalizeQoderRpcUsage({
|
|
713
|
+
userType: usage.userType,
|
|
714
|
+
expiresAt: usage.expiresAt,
|
|
715
|
+
isQuotaExceeded: usage.isQuotaExceeded,
|
|
716
|
+
totalUsagePercentage: usage.totalUsagePercentage,
|
|
717
|
+
userQuota: usage.userQuota,
|
|
718
|
+
addOnQuota: usage.addOnQuota,
|
|
719
|
+
});
|
|
720
|
+
} catch (_error) {
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
if (!limits) continue;
|
|
724
|
+
limits.source = "local-log";
|
|
725
|
+
const timestampMatch = /^\[([^\]]+)\]/.exec(line);
|
|
726
|
+
const lineObservedAtMs = timestampMatch ? Date.parse(timestampMatch[1]) : NaN;
|
|
727
|
+
return {
|
|
728
|
+
limits,
|
|
729
|
+
observedAtMs: Number.isFinite(lineObservedAtMs) ? lineObservedAtMs : fallbackObservedAtMs,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return null;
|
|
607
733
|
}
|
|
608
734
|
|
|
609
735
|
function parseQoderQuotaLog(text) {
|
|
@@ -640,44 +766,57 @@ function parseQoderQuotaLog(text) {
|
|
|
640
766
|
};
|
|
641
767
|
}
|
|
642
768
|
|
|
769
|
+
// Maps a log path to its "size:mtimeMs" signature from the last completed
|
|
770
|
+
// scan that found no usable summary (failed I/O is never recorded). Qoder
|
|
771
|
+
// only appends to these logs, so an unchanged signature cannot have gained
|
|
772
|
+
// one; pruning to the current candidate set keeps the map bounded.
|
|
773
|
+
const qoderNoSummaryScans = new Map();
|
|
774
|
+
|
|
643
775
|
function readQoderLocalQuota({
|
|
644
776
|
home = os.homedir(),
|
|
645
777
|
platform = process.platform,
|
|
646
778
|
env = process.env,
|
|
647
779
|
appDir = "Qoder",
|
|
648
780
|
envPrefix = "QODER",
|
|
781
|
+
nowMs = Date.now(),
|
|
782
|
+
maxAgeMs = QODER_LOG_MAX_AGE_MS,
|
|
649
783
|
} = {}) {
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const stat = fs.fstatSync(fd);
|
|
672
|
-
const length = Math.min(stat.size, 512 * 1024);
|
|
673
|
-
const buffer = Buffer.alloc(length);
|
|
674
|
-
fs.readSync(fd, buffer, 0, length, Math.max(0, stat.size - length));
|
|
675
|
-
const parsed = parseQoderQuotaLog(buffer.toString("utf8"));
|
|
676
|
-
if (parsed) return parsed;
|
|
677
|
-
} finally {
|
|
678
|
-
fs.closeSync(fd);
|
|
784
|
+
const candidates = [];
|
|
785
|
+
for (const logRoot of qoderLogRoots({ home, platform, env, appDir, envPrefix })) {
|
|
786
|
+
for (const entry of listRecentQoderLogs(logRoot)) {
|
|
787
|
+
if (nowMs - entry.mtimeMs > maxAgeMs) continue;
|
|
788
|
+
candidates.push(entry);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
792
|
+
const scanned = candidates.slice(0, QODER_LOG_MAX_FILES);
|
|
793
|
+
const seenPaths = new Set();
|
|
794
|
+
for (const { full, mtimeMs, size } of scanned) {
|
|
795
|
+
seenPaths.add(full);
|
|
796
|
+
const signature = `${size}:${mtimeMs}`;
|
|
797
|
+
if (qoderNoSummaryScans.get(full) === signature) continue;
|
|
798
|
+
// A stale modern summary must not end the scan: fall through so a legacy
|
|
799
|
+
// match in the same chunk — or deeper — can still be used; only a fresh
|
|
800
|
+
// modern result returns early.
|
|
801
|
+
const parsed = scanQoderLogFromEnd(full, (text) => {
|
|
802
|
+
const accountUsage = parseQoderAccountUsageLog(text, { fallbackObservedAtMs: mtimeMs });
|
|
803
|
+
if (accountUsage && nowMs - (accountUsage.observedAtMs ?? mtimeMs) <= maxAgeMs) {
|
|
804
|
+
return accountUsage;
|
|
679
805
|
}
|
|
680
|
-
|
|
806
|
+
const legacy = parseQoderQuotaLog(text);
|
|
807
|
+
return legacy ? { limits: legacy, observedAtMs: mtimeMs } : null;
|
|
808
|
+
});
|
|
809
|
+
if (parsed === undefined) continue; // Scan failed (I/O); retry on the next call.
|
|
810
|
+
if (!parsed) {
|
|
811
|
+
qoderNoSummaryScans.set(full, signature);
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const observedAtMs = parsed.observedAtMs ?? mtimeMs;
|
|
815
|
+
if (nowMs - observedAtMs > maxAgeMs) continue;
|
|
816
|
+
return parsed.limits;
|
|
817
|
+
}
|
|
818
|
+
for (const cachedPath of qoderNoSummaryScans.keys()) {
|
|
819
|
+
if (!seenPaths.has(cachedPath)) qoderNoSummaryScans.delete(cachedPath);
|
|
681
820
|
}
|
|
682
821
|
return null;
|
|
683
822
|
}
|
|
@@ -793,7 +932,7 @@ async function fetchQoderLimits({
|
|
|
793
932
|
// Preserve that status as metadata, but match Qoder's Credits UI by
|
|
794
933
|
// rendering an empty 0/0 bucket as 0%, not a full red 100% bar.
|
|
795
934
|
const local = result.primary_window?.limit_credits === 0
|
|
796
|
-
? readQoderLocalQuota({ home, platform, env, appDir, envPrefix })
|
|
935
|
+
? readQoderLocalQuota({ home, platform, env, appDir, envPrefix, nowMs })
|
|
797
936
|
: null;
|
|
798
937
|
if (local) {
|
|
799
938
|
result.quota_exceeded = local.quota_exceeded === true;
|
|
@@ -817,7 +956,7 @@ async function fetchQoderLimits({
|
|
|
817
956
|
}
|
|
818
957
|
}
|
|
819
958
|
|
|
820
|
-
const local = readQoderLocalQuota({ home, platform, env, appDir, envPrefix });
|
|
959
|
+
const local = readQoderLocalQuota({ home, platform, env, appDir, envPrefix, nowMs });
|
|
821
960
|
if (local) {
|
|
822
961
|
if (activityWindow) {
|
|
823
962
|
local.secondary_window = activityWindow;
|
|
@@ -874,6 +1013,7 @@ module.exports = {
|
|
|
874
1013
|
readQoderLimitsCache,
|
|
875
1014
|
writeQoderLimitsCache,
|
|
876
1015
|
parseQoderQuotaLog,
|
|
1016
|
+
parseQoderAccountUsageLog,
|
|
877
1017
|
readQoderLocalQuota,
|
|
878
1018
|
fetchQoderLimits,
|
|
879
1019
|
fetchQoderCnLimits,
|
|
@@ -28,7 +28,7 @@ const fsp = require("node:fs/promises");
|
|
|
28
28
|
const os = require("node:os");
|
|
29
29
|
const path = require("node:path");
|
|
30
30
|
const readline = require("node:readline");
|
|
31
|
-
const { listClaudeProjectFiles, listRolloutFilesDeep, claudeMessageDedupKey, normalizeClaudeUsage, readZcodeDbMessages, coerceEpochMs, resolveZcodeInstallPaths, normalizeOpencodeModelFields, normalizeOpencodeTokens, deriveOpencodeMessageFingerprint } = require("./rollout");
|
|
31
|
+
const { listClaudeProjectFiles, listRolloutFilesDeep, claudeMessageDedupKey, normalizeClaudeUsage, readZcodeDbMessages, coerceEpochMs, resolveZcodeInstallPaths, normalizeOpencodeModelFields, normalizeOpencodeTokens, deriveOpencodeMessageFingerprint, resolveDshHomes } = require("./rollout");
|
|
32
32
|
const { parseCodexRolloutFile } = require("./codex-rollout-parser");
|
|
33
33
|
const { computeRowCost, getModelPricingInfo } = require("./pricing");
|
|
34
34
|
const { normalizePerformance, mergePerformance, createClaudePerformanceCollector, createCodexPerformanceCollector, createZcodePerformanceCollector } = require("./session-performance");
|
|
@@ -39,6 +39,7 @@ const { USD_TICKS_PER_USD, normalizeGrokUsage } = require("./grok-usage");
|
|
|
39
39
|
const wsl = require("./wsl-probe");
|
|
40
40
|
const { discoverKimiWireFiles, kimiSessionDependencies, readKimiSessions } = require("./kimi-session");
|
|
41
41
|
const { discoverOmpSessionFiles, ompSessionDependencies, readOmpSessions } = require("./omp-session");
|
|
42
|
+
const { discoverDshSessionFiles, readDshSessions } = require("./dsh-session");
|
|
42
43
|
const {
|
|
43
44
|
appendUniqueDirs,
|
|
44
45
|
dedupeDirsByRealpath,
|
|
@@ -1970,6 +1971,38 @@ async function scanOmpSession(filePath) {
|
|
|
1970
1971
|
return rows[0] || null;
|
|
1971
1972
|
}
|
|
1972
1973
|
|
|
1974
|
+
async function scanDshSessions(filePaths) {
|
|
1975
|
+
// Batch the whole inventory: a seeded Harness session replays its parent's
|
|
1976
|
+
// message ids, so cross-file identity is resolved before any row is emitted
|
|
1977
|
+
// (same contract as the hourly dsh reader's per-message identity).
|
|
1978
|
+
const rows = await readDshSessions(Array.isArray(filePaths) ? filePaths : [filePaths]);
|
|
1979
|
+
const scanned = rows.map(({ identity, files, title, ...row }) => finalizeRecord({
|
|
1980
|
+
...row,
|
|
1981
|
+
title: cleanSessionTitle(title),
|
|
1982
|
+
version: SIDECAR_VERSION,
|
|
1983
|
+
source: "dsh",
|
|
1984
|
+
session_hash: sessionHash("dsh", identity),
|
|
1985
|
+
project_key: projectKey(row.project_ref, files[0]),
|
|
1986
|
+
...sessionStorageUsage(files),
|
|
1987
|
+
}));
|
|
1988
|
+
// readDshSessions reports unreadable transcripts on a non-enumerable
|
|
1989
|
+
// property; map() built a fresh array, so carry the count (and the matching
|
|
1990
|
+
// accessor) over or the caller would cache a partial inventory as complete.
|
|
1991
|
+
return defineSkippedFileCount(scanned, rows.skippedFiles);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
function defineSkippedFileCount(rows, skippedFiles) {
|
|
1995
|
+
if (Number.isFinite(skippedFiles) && skippedFiles > 0) {
|
|
1996
|
+
Object.defineProperty(rows, "skippedFiles", { value: skippedFiles, enumerable: false });
|
|
1997
|
+
}
|
|
1998
|
+
return rows;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
async function scanDshSession(filePath) {
|
|
2002
|
+
const rows = await scanDshSessions([filePath]);
|
|
2003
|
+
return rows[0] || null;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1973
2006
|
async function discoverSessionFiles(home, env = process.env, deps = {}) {
|
|
1974
2007
|
const grokHome = resolveGrokHome(home);
|
|
1975
2008
|
// config.scanRoots (#657): read from the tracker config unless the caller
|
|
@@ -2052,6 +2085,22 @@ async function discoverSessionFiles(home, env = process.env, deps = {}) {
|
|
|
2052
2085
|
const opencodePaths = resolveInstallPaths({ nativeValue: opencodeNativeDir, wslValue: opencodeWslDir }, env, { platform: zcodePlatform });
|
|
2053
2086
|
const opencode = [...new Set(Object.values(opencodePaths).filter(Boolean)
|
|
2054
2087
|
.map((dir) => path.join(dir, "opencode.db")).filter((dbPath) => fs.existsSync(dbPath)))];
|
|
2088
|
+
// DeepSeek Harness keeps one directory per session under ~/.dsh/sessions.
|
|
2089
|
+
// Reuse the hourly reader's resolver so the Usage and Sessions views always
|
|
2090
|
+
// agree: an explicit TOKENTRACKER_DSH_HOME/$DSH_HOME replaces the home
|
|
2091
|
+
// outright (a user who pointed the harness elsewhere is not also running the
|
|
2092
|
+
// default, so an override never gets a WSL sibling spliced in), and only the
|
|
2093
|
+
// machine home probes WSL. A private harness home therefore needs no
|
|
2094
|
+
// config.scanRoots entry: the variable itself is the override.
|
|
2095
|
+
// An override describes where the *machine* keeps its harness home, so it only
|
|
2096
|
+
// applies when the caller asked about that same home: a test or a fixture
|
|
2097
|
+
// home must never pick up the logged-in user's transcripts. Same gate as the
|
|
2098
|
+
// kimi roots below.
|
|
2099
|
+
const usingMachineHome = path.resolve(home) === path.resolve((deps.homedir || os.homedir)());
|
|
2100
|
+
const dshRoots = usingMachineHome
|
|
2101
|
+
? resolveDshHomes(env, { nativeHome: path.join(home, ".dsh") })
|
|
2102
|
+
: [path.join(home, ".dsh")];
|
|
2103
|
+
const dsh = discoverDshSessionFiles(dedupeDirsByRealpath(dshRoots), incomplete);
|
|
2055
2104
|
const kimiRoots = [".kimi", ".kimi-code"].flatMap((directory) => {
|
|
2056
2105
|
const roots = providerRoots(home, directory, env, deps);
|
|
2057
2106
|
const override = env[directory === ".kimi" ? "KIMI_HOME" : "KIMI_CODE_HOME"];
|
|
@@ -2062,12 +2111,11 @@ async function discoverSessionFiles(home, env = process.env, deps = {}) {
|
|
|
2062
2111
|
return roots;
|
|
2063
2112
|
});
|
|
2064
2113
|
const kimi = discoverKimiWireFiles(dedupeDirsByRealpath(kimiRoots), incomplete);
|
|
2065
|
-
const usingMachineHome = path.resolve(home) === path.resolve((deps.homedir || os.homedir)());
|
|
2066
2114
|
const ompEnv = usingMachineHome
|
|
2067
2115
|
? { ...env, HOME: home }
|
|
2068
2116
|
: { HOME: home, OMP_HOME: path.join(home, ".omp") };
|
|
2069
2117
|
const omp = discoverOmpSessionFiles(ompEnv, incomplete);
|
|
2070
|
-
return { claude, codex: groupCodexFiles([...codex, ...archived]), grok, zcode, opencode, kimi, omp, incomplete };
|
|
2118
|
+
return { claude, codex: groupCodexFiles([...codex, ...archived]), grok, zcode, opencode, kimi, omp, dsh, incomplete };
|
|
2071
2119
|
}
|
|
2072
2120
|
|
|
2073
2121
|
// Warn once per unreadable directory per process: the dashboard polls this
|
|
@@ -2217,13 +2265,15 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2217
2265
|
const retryKimiScan = fs.existsSync(incompleteKimiPath);
|
|
2218
2266
|
const incompleteOmpPath = `${metaPath}.omp-incomplete`;
|
|
2219
2267
|
const retryOmpScan = fs.existsSync(incompleteOmpPath);
|
|
2268
|
+
const incompleteDshPath = `${metaPath}.dsh-incomplete`;
|
|
2269
|
+
const retryDshScan = fs.existsSync(incompleteDshPath);
|
|
2220
2270
|
let previousMeta = null;
|
|
2221
2271
|
if (!force) {
|
|
2222
2272
|
try {
|
|
2223
2273
|
previousMeta = JSON.parse(fs.readFileSync(metaPath, "utf8"));
|
|
2224
2274
|
const checkedAt = Date.parse(previousMeta.checked_at || previousMeta.generated_at || "");
|
|
2225
2275
|
if (
|
|
2226
|
-
!retryZcodeScan && !retryOpencodeScan && !retryKimiScan && !retryOmpScan && previousMeta.version === SIDECAR_VERSION &&
|
|
2276
|
+
!retryZcodeScan && !retryOpencodeScan && !retryKimiScan && !retryOmpScan && !retryDshScan && previousMeta.version === SIDECAR_VERSION &&
|
|
2227
2277
|
Number.isFinite(checkedAt) &&
|
|
2228
2278
|
Date.now() - checkedAt < Math.max(0, Number(cacheTtlMs) || 0)
|
|
2229
2279
|
) {
|
|
@@ -2257,13 +2307,14 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2257
2307
|
...kimiSessionDependencies(discovered.kimi),
|
|
2258
2308
|
...discovered.omp,
|
|
2259
2309
|
...ompSessionDependencies(discovered.omp),
|
|
2310
|
+
...discovered.dsh,
|
|
2260
2311
|
...discovered.grok,
|
|
2261
2312
|
...discovered.grok.map(grokSummaryPathFor),
|
|
2262
2313
|
...discovered.grok.map(grokSignalsPathFor),
|
|
2263
2314
|
// db + its WAL sidecars: the trio is one logical file for change detection.
|
|
2264
2315
|
...[...(discovered.zcode || []), ...(discovered.opencode || [])].flatMap((dbPath) => [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]),
|
|
2265
2316
|
]);
|
|
2266
|
-
if (!force && !retryZcodeScan && !retryOpencodeScan && !retryKimiScan && !retryOmpScan && previousMeta?.version === SIDECAR_VERSION && previousMeta.signature === signature) {
|
|
2317
|
+
if (!force && !retryZcodeScan && !retryOpencodeScan && !retryKimiScan && !retryOmpScan && !retryDshScan && previousMeta?.version === SIDECAR_VERSION && previousMeta.signature === signature) {
|
|
2267
2318
|
await writeAtomic(metaPath, `${JSON.stringify({ ...previousMeta, checked_at: new Date().toISOString() })}\n`);
|
|
2268
2319
|
return readSidecar(sidecarPath);
|
|
2269
2320
|
}
|
|
@@ -2287,6 +2338,7 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2287
2338
|
...(discovered.opencode.length ? [{ source: "opencode", filePath: discovered.opencode, scan: scanOpencodeSessions }] : []),
|
|
2288
2339
|
...(discovered.kimi.length ? [{ source: "kimi", filePath: discovered.kimi, scan: scanKimiSessions }] : []),
|
|
2289
2340
|
...(discovered.omp.length ? [{ source: "omp", filePath: discovered.omp, scan: scanOmpSessions }] : []),
|
|
2341
|
+
...(discovered.dsh.length ? [{ source: "dsh", filePath: discovered.dsh, scan: scanDshSessions }] : []),
|
|
2290
2342
|
];
|
|
2291
2343
|
// Files we could not turn into a row (permission denied, half-written line,
|
|
2292
2344
|
// vanished mid-scan). Swallowing these silently made sessions disappear with
|
|
@@ -2296,6 +2348,7 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2296
2348
|
let incompleteOpencodeScan = false;
|
|
2297
2349
|
let incompleteKimiScan = false;
|
|
2298
2350
|
let incompleteOmpScan = false;
|
|
2351
|
+
let incompleteDshScan = false;
|
|
2299
2352
|
for (const entry of entries) {
|
|
2300
2353
|
const cacheKey = sessionFileCacheKey(entry.source, entry.filePath);
|
|
2301
2354
|
const statKey = analyticsEntryStatKey(entry.source, entry.filePath);
|
|
@@ -2305,11 +2358,12 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2305
2358
|
if (entry.source === "opencode") incompleteOpencodeScan = true;
|
|
2306
2359
|
if (entry.source === "kimi") incompleteKimiScan = true;
|
|
2307
2360
|
if (entry.source === "omp") incompleteOmpScan = true;
|
|
2361
|
+
if (entry.source === "dsh") incompleteDshScan = true;
|
|
2308
2362
|
continue;
|
|
2309
2363
|
}
|
|
2310
2364
|
let rows = null;
|
|
2311
|
-
if (!force && !(entry.source === "zcode" && retryZcodeScan) && !(entry.source === "opencode" && retryOpencodeScan) && !(entry.source === "kimi" && retryKimiScan) && !(entry.source === "omp" && retryOmpScan) && previousFiles[cacheKey]?.stat_key === statKey) {
|
|
2312
|
-
if (entry.source === "zcode" || entry.source === "opencode" || entry.source === "kimi" || entry.source === "omp") {
|
|
2365
|
+
if (!force && !(entry.source === "zcode" && retryZcodeScan) && !(entry.source === "opencode" && retryOpencodeScan) && !(entry.source === "kimi" && retryKimiScan) && !(entry.source === "omp" && retryOmpScan) && !(entry.source === "dsh" && retryDshScan) && previousFiles[cacheKey]?.stat_key === statKey) {
|
|
2366
|
+
if (entry.source === "zcode" || entry.source === "opencode" || entry.source === "kimi" || entry.source === "omp" || entry.source === "dsh") {
|
|
2313
2367
|
// One db scans to many cached rows; recover them all from the previous
|
|
2314
2368
|
// pass. An empty recovery means the sidecar lost its rows — rescan.
|
|
2315
2369
|
const cached = previousRows.filter((row) => row?.source === entry.source);
|
|
@@ -2323,6 +2377,17 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2323
2377
|
try {
|
|
2324
2378
|
const scanned = await entry.scan(entry.filePath);
|
|
2325
2379
|
rows = Array.isArray(scanned) ? scanned.filter(Boolean) : (scanned ? [scanned] : []);
|
|
2380
|
+
// Batch sources count the files they had to give up on themselves.
|
|
2381
|
+
// A dsh batch is one deduplicated inventory, so a single unreadable log
|
|
2382
|
+
// makes the whole batch partial: flag it incomplete and keep the last
|
|
2383
|
+
// complete rows instead of caching a silently smaller inventory.
|
|
2384
|
+
if (Array.isArray(scanned) && Number.isFinite(scanned.skippedFiles)) {
|
|
2385
|
+
skippedFiles += scanned.skippedFiles;
|
|
2386
|
+
if (entry.source === "dsh" && scanned.skippedFiles > 0) {
|
|
2387
|
+
incompleteDshScan = true;
|
|
2388
|
+
rows = [];
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2326
2391
|
} catch (error) {
|
|
2327
2392
|
// One active/partial session must not poison the sidecar, but do not
|
|
2328
2393
|
// pretend it never existed either.
|
|
@@ -2331,6 +2396,7 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2331
2396
|
if (entry.source === "opencode") incompleteOpencodeScan = true;
|
|
2332
2397
|
if (entry.source === "kimi") incompleteKimiScan = true;
|
|
2333
2398
|
if (entry.source === "omp") incompleteOmpScan = true;
|
|
2399
|
+
if (entry.source === "dsh") incompleteDshScan = true;
|
|
2334
2400
|
if (!process.env.NODE_TEST_CONTEXT) {
|
|
2335
2401
|
console.warn(`[session-analytics] skipped ${entry.source} session file: ${error?.message || error}`);
|
|
2336
2402
|
}
|
|
@@ -2344,7 +2410,7 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2344
2410
|
// so key each by the db path plus its session id; a bare session id as a
|
|
2345
2411
|
// separate path element would be path.resolve()d against the process cwd
|
|
2346
2412
|
// and never match across runs.
|
|
2347
|
-
row._cache_key = (entry.source === "zcode" || entry.source === "opencode" || entry.source === "kimi" || entry.source === "omp") && (row.session_id || row.session_hash)
|
|
2413
|
+
row._cache_key = (entry.source === "zcode" || entry.source === "opencode" || entry.source === "kimi" || entry.source === "omp" || entry.source === "dsh") && (row.session_id || row.session_hash)
|
|
2348
2414
|
? `${cacheKey}#${sessionHash(entry.source, row.session_id || row.session_hash)}`
|
|
2349
2415
|
: cacheKey;
|
|
2350
2416
|
sessions.push(row);
|
|
@@ -2372,10 +2438,18 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2372
2438
|
await writeAtomic(incompleteOmpPath, "1\n");
|
|
2373
2439
|
sessions.push(...readSidecar(sidecarPath).filter((row) => row.source === "omp" && row.version === SIDECAR_VERSION));
|
|
2374
2440
|
}
|
|
2441
|
+
if (incompleteDshScan) {
|
|
2442
|
+
await writeAtomic(incompleteDshPath, "1\n");
|
|
2443
|
+
// Replay ownership depends on the entire inventory. A readable fork may
|
|
2444
|
+
// claim an unreadable parent's events, so retain the complete batch rather
|
|
2445
|
+
// than combining newly attributed rows with the parent's cached usage.
|
|
2446
|
+
sessions.push(...readSidecar(sidecarPath).filter((row) =>
|
|
2447
|
+
row.source === "dsh" && row.version === SIDECAR_VERSION));
|
|
2448
|
+
}
|
|
2375
2449
|
sessions.sort((a, b) => String(b.ended_at || "").localeCompare(String(a.ended_at || "")));
|
|
2376
2450
|
// A failed database scan is an incomplete inventory. Keep the last complete
|
|
2377
2451
|
// snapshot and signature so an unchanged database is retried next build.
|
|
2378
|
-
if (incompleteDirs.length === 0 && !incompleteZcodeScan && !incompleteOpencodeScan && !incompleteKimiScan && !incompleteOmpScan) {
|
|
2452
|
+
if (incompleteDirs.length === 0 && !incompleteZcodeScan && !incompleteOpencodeScan && !incompleteKimiScan && !incompleteOmpScan && !incompleteDshScan) {
|
|
2379
2453
|
const content = sessions.map(serializeSessionRecord).join("\n") + (sessions.length ? "\n" : "");
|
|
2380
2454
|
await writeAtomic(sidecarPath, content);
|
|
2381
2455
|
const generatedAt = new Date().toISOString();
|
|
@@ -2390,6 +2464,7 @@ async function buildSessionAnalyticsInternal({ home = os.homedir(), force = fals
|
|
|
2390
2464
|
await fsp.rm(incompleteOpencodePath, { force: true });
|
|
2391
2465
|
await fsp.rm(incompleteKimiPath, { force: true });
|
|
2392
2466
|
await fsp.rm(incompleteOmpPath, { force: true });
|
|
2467
|
+
await fsp.rm(incompleteDshPath, { force: true });
|
|
2393
2468
|
}
|
|
2394
2469
|
// Non-enumerable so the array still behaves exactly like a plain row list
|
|
2395
2470
|
// for every existing caller (map/filter/JSON of the rows is unaffected).
|
|
@@ -2863,7 +2938,7 @@ function annotateThreadUsage(rows) {
|
|
|
2863
2938
|
const identity = (row, id = row.session_id) => `${row.source}\u0000${id}`;
|
|
2864
2939
|
|
|
2865
2940
|
for (const row of rows || []) {
|
|
2866
|
-
if ((row?.source !== "codex" && row?.source !== "zcode" && row?.source !== "opencode") || typeof row.session_id !== "string" || !row.session_id) continue;
|
|
2941
|
+
if ((row?.source !== "codex" && row?.source !== "zcode" && row?.source !== "opencode" && row?.source !== "dsh") || typeof row.session_id !== "string" || !row.session_id) continue;
|
|
2867
2942
|
const existing = byId.get(identity(row));
|
|
2868
2943
|
if (existing && existing !== row) {
|
|
2869
2944
|
existing.parent_link_conflict = true;
|
|
@@ -2941,7 +3016,8 @@ function annotateThreadUsage(rows) {
|
|
|
2941
3016
|
}
|
|
2942
3017
|
|
|
2943
3018
|
for (const row of rows || []) {
|
|
2944
|
-
const threadAware = (row.source === "codex" || row.source === "zcode"
|
|
3019
|
+
const threadAware = (row.source === "codex" || row.source === "zcode"
|
|
3020
|
+
|| row.source === "opencode" || row.source === "dsh")
|
|
2945
3021
|
&& typeof row.session_id === "string" && row.session_id;
|
|
2946
3022
|
row.own_total_tokens = finite(row.total_tokens);
|
|
2947
3023
|
row.own_cost_usd = finite(row.cost_usd);
|
|
@@ -3019,6 +3095,8 @@ module.exports = {
|
|
|
3019
3095
|
scanKimiSessions,
|
|
3020
3096
|
scanOmpSessions,
|
|
3021
3097
|
scanOmpSession,
|
|
3098
|
+
scanDshSessions,
|
|
3099
|
+
scanDshSession,
|
|
3022
3100
|
buildSessionAnalytics,
|
|
3023
3101
|
summarizeSessions,
|
|
3024
3102
|
listSessionsForBrowser,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o,V as H,j as r}from"./__vite-browser-external-Bp0fEMn1.js";import{Z as R}from"./zap-B6nc-Upg.js";import{F}from"./flame-D7CLiokj.js";import{C as I}from"./calendar-days-zge_5pfQ.js";import{S as G}from"./shield-check-BdF9MoZ4.js";const u={name:"blocks",size:24,node:[["path",{d:"M10 22V7a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5a1 1 0 0 0-1-1H2",key:"1ah6g2"}],["rect",{x:"14",y:"2",width:"8",height:"8",rx:"1",key:"88lufb"}]]};u.node;const N=o(u);const k={name:"brain",size:24,node:[["path",{d:"M12 18V5",key:"adv99a"}],["path",{d:"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4",key:"1e3is1"}],["path",{d:"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5",key:"1gqd8o"}],["path",{d:"M17.997 5.125a4 4 0 0 1 2.526 5.77",key:"iwvgf7"}],["path",{d:"M18 18a4 4 0 0 0 2-7.464",key:"efp6ie"}],["path",{d:"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517",key:"1gq6am"}],["path",{d:"M6 18a4 4 0 0 1-2-7.464",key:"k1g0md"}],["path",{d:"M6.003 5.125a4 4 0 0 0-2.526 5.77",key:"q97ue3"}]]};k.node;const S=o(k);const v={name:"crown",size:24,node:[["path",{d:"M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z",key:"1vdc57"}],["path",{d:"M5 21h14",key:"11awu3"}]]};v.node;const V=o(v);const b={name:"feather",size:24,node:[["path",{d:"M14.086 18.412A2 2 0 0112.67 19H5v-7.672a2 2 0 01.586-1.414L11.75 3.75a6 6 0 118.49 8.49z",key:"1nq9jb"}],["path",{d:"M16 8 2 22",key:"vp34q"}],["path",{d:"M17.488 15H9",key:"16yirz"}]]};b.node;const Z=o(b);const M={name:"folder-git-2",size:24,node:[["path",{d:"M18 19a5 5 0 0 1-5-5v8",key:"sz5oeg"}],["path",{d:"M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5",key:"1w6njk"}],["circle",{cx:"13",cy:"12",r:"2",key:"1j92g6"}],["circle",{cx:"20",cy:"19",r:"2",key:"1obnsp"}]]};M.node;const O=o(M);const _={name:"footprints",size:24,node:[["path",{d:"M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z",key:"1dudjm"}],["path",{d:"M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z",key:"l2t8xc"}],["path",{d:"M16 17h4",key:"1dejxt"}],["path",{d:"M4 13h4",key:"1bwh8b"}]]};_.node;const P=o(_);const x={name:"heart",size:24,node:[["path",{d:"M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",key:"mvr1a0"}]]};x.node;const X=o(x);const w={name:"medal",size:24,node:[["path",{d:"M7.21 15 2.66 7.14a2 2 0 0 1 .13-2.2L4.4 2.8A2 2 0 0 1 6 2h12a2 2 0 0 1 1.6.8l1.6 2.14a2 2 0 0 1 .14 2.2L16.79 15",key:"143lza"}],["path",{d:"M11 12 5.12 2.2",key:"qhuxz6"}],["path",{d:"m13 12 5.88-9.8",key:"hbye0f"}],["path",{d:"M8 7h8",key:"i86dvs"}],["circle",{cx:"12",cy:"17",r:"5",key:"qbz8iq"}],["path",{d:"M12 18v-2h-.5",key:"fawc4q"}]]};w.node;const K=o(w);const z={name:"moon-star",size:24,node:[["path",{d:"M18 5h4",key:"1lhgn2"}],["path",{d:"M20 3v4",key:"1olli1"}],["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",key:"kfwtm"}]]};z.node;const U=o(z);const A={name:"rocket",size:24,node:[["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}],["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09",key:"u4xsad"}],["path",{d:"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z",key:"676m9"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05",key:"92ym6u"}]]};A.node;const W=o(A);const D={name:"trending-up",size:24,node:[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]]};D.node;const Y=o(D),$=[{id:"token_titan",scope:"cloud",icon:V,format:"tokens",art:"/achievements/token-titan.png"},{id:"big_day",scope:"cloud",icon:R,format:"tokens",art:"/achievements/big-day.png"},{id:"wordsmith",scope:"cloud",icon:Z,format:"tokens",art:"/achievements/wordsmith.png"},{id:"marathoner",scope:"cloud",icon:P,format:"days",art:"/achievements/marathoner.png"},{id:"streak",scope:"cloud",icon:F,format:"days",art:"/achievements/streak.png"},{id:"weekend_warrior",scope:"cloud",icon:I,format:"days",art:"/achievements/weekend-warrior.png"},{id:"momentum",scope:"cloud",icon:Y,format:"multiplier",art:"/achievements/momentum.png"},{id:"polyglot",scope:"cloud",icon:S,format:"count",art:"/achievements/polyglot.png"},{id:"trendsetter",scope:"cloud",icon:W,format:"count",art:"/achievements/trendsetter.png"},{id:"multitool",scope:"cloud",icon:N,format:"count",art:"/achievements/multitool.png"},{id:"podium",scope:"cloud",icon:K,format:"rank",art:"/achievements/podium.png"},{id:"veteran",scope:"cloud",icon:G,format:"days",art:"/achievements/veteran.png"},{id:"project_hopper",scope:"local",icon:O,format:"count",art:"/achievements/project-hopper.png"},{id:"project_devotion",scope:"local",icon:X,format:"tokens",art:"/achievements/project-devotion.png"},{id:"night_owl",scope:"local",icon:U,format:"count",art:"/achievements/night-owl.png"}],j=new Map($.map(e=>[e.id,e])),m=new Map($.map((e,a)=>[e.id,a]));function de(e,a){return`achievements.badge.${e}.${a}`}function J(e){return[...e||[]].sort((a,t)=>{const c=(t?.tier||0)-(a?.tier||0);return c!==0?c:(m.get(a?.id)??99)-(m.get(t?.id)??99)})}function re(e){const a=(e||[]).filter(t=>t&&(t.tier||0)>=1&&j.has(t.id));return a.length===0?null:J(a)[0]}const E={1:"bronze",2:"silver",3:"gold",4:"diamond"},ie=["bronze","silver","gold","diamond"],Q={bronze:{rim:["#a06a44","#5c3a22"],face:["#d29a6c","#96602f"],glyph:"#3f2817",ring:"#e8c3a0",sheen:"rgba(255, 236, 220, 0.45)",label:"#a06a44"},silver:{rim:["#aab3bb","#67707b"],face:["#dde2e7","#9ba5ae"],glyph:"#39424b",ring:"#f2f5f7",sheen:"rgba(255, 255, 255, 0.55)",label:"#8b959f"},gold:{rim:["#c9971c","#8a6508"],face:["#f2ca52","#cf9c26"],glyph:"#5c430e",ring:"#ffe9a8",sheen:"rgba(255, 248, 214, 0.55)",label:"#b8860b"},diamond:{rim:["#62b1e0","#7c6fd9"],face:["#c8ecf6","#84b4ea"],glyph:"#27496d",ring:"#eafcff",sheen:"rgba(255, 255, 255, 0.65)",label:"#5a9bd8"}},g={rim:["#a8ada9","#7c817d"],face:["#cdd2ce","#a3a8a4"],glyph:"#6a706c",ring:"#dde1de",sheen:"rgba(255, 255, 255, 0.25)",label:"#8b908c"};function ee(e,a=!1){return a||!e||e<1?g:Q[E[Math.min(e,4)]]||g}function se(e){return E[e]||null}const f={xs:18,sm:24,md:44,xl:88,lg:108},y={xs:.75,sm:1,md:1.5,xl:2,lg:2};function le({badgeId:e,tier:a=0,size:t="md",locked:c=!1,label:i,className:L=""}){const s=j.get(e),p=c||!a||a<1,n=ee(a,p),d=t==="fill",l=d?"100%":f[t]||f.md,T=d?4:y[t]||y.md,[C,q]=H.useState(!1),h=s?.icon||null,B=!!s?.art&&!C;return r.jsx("span",{role:i?"img":void 0,"aria-label":i||void 0,"aria-hidden":i?void 0:!0,className:`inline-flex shrink-0 rounded-full ${L}`,style:{width:l,height:l,padding:T,background:`linear-gradient(135deg, ${n.rim[0]} 0%, ${n.ring} 42%, ${n.rim[1]} 100%)`,boxShadow:d||t==="md"||t==="xl"||t==="lg"?"0 0 0 1px rgba(10, 15, 12, 0.06), 0 1px 2px rgba(10, 15, 12, 0.08)":void 0},children:B?r.jsx("img",{src:s.art,alt:"",draggable:!1,loading:"lazy",onError:()=>q(!0),className:"h-full w-full rounded-full object-cover",style:p?{filter:"grayscale(1)",opacity:.55}:void 0}):r.jsx("span",{className:"flex h-full w-full items-center justify-center rounded-full",style:{background:`linear-gradient(145deg, ${n.face[0]}, ${n.face[1]})`},children:h?r.jsx(h,{size:d?64:Math.round(l*.5),color:n.glyph,strokeWidth:2}):null})})}export{le as A,$ as B,X as H,Y as T,Q as a,de as b,j as c,ie as d,re as h,ee as p,J as s,se as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as b,r as l,j as r,c as e,f as v}from"./__vite-browser-external-Bp0fEMn1.js";const f={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1,VITE_APP_VERSION:"1.4.14",VITE_INSFORGE_ANON_KEY:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3OC0xMjM0LTU2NzgtOTBhYi1jZGVmMTIzNDU2NzgiLCJlbWFpbCI6ImFub25AaW5zZm9yZ2UuY29tIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODExNDU5NDd9.T0auta_IrVIh0uXW1bob5QSnzvsnJmN28r5XkSGEuQY",VITE_INSFORGE_BASE_URL:"https://srctyff5.us-east.insforge.app",VITE_SENTRY_DSN:"https://5f9fc9cc76a1d9320197c63a4cbef478@o4512274440781824.ingest.us.sentry.io/4512286762139648"};function x(){return v(),`${(f&&"https://srctyff5.us-east.insforge.app"||"https://srctyff5.us-east.insforge.app").replace(/\/$/,"")}/functions/tokentracker-device-flow-grant`}function p(s){return(s||"").toUpperCase().replace(/\s+/g,"").replace(/[^A-Z0-9-]/g,"")}function h(){const s=b(),[m,g]=l.useState(""),[i,a]=l.useState({kind:"idle",message:""});l.useEffect(()=>{try{const d=new URLSearchParams(window.location.search).get("user_code");d&&g(p(d))}catch{}},[]);async function u(c){if(c?.preventDefault?.(),!s?.signedIn){a({kind:"error",message:e("device.approval.error.sign_in")});return}const d=p(m);if(!/^[A-HJKMNP-Z2-9]{4}-[A-HJKMNP-Z2-9]{4}$/.test(d)){a({kind:"error",message:e("device.approval.error.invalid_code")});return}a({kind:"working",message:e("device.approval.action.approving")});try{const n=await s.getAccessToken?.();if(!n){a({kind:"error",message:e("device.approval.error.no_token")});return}const o=await fetch(x(),{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({user_code:d})}),t=await o.json().catch(()=>({}));o.ok?a({kind:"success",message:t.status==="already_approved"?t.client_info?e("device.approval.success.already_client",{client:t.client_info}):e("device.approval.success.already"):t.client_info?e("device.approval.success.approved_client",{client:t.client_info}):e("device.approval.success.approved")}):o.status===404?a({kind:"error",message:e("device.approval.error.not_found")}):o.status===410?a({kind:"error",message:e("device.approval.error.expired")}):o.status===401?a({kind:"error",message:e("device.approval.error.session_expired")}):a({kind:"error",message:t?.error?e("device.approval.error.server_detail",{message:t.error}):e("device.approval.error.server",{status:o.status})})}catch(n){a({kind:"error",message:e("device.approval.error.network",{message:n?.message||n})})}}return r.jsx("div",{className:"min-h-screen flex items-center justify-center bg-white dark:bg-oai-gray-950 px-6 py-12",children:r.jsxs("div",{className:"w-full max-w-md rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 p-8",children:[r.jsx("h1",{className:"text-2xl font-semibold text-oai-gray-900 dark:text-white mb-2",children:e("device.approval.title")}),r.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400 mb-6",children:e("device.approval.description")}),s?.signedIn?null:r.jsx("div",{className:"rounded-md border border-amber-200 bg-amber-50 dark:bg-amber-900/20 dark:border-amber-800 p-4 mb-4",children:r.jsx("p",{className:"text-sm text-amber-700 dark:text-amber-300",children:e("device.approval.sign_in_banner")})}),r.jsxs("form",{onSubmit:u,className:"space-y-4",children:[r.jsxs("label",{className:"block",children:[r.jsx("span",{className:"block text-xs font-medium uppercase tracking-wide text-oai-gray-500 dark:text-oai-gray-400 mb-1",children:e("device.approval.code_label")}),r.jsx("input",{type:"text",value:m,onChange:c=>g(p(c.target.value)),placeholder:"XXXX-XXXX",spellCheck:!1,autoCapitalize:"characters",maxLength:9,className:"w-full font-mono text-xl tracking-widest text-center rounded-md border border-oai-gray-300 dark:border-oai-gray-700 bg-white dark:bg-oai-gray-900 text-oai-gray-900 dark:text-white px-4 py-3 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-emerald-600"})]}),r.jsx("button",{type:"submit",disabled:i.kind==="working"||!s?.signedIn,className:"w-full rounded-md bg-emerald-600 hover:bg-emerald-500 disabled:bg-oai-gray-300 disabled:cursor-not-allowed text-white font-medium py-3 transition-colors",children:i.kind==="working"?e("device.approval.action.approving"):e("device.approval.action.approve")})]}),i.kind==="success"&&r.jsx("div",{className:"mt-4 rounded-md border border-emerald-200 bg-emerald-50 dark:bg-emerald-900/20 dark:border-emerald-800 p-4",children:r.jsx("p",{className:"text-sm text-emerald-700 dark:text-emerald-300",children:i.message})}),i.kind==="error"&&r.jsx("div",{className:"mt-4 rounded-md border border-red-200 bg-red-50 dark:bg-red-900/20 dark:border-red-800 p-4",children:r.jsx("p",{className:"text-sm text-red-700 dark:text-red-300",children:i.message})})]})})}export{h as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,J as a}from"./__vite-browser-external-Bp0fEMn1.js";import{u as p}from"./DialogTitle-Dc0-pI0j.js";import{u as c}from"./composite-UMkd0X7k.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};
|