tokentracker-cli 0.84.5 → 0.84.7
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.de.md +10 -8
- package/README.ja.md +9 -7
- package/README.ko.md +9 -7
- package/README.md +12 -10
- package/README.zh-CN.md +9 -7
- package/dashboard/dist/assets/{AchievementBadge-DJvRfvu1.js → AchievementBadge-DAPsTMsx.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-Cr8WToxS.js → AchievementsPage-B9oXfXvm.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BCPYB46e.js → AchievementsSection-DI_LTZb3.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-D-12Ek-H.js → BadgeDetailModal--WCrJWVa.js} +1 -1
- package/dashboard/dist/assets/Card-BzbSN3EC.js +1 -0
- package/dashboard/dist/assets/{ClawdAnimated-DoGgAppT.js → ClawdAnimated-Dgxae1At.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-DIKocEAD.js → CommandPalette-BiZBYV5A.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BNuMbEWy.js → CommunityStatsModal-C6jZx10_.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-F2gPIIS9.js → ConfirmModal-lD-mHMvO.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-YvoCEGG9.js → DashboardPage-DCPNoiRG.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-CbC_J22s.js → DevicePage-DiF2IOjJ.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-VEynQCYg.js → DialogClose-Crx2EFnI.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-CHsHo924.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-BbAVHI_v.js → DialogTitle-amHXttsp.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CZVqSnis.js → FadeIn-CIhF_9PP.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-Cs_h332Y.js → HeaderGithubStar-D6Ph2GuY.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-DvvHTphK.js → HoverTooltip-BS8eO6tj.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DPVmqSS_.js → IpCheckPage-Cz1Je6WS.js} +1 -1
- package/dashboard/dist/assets/LandingPage-DqeXJfwn.js +2 -0
- package/dashboard/dist/assets/{LeaderboardAvatar-BAPZ2ROX.js → LeaderboardAvatar-D09yLoVA.js} +1 -1
- package/dashboard/dist/assets/LeaderboardPage-BetpFetS.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-r03akfe2.js → LeaderboardProfileModal-UW3KnANZ.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-CU63YW2M.js → LeaderboardProfilePage-Chd_2AVY.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-uhpNteXJ.js +4 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-BHlcPCZk.js → LocalOnlyNotice-CpFFX8ro.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-C-dqUumR.js → LoginCard-DkZpGnVu.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-BFzRg4Iy.js → LoginModal-CALv6jkD.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-QtlnGF1S.js → LoginPage-BZuXHYAT.js} +1 -1
- package/dashboard/dist/assets/PetPage-C1LuDyYr.js +1 -0
- package/dashboard/dist/assets/{PopoverPopup-CltmqcrT.js → PopoverPopup-CluVhrMr.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-BqOI9EiD.js → ProviderIcon-D3YKjVKb.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-BsfCBUZD.js → ResetPasswordPage-CzN50nX5.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-D4biXw_Q.js → SegmentedControl-DFOAcEKU.js} +1 -1
- package/dashboard/dist/assets/{Select-DTw-14iE.js → Select-B3W_ihs_.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-3tUrnu3f.js → SelectItemText-FXRtXnFV.js} +1 -1
- package/dashboard/dist/assets/ServiceStatusPage-BItZqH7q.js +1 -0
- package/dashboard/dist/assets/{SessionsPage-DYYlNqHg.js → SessionsPage-DVQU-hTH.js} +2 -2
- package/dashboard/dist/assets/SettingsPage-ByvrwOi6.js +1 -0
- package/dashboard/dist/assets/SkillsPage-DrGr5Qij.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-DU77e8sM.js → TokenGalaxy-ClF_n9od.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-DV3KGf-a.js → ToolbarRootContext-B82hGhAf.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-D6QYKmbl.js → TrendMonitor-DkWm7_1z.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-DSP4jCsH.js → WidgetsPage-DCY4pyR2.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-B0x6O_DU.js → WrappedPage-yGQ4DLqj.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-BdRrPLk6.js +1483 -0
- package/dashboard/dist/assets/__vite-browser-external-hXV_ZYTG.css +1 -0
- package/dashboard/dist/assets/{agent-logos-B2S_cdOL.js → agent-logos-D5SHGOxB.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-DNUVuhx2.js → arrow-up-right-CbMcAcss.js} +1 -1
- package/dashboard/dist/assets/{check-C3ARiiE_.js → check-Dyzh7lTz.js} +1 -1
- package/dashboard/dist/assets/{chevron-right-3bY7H8jO.js → chevron-right-DT06ZIKC.js} +1 -1
- package/dashboard/dist/assets/{copy-C-UIMKoO.js → copy-B3Kt3Wrf.js} +1 -1
- package/dashboard/dist/assets/{download-DtRzJfcr.js → download-B3rOytqI.js} +1 -1
- package/dashboard/dist/assets/external-link-BamTP0Ks.js +1 -0
- package/dashboard/dist/assets/{flame-Bas3ry-g.js → flame-msy2h1ms.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-DFtRUIH8.js → format-tokens-8b1OUM_8.js} +1 -1
- package/dashboard/dist/assets/{icons-CHBt4Tmo.js → icons-C2NR2_Tf.js} +1 -1
- package/dashboard/dist/assets/{index-CZRlrK7w.js → index-C8sDJzUY.js} +1 -1
- package/dashboard/dist/assets/{index-C1ADnvzm.js → index-CvTfpiCF.js} +1 -1
- package/dashboard/dist/assets/{index-4unZlm2w.js → index-x3vFJQFt.js} +1 -1
- package/dashboard/dist/assets/{info-BkrO-fg3.js → info-C9j23aGq.js} +1 -1
- package/dashboard/dist/assets/limits-providers-ySB9Ci5l.js +1 -0
- package/dashboard/dist/assets/{link-2-DTLFz8uG.js → link-2-rrcyTT6S.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-BqQW_Ipq.js → loader-circle-x9x9I8Hv.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-YdpeE4dq.js → maximize-2-7TWFb2k7.js} +1 -1
- package/dashboard/dist/assets/{provider-display-CgBOpjq0.js → provider-display-JbSwpvxU.js} +1 -1
- package/dashboard/dist/assets/{react-DD9jpv--.js → react-DK5clHtY.js} +1 -1
- package/dashboard/dist/assets/{search--TvX-fig.js → search-DukXRvEV.js} +1 -1
- package/dashboard/dist/assets/{skills-api-C5C8a1at.js → skills-api-BUFJuwiR.js} +1 -1
- package/dashboard/dist/assets/{store-CRA8gPnP.js → store-BTh7bv09.js} +1 -1
- package/dashboard/dist/assets/{terminal-BXZyl2ek.js → terminal-CcUqLtn1.js} +1 -1
- package/dashboard/dist/assets/{trash-2-IFdBQ65v.js → trash-2-Bq7hnvNq.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-LyJ5aMpX.js → use-community-stats-qTpx6ugG.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CKvFS1Nn.js → use-limits-display-prefs-Di_J4eVv.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-Cg-4j_0z.js → use-native-settings-CIqpv9Z3.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-2pX5PXZO.js → use-ordered-list-OwogSXT7.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-5QGGPoom.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-DilJOODA.js → use-session-efficiency-pref-CjAvMh7u.js} +1 -1
- package/dashboard/dist/assets/{use-transform-DiyYTLpI.js → use-transform-KZLdt-NZ.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DkUmp_nW.js → use-usage-limits-Tbifa037.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-lm7KjGoD.js → useCurrency-D2td-bkn.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DVSyJ-GE.js → useScrollLock-Cvyfyezc.js} +1 -1
- package/dashboard/dist/assets/useTokenFormat-DYIUS6IH.js +1 -0
- package/dashboard/dist/assets/{zap-CKZt1_i5.js → zap-B19mEed5.js} +1 -1
- package/dashboard/dist/brand-logos/qoder.svg +5 -0
- package/dashboard/dist/index.html +18 -15
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/llms.txt +3 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/commands/init.js +1 -0
- package/src/commands/status.js +34 -7
- package/src/commands/sync.js +353 -5
- package/src/lib/codex-rollout-parser.js +17 -39
- package/src/lib/codex-token-usage.js +195 -0
- package/src/lib/local-api.js +4 -1
- package/src/lib/pet-packages.js +44 -2
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/provider-status.js +104 -0
- package/src/lib/qoder-limits.js +985 -0
- package/src/lib/rollout.js +744 -91
- package/src/lib/usage-limits.js +243 -43
- package/dashboard/dist/assets/Card-Ha3x9zIj.js +0 -1
- package/dashboard/dist/assets/DialogDescription-B9bb9Ar0.js +0 -1
- package/dashboard/dist/assets/LandingPage-BCvNh3zE.js +0 -2
- package/dashboard/dist/assets/LeaderboardPage-DZSxazHP.js +0 -2
- package/dashboard/dist/assets/LimitsPage-JWZDa5_Y.js +0 -4
- package/dashboard/dist/assets/PetPage-C6UMY2qD.js +0 -1
- package/dashboard/dist/assets/SettingsPage-CYlEboSl.js +0 -1
- package/dashboard/dist/assets/SkillsPage-Bzq8V5Pd.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-D3llGq6p.js +0 -1465
- package/dashboard/dist/assets/__vite-browser-external-WKilYZnq.css +0 -1
- package/dashboard/dist/assets/limits-providers-BtwYmYHA.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-BCR01kRg.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-Li8BANKO.js +0 -1
|
@@ -11,6 +11,12 @@ const path = require("node:path");
|
|
|
11
11
|
const readline = require("node:readline");
|
|
12
12
|
|
|
13
13
|
const { listRolloutFiles } = require("./rollout");
|
|
14
|
+
const {
|
|
15
|
+
consumeUsageDelta,
|
|
16
|
+
createUsageDeltaState,
|
|
17
|
+
snapshotUsageBaselines,
|
|
18
|
+
totalsReset,
|
|
19
|
+
} = require("./codex-token-usage");
|
|
14
20
|
const {
|
|
15
21
|
emptyTotals,
|
|
16
22
|
addInto,
|
|
@@ -434,42 +440,10 @@ function normalizeUsage(u) {
|
|
|
434
440
|
return out;
|
|
435
441
|
}
|
|
436
442
|
|
|
437
|
-
function totalsReset(curr, prev) {
|
|
438
|
-
const a = Number(curr?.total_tokens);
|
|
439
|
-
const b = Number(prev?.total_tokens);
|
|
440
|
-
if (!Number.isFinite(a) || !Number.isFinite(b)) return false;
|
|
441
|
-
return a < b;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
443
|
function pickDelta(lastUsage, totalUsage, prevTotals) {
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (hasTotal && hasPrev) {
|
|
450
|
-
if (totalsReset(totalUsage, prevTotals)) {
|
|
451
|
-
const resetUsage = hasLast ? lastUsage : totalUsage;
|
|
452
|
-
return normalizeUsage(resetUsage);
|
|
453
|
-
}
|
|
454
|
-
const delta = {};
|
|
455
|
-
for (const k of [
|
|
456
|
-
"input_tokens",
|
|
457
|
-
"cached_input_tokens",
|
|
458
|
-
"cache_creation_input_tokens",
|
|
459
|
-
"output_tokens",
|
|
460
|
-
"reasoning_output_tokens",
|
|
461
|
-
"total_tokens",
|
|
462
|
-
]) {
|
|
463
|
-
const a = Number(totalUsage[k]);
|
|
464
|
-
const b = Number(prevTotals[k]);
|
|
465
|
-
if (Number.isFinite(a) && Number.isFinite(b)) delta[k] = Math.max(0, a - b);
|
|
466
|
-
}
|
|
467
|
-
return normalizeUsage(delta);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (hasLast) return normalizeUsage(lastUsage);
|
|
471
|
-
if (hasTotal) return normalizeUsage(totalUsage);
|
|
472
|
-
return null;
|
|
444
|
+
const state = createUsageDeltaState({ lastTotal: prevTotals });
|
|
445
|
+
const delta = consumeUsageDelta(state, lastUsage, totalUsage);
|
|
446
|
+
return delta ? normalizeUsage(delta) : null;
|
|
473
447
|
}
|
|
474
448
|
|
|
475
449
|
// ---------------------------------------------------------------------------
|
|
@@ -596,7 +570,10 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
596
570
|
let provider = isResuming ? resumeState.provider || null : null;
|
|
597
571
|
let cliVersion = isResuming ? resumeState.cliVersion || null : null;
|
|
598
572
|
|
|
599
|
-
|
|
573
|
+
const usageDeltaState = createUsageDeltaState({
|
|
574
|
+
lastTotal: isResuming ? resumeState.prevTotals || null : null,
|
|
575
|
+
baselines: isResuming ? resumeState.tokenUsageBaselines || null : null,
|
|
576
|
+
});
|
|
600
577
|
let pendingToolNames = isResuming
|
|
601
578
|
? Array.from(resumeState.pendingToolNames || [])
|
|
602
579
|
: [];
|
|
@@ -875,8 +852,8 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
875
852
|
const info = tokenCount.info;
|
|
876
853
|
const lastUsage = info?.last_token_usage;
|
|
877
854
|
const totalUsage = info?.total_token_usage;
|
|
878
|
-
const
|
|
879
|
-
|
|
855
|
+
const rawDelta = consumeUsageDelta(usageDeltaState, lastUsage, totalUsage);
|
|
856
|
+
const delta = rawDelta ? normalizeUsage(rawDelta) : null;
|
|
880
857
|
const isNewResumeEvent = noteTokenEvent(ts, lastUsage, totalUsage);
|
|
881
858
|
if (inRequestedRange) {
|
|
882
859
|
const eventSessionId = sessionId || rolloutSessionIdFromPath(primaryFilePath) || primaryFilePath;
|
|
@@ -927,7 +904,8 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
927
904
|
model,
|
|
928
905
|
provider,
|
|
929
906
|
cliVersion,
|
|
930
|
-
prevTotals,
|
|
907
|
+
prevTotals: usageDeltaState.lastTotal,
|
|
908
|
+
tokenUsageBaselines: snapshotUsageBaselines(usageDeltaState),
|
|
931
909
|
pendingToolNames,
|
|
932
910
|
pendingSkills,
|
|
933
911
|
pendingExecDetails,
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// A rollout can contain TokenCount events from more than one Codex SessionState
|
|
4
|
+
// (for example, a parent agent and an embedded reviewer). Each SessionState has
|
|
5
|
+
// its own cumulative total, but the event does not carry a stable stream id.
|
|
6
|
+
// Recover the stream from Codex's invariant: total - last = that stream's prior
|
|
7
|
+
// total. Keeping a small LRU of stream heads also makes repeated rate-limit
|
|
8
|
+
// snapshots idempotent when the streams are interleaved.
|
|
9
|
+
|
|
10
|
+
const USAGE_FIELDS = [
|
|
11
|
+
"input_tokens",
|
|
12
|
+
"cached_input_tokens",
|
|
13
|
+
"cache_creation_input_tokens",
|
|
14
|
+
"output_tokens",
|
|
15
|
+
"reasoning_output_tokens",
|
|
16
|
+
"total_tokens",
|
|
17
|
+
];
|
|
18
|
+
const MAX_USAGE_BASELINES = 32;
|
|
19
|
+
|
|
20
|
+
function canonicalUsage(value) {
|
|
21
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
22
|
+
const out = {};
|
|
23
|
+
let sawNumber = false;
|
|
24
|
+
for (const field of USAGE_FIELDS) {
|
|
25
|
+
const raw = field === "cache_creation_input_tokens"
|
|
26
|
+
? value.cache_creation_input_tokens ?? value.cache_write_input_tokens
|
|
27
|
+
: value[field];
|
|
28
|
+
const number = Number(raw);
|
|
29
|
+
if (Number.isFinite(number) && number >= 0) {
|
|
30
|
+
out[field] = Math.floor(number);
|
|
31
|
+
sawNumber = true;
|
|
32
|
+
} else {
|
|
33
|
+
out[field] = 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return sawNumber ? out : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function usageSignature(value) {
|
|
40
|
+
const usage = canonicalUsage(value);
|
|
41
|
+
return usage ? USAGE_FIELDS.map((field) => usage[field]).join(":") : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sameUsage(left, right) {
|
|
45
|
+
const leftSignature = usageSignature(left);
|
|
46
|
+
return leftSignature !== null && leftSignature === usageSignature(right);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function subtractUsage(totalUsage, lastUsage) {
|
|
50
|
+
const total = canonicalUsage(totalUsage);
|
|
51
|
+
const last = canonicalUsage(lastUsage);
|
|
52
|
+
if (!total || !last) return null;
|
|
53
|
+
const previous = {};
|
|
54
|
+
for (const field of USAGE_FIELDS) {
|
|
55
|
+
if (total[field] < last[field]) return null;
|
|
56
|
+
previous[field] = total[field] - last[field];
|
|
57
|
+
}
|
|
58
|
+
return previous;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function diffUsage(totalUsage, previousUsage) {
|
|
62
|
+
const total = canonicalUsage(totalUsage);
|
|
63
|
+
const previous = canonicalUsage(previousUsage);
|
|
64
|
+
if (!total || !previous) return null;
|
|
65
|
+
const delta = {};
|
|
66
|
+
for (const field of USAGE_FIELDS) {
|
|
67
|
+
delta[field] = Math.max(0, total[field] - previous[field]);
|
|
68
|
+
}
|
|
69
|
+
return delta;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function totalsReset(totalUsage, previousUsage) {
|
|
73
|
+
const total = canonicalUsage(totalUsage);
|
|
74
|
+
const previous = canonicalUsage(previousUsage);
|
|
75
|
+
return Boolean(total && previous && total.total_tokens < previous.total_tokens);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createUsageDeltaState({ lastTotal = null, baselines = null } = {}) {
|
|
79
|
+
const state = {
|
|
80
|
+
lastTotal: null,
|
|
81
|
+
baselines: [],
|
|
82
|
+
sawDivergentCumulative: false,
|
|
83
|
+
sawInterleaved: false,
|
|
84
|
+
};
|
|
85
|
+
for (const baseline of Array.isArray(baselines) ? baselines : []) {
|
|
86
|
+
touchBaseline(state, baseline);
|
|
87
|
+
}
|
|
88
|
+
if (canonicalUsage(lastTotal)) touchBaseline(state, lastTotal);
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function snapshotUsageBaselines(state) {
|
|
93
|
+
return Array.isArray(state?.baselines)
|
|
94
|
+
? state.baselines.map((usage) => ({ ...usage }))
|
|
95
|
+
: [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function consumeUsageDelta(state, lastUsage, totalUsage) {
|
|
99
|
+
if (!state || typeof state !== "object") {
|
|
100
|
+
throw new TypeError("usage delta state is required");
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(state.baselines)) state.baselines = [];
|
|
103
|
+
|
|
104
|
+
const last = canonicalUsage(lastUsage);
|
|
105
|
+
const total = canonicalUsage(totalUsage);
|
|
106
|
+
if (!total) return last;
|
|
107
|
+
|
|
108
|
+
const activeSignature = usageSignature(state.lastTotal);
|
|
109
|
+
const duplicateIndex = findBaselineIndex(state, total);
|
|
110
|
+
if (duplicateIndex >= 0) {
|
|
111
|
+
if (
|
|
112
|
+
activeSignature !== null &&
|
|
113
|
+
usageSignature(state.baselines[duplicateIndex]) !== activeSignature
|
|
114
|
+
) {
|
|
115
|
+
state.sawInterleaved = true;
|
|
116
|
+
}
|
|
117
|
+
touchBaselineAt(state, duplicateIndex, total);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (last) {
|
|
122
|
+
const expectedPrevious = subtractUsage(total, last);
|
|
123
|
+
const lineageIndex = findBaselineIndex(state, expectedPrevious);
|
|
124
|
+
if (lineageIndex >= 0) {
|
|
125
|
+
if (
|
|
126
|
+
activeSignature !== null &&
|
|
127
|
+
usageSignature(state.baselines[lineageIndex]) !== activeSignature
|
|
128
|
+
) {
|
|
129
|
+
state.sawInterleaved = true;
|
|
130
|
+
}
|
|
131
|
+
touchBaselineAt(state, lineageIndex, total);
|
|
132
|
+
return last;
|
|
133
|
+
}
|
|
134
|
+
if (expectedPrevious) {
|
|
135
|
+
if (canonicalUsage(state.lastTotal)) state.sawDivergentCumulative = true;
|
|
136
|
+
touchBaseline(state, total);
|
|
137
|
+
return last;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const active = canonicalUsage(state.lastTotal);
|
|
142
|
+
if (active && !totalsReset(total, active)) {
|
|
143
|
+
const delta = diffUsage(total, active);
|
|
144
|
+
if (!last || Number(delta?.total_tokens || 0) <= Number(last.total_tokens || 0)) {
|
|
145
|
+
const activeIndex = findBaselineIndex(state, active);
|
|
146
|
+
touchBaselineAt(state, activeIndex, total);
|
|
147
|
+
return delta;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// A cumulative jump larger than last usage cannot come from the normal
|
|
151
|
+
// append_last_usage path. Treat it as a new stream instead of charging the
|
|
152
|
+
// unrelated cumulative gap; a later event will establish its lineage.
|
|
153
|
+
state.sawDivergentCumulative = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
touchBaseline(state, total);
|
|
157
|
+
return last || total;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function findBaselineIndex(state, usage) {
|
|
161
|
+
const signature = usageSignature(usage);
|
|
162
|
+
if (signature === null) return -1;
|
|
163
|
+
return state.baselines.findIndex((baseline) => usageSignature(baseline) === signature);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function touchBaselineAt(state, index, usage) {
|
|
167
|
+
const canonical = canonicalUsage(usage);
|
|
168
|
+
if (!canonical) return;
|
|
169
|
+
if (Number.isInteger(index) && index >= 0 && index < state.baselines.length) {
|
|
170
|
+
state.baselines.splice(index, 1);
|
|
171
|
+
} else {
|
|
172
|
+
const duplicateIndex = findBaselineIndex(state, canonical);
|
|
173
|
+
if (duplicateIndex >= 0) state.baselines.splice(duplicateIndex, 1);
|
|
174
|
+
}
|
|
175
|
+
state.baselines.push(canonical);
|
|
176
|
+
while (state.baselines.length > MAX_USAGE_BASELINES) state.baselines.shift();
|
|
177
|
+
state.lastTotal = canonical;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function touchBaseline(state, usage) {
|
|
181
|
+
touchBaselineAt(state, findBaselineIndex(state, usage), usage);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
MAX_USAGE_BASELINES,
|
|
186
|
+
canonicalUsage,
|
|
187
|
+
consumeUsageDelta,
|
|
188
|
+
createUsageDeltaState,
|
|
189
|
+
diffUsage,
|
|
190
|
+
sameUsage,
|
|
191
|
+
snapshotUsageBaselines,
|
|
192
|
+
subtractUsage,
|
|
193
|
+
totalsReset,
|
|
194
|
+
usageSignature,
|
|
195
|
+
};
|
package/src/lib/local-api.js
CHANGED
|
@@ -2684,7 +2684,10 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
2684
2684
|
json(res, { importable, codexDetected: importable.length > 0 });
|
|
2685
2685
|
return true;
|
|
2686
2686
|
}
|
|
2687
|
-
json(res, {
|
|
2687
|
+
json(res, {
|
|
2688
|
+
pets: pets.listInstalledPets(),
|
|
2689
|
+
hiddenBuiltinIds: pets.readHiddenBuiltinIds(),
|
|
2690
|
+
});
|
|
2688
2691
|
return true;
|
|
2689
2692
|
}
|
|
2690
2693
|
if (method === "POST") {
|
package/src/lib/pet-packages.js
CHANGED
|
@@ -10,6 +10,8 @@ const MAX_PACKAGE_BYTES = 12 * 1024 * 1024;
|
|
|
10
10
|
const MAX_MANIFEST_BYTES = 16 * 1024;
|
|
11
11
|
const MAX_SPRITESHEET_BYTES = 10 * 1024 * 1024;
|
|
12
12
|
const BUILTIN_IDS = new Set(["clawd", "sprout", "byte", "ember"]);
|
|
13
|
+
const REMOVABLE_BUILTIN_IDS = new Set(["sprout", "byte", "ember"]);
|
|
14
|
+
const HIDDEN_BUILTINS_FILE = ".hidden-builtins.json";
|
|
13
15
|
// codex-pets.net added `kind` to pet.json long after launch, and its enum may keep
|
|
14
16
|
// growing — packages published before that (and future kinds) must stay importable,
|
|
15
17
|
// so kind is optional metadata validated only as a short slug.
|
|
@@ -35,6 +37,44 @@ function resolveCodexPetsDir() {
|
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
function readHiddenBuiltinIds() {
|
|
41
|
+
const file = path.join(resolvePetsDir(), HIDDEN_BUILTINS_FILE);
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
44
|
+
if (!Array.isArray(parsed)) return [];
|
|
45
|
+
return [...new Set(parsed
|
|
46
|
+
.map((id) => String(id || "").trim().toLowerCase())
|
|
47
|
+
.filter((id) => REMOVABLE_BUILTIN_IDS.has(id)))].sort();
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hideBuiltinPet(id) {
|
|
54
|
+
const normalized = String(id || "").trim().toLowerCase();
|
|
55
|
+
if (normalized === "clawd") throw new Error("Clawd cannot be removed");
|
|
56
|
+
if (!REMOVABLE_BUILTIN_IDS.has(normalized)) throw new Error("Invalid built-in pet id");
|
|
57
|
+
|
|
58
|
+
const root = resolvePetsDir();
|
|
59
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
60
|
+
const hidden = new Set(readHiddenBuiltinIds());
|
|
61
|
+
hidden.add(normalized);
|
|
62
|
+
const destination = path.join(root, HIDDEN_BUILTINS_FILE);
|
|
63
|
+
const stage = path.join(root, `${HIDDEN_BUILTINS_FILE}.${process.pid}-${crypto.randomBytes(6).toString("hex")}`);
|
|
64
|
+
const backup = `${stage}-backup`;
|
|
65
|
+
try {
|
|
66
|
+
fs.writeFileSync(stage, `${JSON.stringify([...hidden].sort(), null, 2)}\n`, { mode: 0o600 });
|
|
67
|
+
if (fs.existsSync(destination)) fs.renameSync(destination, backup);
|
|
68
|
+
fs.renameSync(stage, destination);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
try { fs.rmSync(stage, { force: true }); } catch {}
|
|
71
|
+
if (!fs.existsSync(destination) && fs.existsSync(backup)) fs.renameSync(backup, destination);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
try { fs.rmSync(backup, { force: true }); } catch {}
|
|
75
|
+
return { id: normalized, hidden: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
38
78
|
function readUInt24LE(buffer, offset) {
|
|
39
79
|
return buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16);
|
|
40
80
|
}
|
|
@@ -368,10 +408,11 @@ async function installFromCodexPets(input, fetchImpl = fetch) {
|
|
|
368
408
|
|
|
369
409
|
function removeInstalledPet(id) {
|
|
370
410
|
const normalized = String(id || "").trim().toLowerCase();
|
|
371
|
-
if (!PET_ID_RE.test(normalized)
|
|
411
|
+
if (!PET_ID_RE.test(normalized)) throw new Error("Invalid pet id");
|
|
412
|
+
if (BUILTIN_IDS.has(normalized)) return hideBuiltinPet(normalized);
|
|
372
413
|
const root = resolvePetsDir();
|
|
373
414
|
const destination = path.join(root, normalized);
|
|
374
|
-
if (!destination.startsWith(`${root}${path.sep}`)) throw new Error("Invalid
|
|
415
|
+
if (!destination.startsWith(`${root}${path.sep}`)) throw new Error("Invalid pet path");
|
|
375
416
|
fs.rmSync(destination, { recursive: true, force: true });
|
|
376
417
|
petDirectoryCache.delete(destination);
|
|
377
418
|
return { id: normalized };
|
|
@@ -627,6 +668,7 @@ module.exports = {
|
|
|
627
668
|
petIdFromCodexPetsUrl,
|
|
628
669
|
readCodexBuiltinPets,
|
|
629
670
|
readCodexImportableAsset,
|
|
671
|
+
readHiddenBuiltinIds,
|
|
630
672
|
readWebpDimensions,
|
|
631
673
|
removeInstalledPet,
|
|
632
674
|
resolveCodexPetsDir,
|