tokentracker-cli 1.5.6 → 1.5.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.md +2 -0
- package/dashboard/dist/assets/{AchievementBadge-NKpY___e.js → AchievementBadge-DIHKXKgm.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-9nXmSX72.js → AchievementsPage-U66jjKFf.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DttQciSY.js → AchievementsSection-BAW79xKg.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-bvWEdJbe.js → BadgeDetailModal-IfomOw9B.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-ChuUfph8.js → BotAnimated-B33gSUM3.js} +1 -1
- package/dashboard/dist/assets/{Card-SWtLDXt1.js → Card-dtrkef-y.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-c2fNl1Rl.js → CloudCheckoutPage-jYPgHJYE.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-CfwDjZMS.js → CloudPage-BumdgM5w.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-tWgPa295.js → CommandPalette-CFEeoIo3.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-D7UaaUdM.js → CommunityStatsModal-DL3MrPmb.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-BrBcKhfW.js → ConfirmModal-BRdtCv2g.js} +1 -1
- package/dashboard/dist/assets/{Controls-CcvBkk7W.js → Controls-Bb-_ZAsT.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-GXwt6DoC.js +1 -0
- package/dashboard/dist/assets/{DateRangePopover-BkN_pird.js → DateRangePopover-CHkFCFkE.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-BbUL7WwT.js → DevicePage-Da753sLS.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BXO8SBFr.js → DialogClose-4A_jISLc.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-CiEhDvt9.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-Du332auj.js → DialogTitle-C0oQdI9n.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-CXLYfOt7.js → ElectricLogo-Bv-sB-O-.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-HSQBOc8B.js → FadeIn-Ch2AuGxY.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-D5tyQDGE.js → HeaderGithubStar-C_dy8a5K.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-BhidhbnD.js → HoverTooltip-iuzo5YeN.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-WMEF1oS3.js → IpCheckPage-BKEID9fw.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BmO-59ZP.js → LandingPage-DZt5JWZ7.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-C2WyniEt.js → LeaderboardPage-CYrCQF5P.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-DAZnMRNn.js → LeaderboardProBadge-B8vG2SAW.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-D1BxcloV.js → LeaderboardProfileModal-DpAvBJPT.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-RPJDTwpG.js → LeaderboardProfilePage-CofOk8GO.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-9ZOhKvn5.js → LimitsPage-DXknvxtI.js} +6 -6
- package/dashboard/dist/assets/{LocalOnlyNotice-CH6w4hRw.js → LocalOnlyNotice-BBVCEm5Q.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-D24HsRjs.js → LoginCard-DcPazHo1.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-sLvHyi9U.js → LoginModal-CGXPaeWD.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-PIvLEw1Y.js → LoginPage-Ct6Al1pz.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DYcw_4P1.js → PetPage-8NPEAa-c.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-D7ZCWq5f.js → PopoverPopup-R69ks89h.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-B0kB52K-.js → ProviderIcon-svF44YE0.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-B_NT4Xfl.js → ResetPasswordPage-DJIYKu8C.js} +1 -1
- package/dashboard/dist/assets/{Select-BtxjjP2Q.js → Select-BVK4XZD5.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-Co1M6XRC.js → SelectItemText-Bh74D8lr.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-XadfhFnd.js → SelfHostPage-8gj2lAFp.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-ZEJyAxsQ.js → ServiceStatusPage-CWPV_Mou.js} +1 -1
- package/dashboard/dist/assets/SessionsPage-Bo5plK6O.js +3 -0
- package/dashboard/dist/assets/{SettingsPage-D4qHgIxn.js → SettingsPage-BptJrWn-.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-CkQCk2uF.js → SkillsPage-CRgGXnJS.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-Bx1vr4b0.js → TokenGalaxy-dgOy0Eo7.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-DQW8FWJO.js → TooltipPopup-DpGnyoFz.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-BMNcY3N8.js → TrendMonitor-BEy_GbMg.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-luRTge7O.js → WidgetsPage-ChUelFrP.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-B1j3WgXx.js → WrappedPage-n63RsX8z.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-CvvpsW-U.js → __vite-browser-external-D_JnEwiu.js} +3 -3
- package/dashboard/dist/assets/{agent-logos-DL4fdks6.js → agent-logos-DQ1h5uQF.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-DiOsjgdu.js → arrow-left-Bo3Sg_cg.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-DBFXV7GU.js → arrow-right-DdxkL8Ul.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BIvpDQHo.js → arrow-up-right-CpvelPD2.js} +1 -1
- package/dashboard/dist/assets/{calendar-days--psnXUPW.js → calendar-days-BvNVya0e.js} +1 -1
- package/dashboard/dist/assets/{check-DLqbsN00.js → check-tO40Glrg.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-C0P0oLzP.js → chevron-down-Dnrra3mC.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-NSmb7QNy.js → cloud-checkout-COC4zB8s.js} +1 -1
- package/dashboard/dist/assets/{composite-D7ahPHYQ.js → composite-C0NgnRTl.js} +1 -1
- package/dashboard/dist/assets/{copy-COD7vwd6.js → copy-DTKN2SO4.js} +1 -1
- package/dashboard/dist/assets/{download-CpSUX4U-.js → download-CSq2Yd3D.js} +1 -1
- package/dashboard/dist/assets/{external-link-DXUjrooT.js → external-link-BtGedmuQ.js} +1 -1
- package/dashboard/dist/assets/{flame-Dl3ZT9oo.js → flame-ChjXIFSx.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-Ci49THbK.js → format-tokens-CXqZY114.js} +1 -1
- package/dashboard/dist/assets/{gift-DTHCN1Of.js → gift-YFwBvsUA.js} +1 -1
- package/dashboard/dist/assets/{icons-CB9s1e1k.js → icons-DCiBpbK0.js} +1 -1
- package/dashboard/dist/assets/{index-DFdfNKJM.js → index-B7sRX2EC.js} +1 -1
- package/dashboard/dist/assets/{index-BGT8ZQyn.js → index-DMqIbTL4.js} +1 -1
- package/dashboard/dist/assets/{index-DeX4t_5p.js → index-Dpmdg98T.js} +1 -1
- package/dashboard/dist/assets/{index-Dozwi-Se.js → index-XSya-vft.js} +1 -1
- package/dashboard/dist/assets/{info-CqM2wmSI.js → info-PYzcoobJ.js} +1 -1
- package/dashboard/dist/assets/{link-2-Byo7Rj3t.js → link-2-BxSrdKyR.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CMxd78Na.js → loader-circle-CgvCCDTH.js} +1 -1
- package/dashboard/dist/assets/{local-usage-transfer-CBjnoeck.js → local-usage-transfer-eTzMdhbs.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-Oowlk_FC.js → modifiers.esm-B796CRz-.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-B2EgB_9q.js → monitor-smartphone-M5nbcGRy.js} +1 -1
- package/dashboard/dist/assets/{play-BSuP-UBL.js → play-12ZzPH9r.js} +1 -1
- package/dashboard/dist/assets/{plus-BWemSIuC.js → plus-OqDA7xZy.js} +1 -1
- package/dashboard/dist/assets/{provider-display-BpzWuFDH.js → provider-display-JlEBv1VB.js} +1 -1
- package/dashboard/dist/assets/{react-cIJ4zx62.js → react-CeDcnQho.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-C_ZMtbgL.js → safePolygon-B_SGhQbo.js} +1 -1
- package/dashboard/dist/assets/{search-BK7eyNds.js → search-D9wz4rWp.js} +1 -1
- package/dashboard/dist/assets/{server-BqQHX-rd.js → server-UCMw4AQF.js} +1 -1
- package/dashboard/dist/assets/{shield-check-CO5U6gRw.js → shield-check-lJ0-Z5Ot.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CMye76ag.js → skills-api-Da-WZbvW.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-Bvb61Lvj.js → sortable.esm-Bj1ZkbqW.js} +1 -1
- package/dashboard/dist/assets/{store-CICV1KXb.js → store-Bpwwxxsy.js} +1 -1
- package/dashboard/dist/assets/{terminal-BKMWT1Ux.js → terminal-BHQMC4hm.js} +1 -1
- package/dashboard/dist/assets/{trash-D2Oqim4r.js → trash-yTV6vhGX.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-CEqwmjct.js → use-community-stats-rrE4Ohic.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-CTuzzu3F.js → use-latest-request-guard-D4Y1aCyd.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CA2BLWkY.js → use-limits-display-prefs-ChHr4XEF.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-B54wHSj9.js → use-native-settings-BmoCls3D.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list--PU9ekxY.js → use-ordered-list-BZSSYcLx.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-CKTIXp4-.js +1 -0
- package/dashboard/dist/assets/{use-transform-BRyR8szg.js → use-transform-CJO1b0Iu.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-sufKdfDY.js → use-usage-limits-BbAC_LWP.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-6oy7kSX8.js → useCurrency-B2enOMwJ.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-ZtDdVdJg.js → usePositioner-BaoW4QRL.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-tiOJ-PeE.js → useTokenFormat-B2bbeEu-.js} +1 -1
- package/dashboard/dist/assets/{zap-Dr1dXUjN.js → zap-CT5a8-_j.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/init.js +9 -0
- package/src/commands/status.js +12 -6
- package/src/commands/sync.js +22 -4
- package/src/lib/claude-backend.js +291 -0
- package/src/lib/claude-config.js +1 -1
- package/src/lib/cursor-config.js +34 -9
- package/src/lib/cursor-safe-storage.js +119 -0
- package/src/lib/devin-limits.js +65 -25
- package/src/lib/diagnostics.js +3 -0
- package/src/lib/doctor.js +13 -0
- package/src/lib/local-api.js +2 -0
- package/src/lib/local-usage-transfer.js +22 -1
- package/src/lib/pricing/index.js +12 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +70 -15
- package/src/lib/session-analytics.js +20 -8
- package/src/lib/trae-cn-config.js +32 -2
- package/src/lib/usage-limits.js +601 -35
- package/dashboard/dist/assets/DashboardPage-DJSgRHGq.js +0 -1
- package/dashboard/dist/assets/DialogDescription-B1HyKP0x.js +0 -1
- package/dashboard/dist/assets/SessionsPage-Cte1taUu.js +0 -3
- package/dashboard/dist/assets/use-reduced-motion-CZ8_YWc2.js +0 -1
package/src/lib/devin-limits.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const fs = require("node:fs");
|
|
18
18
|
const path = require("node:path");
|
|
19
|
+
const { parse: parseToml } = require("smol-toml");
|
|
19
20
|
|
|
20
21
|
const DEVIN_PLAN_STATUS_URL =
|
|
21
22
|
"https://server.codeium.com/exa.seat_management_pb.SeatManagementService/GetPlanStatus";
|
|
@@ -28,25 +29,26 @@ function isNonEmptyString(value) {
|
|
|
28
29
|
return typeof value === "string" && value.trim().length > 0;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (trimmed.startsWith("[")) break;
|
|
39
|
-
const match = /^([A-Za-z0-9_]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s#]+))\s*(?:#.*)?$/.exec(
|
|
40
|
-
trimmed,
|
|
41
|
-
);
|
|
42
|
-
if (!match) continue;
|
|
43
|
-
const [, key, doubleQuoted, singleQuoted, bare] = match;
|
|
44
|
-
fields[key] = doubleQuoted ?? singleQuoted ?? bare ?? "";
|
|
45
|
-
}
|
|
46
|
-
return fields;
|
|
32
|
+
// Local sign-in problems are persistent configuration errors, not transient
|
|
33
|
+
// fetch failures: the aggregator's disk-cache fallback must never mask them,
|
|
34
|
+
// so they carry a distinctive code the assembly can gate on.
|
|
35
|
+
function credentialsError(message) {
|
|
36
|
+
const error = new Error(message);
|
|
37
|
+
error.code = "CREDENTIALS_INVALID";
|
|
38
|
+
return error;
|
|
47
39
|
}
|
|
48
40
|
|
|
49
|
-
//
|
|
41
|
+
// A credential-file revision, not a token fingerprint or account claim. Both
|
|
42
|
+
// in-place edits (ctime/mtime) and atomic replacements (device/inode) invalidate
|
|
43
|
+
// old windows, including across restarts. Never persist the token or its hash.
|
|
44
|
+
function devinCredentialIdentity(filePath, stat) {
|
|
45
|
+
return JSON.stringify([
|
|
46
|
+
path.resolve(filePath),
|
|
47
|
+
...[stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].map(String),
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Returns { apiKey, cacheIdentity } or null when no sign-in exists (no home, or the file is
|
|
50
52
|
// absent — ENOENT only). Any other read failure or a mangled file throws an
|
|
51
53
|
// owned, actionable error with no path or filesystem details, so a broken
|
|
52
54
|
// sign-in can never masquerade as "not configured".
|
|
@@ -64,20 +66,42 @@ function readDevinCredentials({ home, env = process.env } = {}) {
|
|
|
64
66
|
? path.join(dir, "devin", "credentials.toml")
|
|
65
67
|
: path.join(dir, ".local", "share", "devin", "credentials.toml");
|
|
66
68
|
let raw;
|
|
69
|
+
let descriptor;
|
|
70
|
+
let cacheIdentity = null;
|
|
67
71
|
try {
|
|
68
|
-
|
|
72
|
+
// Read the token and revision from the same opened file, so an atomic
|
|
73
|
+
// replacement cannot stamp the old token with the new file's revision.
|
|
74
|
+
descriptor = fs.openSync(base, "r");
|
|
75
|
+
const before = fs.fstatSync(descriptor, { bigint: true });
|
|
76
|
+
if (!before.isFile()) throw new Error("Not a credentials file");
|
|
77
|
+
raw = fs.readFileSync(descriptor, "utf8");
|
|
78
|
+
const after = fs.fstatSync(descriptor, { bigint: true });
|
|
79
|
+
const identity = devinCredentialIdentity(base, before);
|
|
80
|
+
if (identity === devinCredentialIdentity(base, after)) cacheIdentity = identity;
|
|
69
81
|
} catch (error) {
|
|
70
82
|
if (error && error.code === "ENOENT") return null;
|
|
71
|
-
throw
|
|
83
|
+
throw credentialsError(
|
|
72
84
|
"Could not read the Devin CLI credentials file — check its permissions or run `devin auth login` to sign in again.",
|
|
73
85
|
);
|
|
86
|
+
} finally {
|
|
87
|
+
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
88
|
+
}
|
|
89
|
+
// Decode the credential document, then select only the root-level auth
|
|
90
|
+
// fields, like the other providers' structured credential readers.
|
|
91
|
+
let fields;
|
|
92
|
+
try {
|
|
93
|
+
fields = parseToml(raw);
|
|
94
|
+
} catch {
|
|
95
|
+
// Parser errors may quote credentials; expose only an owned message.
|
|
96
|
+
throw credentialsError(
|
|
97
|
+
"Could not parse the Devin CLI credentials file — run `devin auth login` to sign in again.",
|
|
98
|
+
);
|
|
74
99
|
}
|
|
75
|
-
const fields = parseDevinCredentialsToml(raw);
|
|
76
100
|
const apiKey = isNonEmptyString(fields.windsurf_api_key)
|
|
77
101
|
? fields.windsurf_api_key.trim()
|
|
78
102
|
: null;
|
|
79
103
|
if (!apiKey) {
|
|
80
|
-
throw
|
|
104
|
+
throw credentialsError(
|
|
81
105
|
"Devin credentials file has no sign-in token — run `devin auth login`.",
|
|
82
106
|
);
|
|
83
107
|
}
|
|
@@ -85,11 +109,20 @@ function readDevinCredentials({ home, env = process.env } = {}) {
|
|
|
85
109
|
? fields.api_server_url.trim().replace(/\/+$/, "")
|
|
86
110
|
: null;
|
|
87
111
|
if (apiServerUrl && apiServerUrl !== DEVIN_API_ORIGIN) {
|
|
88
|
-
throw
|
|
112
|
+
throw credentialsError(
|
|
89
113
|
"Devin credentials point at a custom api_server_url, which this quota provider does not support.",
|
|
90
114
|
);
|
|
91
115
|
}
|
|
92
|
-
return { apiKey };
|
|
116
|
+
return { apiKey, cacheIdentity };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resolveDevinCacheIdentity(options) {
|
|
120
|
+
try {
|
|
121
|
+
return readDevinCredentials(options)?.cacheIdentity || null;
|
|
122
|
+
} catch {
|
|
123
|
+
// Unreadable, missing or invalid current credentials must fail closed.
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
93
126
|
}
|
|
94
127
|
|
|
95
128
|
function malformed(field) {
|
|
@@ -226,9 +259,14 @@ async function fetchDevinLimits({ home, env = process.env, enabled = false, fetc
|
|
|
226
259
|
throw error;
|
|
227
260
|
}
|
|
228
261
|
if (!response?.ok) {
|
|
229
|
-
|
|
262
|
+
const error = new Error(
|
|
230
263
|
`Devin quota request was rejected (HTTP ${response?.status ?? "?"}).`,
|
|
231
264
|
);
|
|
265
|
+
// The aggregate's disk-cache fallback only covers transient failures:
|
|
266
|
+
// carrying the status lets it tell a 429/5xx blip from an ambiguous or
|
|
267
|
+
// persistent rejection like HTTP 400 (docs/devin-limits.md).
|
|
268
|
+
if (typeof response?.status === "number") error.statusCode = response.status;
|
|
269
|
+
throw error;
|
|
232
270
|
}
|
|
233
271
|
let body;
|
|
234
272
|
try {
|
|
@@ -243,7 +281,9 @@ async function fetchDevinLimits({ home, env = process.env, enabled = false, fetc
|
|
|
243
281
|
plan_label: windows.plan_label,
|
|
244
282
|
primary_window: windows.primary_window,
|
|
245
283
|
secondary_window: windows.secondary_window,
|
|
284
|
+
// Internal: the aggregate strips this before returning its payload.
|
|
285
|
+
cache_identity: credentials.cacheIdentity,
|
|
246
286
|
};
|
|
247
287
|
}
|
|
248
288
|
|
|
249
|
-
module.exports = { fetchDevinLimits };
|
|
289
|
+
module.exports = { fetchDevinLimits, resolveDevinCacheIdentity };
|
package/src/lib/diagnostics.js
CHANGED
|
@@ -20,6 +20,7 @@ const { probeOpenclawSessionPluginState } = require("./openclaw-session-plugin")
|
|
|
20
20
|
const { probeGrokHookState } = require("./grok-hook");
|
|
21
21
|
const { resolveTrackerPaths } = require("./tracker-paths");
|
|
22
22
|
const { resolveDeviceTokenPresence } = require("./cloud-device-token");
|
|
23
|
+
const { inspectTraeCnAuth } = require("./trae-cn-config");
|
|
23
24
|
const wsl = require("./wsl-probe");
|
|
24
25
|
// TASK-011: Kiro paths inlined here to avoid pulling the ~4000-line
|
|
25
26
|
// rollout module on every `tokentracker status` / `diagnostics` call.
|
|
@@ -195,6 +196,7 @@ async function collectTrackerDiagnostics({
|
|
|
195
196
|
? new Date(uploadThrottle.lastSuccessMs).toISOString()
|
|
196
197
|
: null;
|
|
197
198
|
const autoRetryAt = parseEpochMsToIso(autoRetry?.retryAtMs);
|
|
199
|
+
const traeCn = inspectTraeCnAuth({ home });
|
|
198
200
|
|
|
199
201
|
return {
|
|
200
202
|
ok: true,
|
|
@@ -233,6 +235,7 @@ async function collectTrackerDiagnostics({
|
|
|
233
235
|
merge_policy:
|
|
234
236
|
"Kiro IDE and Kiro CLI both emit source='kiro' in queue.jsonl so token, cost, heatmap, and leaderboard aggregations merge transparently. Use this block to distinguish sub-path contributions.",
|
|
235
237
|
},
|
|
238
|
+
trae_cn: { ...traeCn, storage_path: redactValue(traeCn.storage_path, home) },
|
|
236
239
|
scan_roots: {
|
|
237
240
|
codex: describeScanRoots(scanRoots.codex),
|
|
238
241
|
claude: describeScanRoots(scanRoots.claude),
|
package/src/lib/doctor.js
CHANGED
|
@@ -293,6 +293,19 @@ async function checkNetwork({ baseUrl, fetch }) {
|
|
|
293
293
|
|
|
294
294
|
function buildDiagnosticsChecks(diagnostics) {
|
|
295
295
|
const checks = [];
|
|
296
|
+
const traeCn = diagnostics?.trae_cn;
|
|
297
|
+
if (traeCn) {
|
|
298
|
+
const { TRAE_CN_AUTH_HELP } = require("./trae-cn-config");
|
|
299
|
+
checks.push({
|
|
300
|
+
id: "trae_cn.auth",
|
|
301
|
+
status: !traeCn.usage_opt_in || traeCn.auth === "readable" ? "ok" : "warn",
|
|
302
|
+
detail: !traeCn.usage_opt_in
|
|
303
|
+
? "usage sync off (TOKENTRACKER_TRAE_CN_USAGE=1 opts in); auth not read; no TRAE request made"
|
|
304
|
+
: `local auth ${traeCn.auth}; API access not checked (${traeCn.storage_path || "storage.json not found"}). ${TRAE_CN_AUTH_HELP}`,
|
|
305
|
+
critical: false,
|
|
306
|
+
meta: traeCn,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
296
309
|
const notify = diagnostics?.notify || {};
|
|
297
310
|
const notifyConfigured = Boolean(
|
|
298
311
|
notify.codex_notify_configured ||
|
package/src/lib/local-api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { hasUnknownBackendCost } = require("./claude-backend");
|
|
1
2
|
const fs = require("node:fs");
|
|
2
3
|
const os = require("node:os");
|
|
3
4
|
const path = require("node:path");
|
|
@@ -2942,6 +2943,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
2942
2943
|
if (!sa.models.has(mdl))
|
|
2943
2944
|
sa.models.set(mdl, { model: mdl, model_id: mdl, pricing: getModelPricingInfo(mdl, { source: src }), totals: { total_tokens: 0, billable_total_tokens: 0, input_tokens: 0, output_tokens: 0, cached_input_tokens: 0, cache_creation_input_tokens: 0, reasoning_output_tokens: 0, conversation_count: 0, total_cost_usd: "0" } });
|
|
2944
2945
|
const ma = sa.models.get(mdl);
|
|
2946
|
+
if (hasUnknownBackendCost(row)) ma.pricing = getModelPricingInfo(mdl, row);
|
|
2945
2947
|
const reportedCost = ["grok", "cline"].includes(src) && Number(row.total_cost_usd) > 0;
|
|
2946
2948
|
const costSource = reportedCost ? "provider_reported" : "model_pricing";
|
|
2947
2949
|
ma.cost_source = ma.cost_source && ma.cost_source !== costSource ? "mixed" : costSource;
|
|
@@ -7,6 +7,8 @@ const crypto = require("node:crypto");
|
|
|
7
7
|
const { openLock, writeFileAtomic } = require("./fs");
|
|
8
8
|
const { isAccountLevelSource } = require("./source-metadata");
|
|
9
9
|
|
|
10
|
+
const { backendFromUrl, TOKEN_FIELDS: BACKEND_TOKEN_FIELDS } = require("./claude-backend");
|
|
11
|
+
|
|
10
12
|
const SCHEMA = "tokentracker.local-usage.v1";
|
|
11
13
|
const MAX_BYTES = 5 * 1024 * 1024;
|
|
12
14
|
const MAX_STATE_BYTES = 64 * 1024 * 1024;
|
|
@@ -15,7 +17,7 @@ const TOKEN_FIELDS = ["input_tokens", "output_tokens", "cached_input_tokens", "c
|
|
|
15
17
|
"reasoning_output_tokens", "total_tokens", "billable_total_tokens", "conversation_count",
|
|
16
18
|
...["long_context_", "priority_", "priority_long_context_", "ultrafast_", "ultrafast_long_context_"].flatMap((prefix) =>
|
|
17
19
|
["input_tokens", "output_tokens", "cached_input_tokens", "cache_creation_input_tokens", "reasoning_output_tokens"].map((field) => prefix + field))];
|
|
18
|
-
const ROW_FIELDS = new Set(["source", "model", "hour_start", "total_cost_usd", ...TOKEN_FIELDS]);
|
|
20
|
+
const ROW_FIELDS = new Set(["source", "model", "hour_start", "total_cost_usd", "backend_usage", ...TOKEN_FIELDS]);
|
|
19
21
|
const validId = (id) => typeof id === "string" && /^[A-Za-z0-9_-]{8,128}$/.test(id);
|
|
20
22
|
const validTime = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
21
23
|
const digest = (value) => crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
@@ -47,6 +49,25 @@ function cleanRow(row, strict = false) {
|
|
|
47
49
|
if (!Number.isFinite(row.total_cost_usd) || row.total_cost_usd < 0) fail("invalid_snapshot");
|
|
48
50
|
result.total_cost_usd = row.total_cost_usd;
|
|
49
51
|
}
|
|
52
|
+
if (row.backend_usage !== undefined) {
|
|
53
|
+
if (row.source !== "claude" || !Array.isArray(row.backend_usage)) fail("invalid_snapshot");
|
|
54
|
+
const sums = {};
|
|
55
|
+
result.backend_usage = row.backend_usage.map((part) => {
|
|
56
|
+
if (!part || typeof part !== "object") fail("invalid_snapshot");
|
|
57
|
+
const identity = backendFromUrl(part.backend_origin);
|
|
58
|
+
if (part.backend !== identity.backend || part.backend_origin !== identity.backend_origin ||
|
|
59
|
+
Object.keys(part).some((key) => !["backend", "backend_origin", ...BACKEND_TOKEN_FIELDS].includes(key))) fail("invalid_snapshot");
|
|
60
|
+
const clean = { ...identity };
|
|
61
|
+
for (const field of BACKEND_TOKEN_FIELDS) {
|
|
62
|
+
const value = part[field] ?? 0;
|
|
63
|
+
if (!Number.isSafeInteger(value) || value < 0) fail("invalid_snapshot");
|
|
64
|
+
sums[field] = (sums[field] || 0) + value;
|
|
65
|
+
if (sums[field] > (result[field] || 0)) fail("invalid_snapshot");
|
|
66
|
+
clean[field] = value;
|
|
67
|
+
}
|
|
68
|
+
return clean;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
50
71
|
return result;
|
|
51
72
|
}
|
|
52
73
|
|
package/src/lib/pricing/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { hasUnknownBackendCost, TOKEN_FIELDS } = require("../claude-backend");
|
|
1
2
|
// Public pricing API. Replaces the hard-coded MODEL_PRICING table that used
|
|
2
3
|
// to live in src/lib/local-api.js. Keeps the same synchronous shape so all
|
|
3
4
|
// existing callers (computeRowCost, /functions/* handlers, tests) work
|
|
@@ -139,6 +140,7 @@ const pricingInfoCache = { revision: -1, entries: new Map() };
|
|
|
139
140
|
// Preserve matcher provenance: a known zero rate is free, a missing model is
|
|
140
141
|
// unpriced. Both return zero dollars, but they mean different things in the UI.
|
|
141
142
|
function getModelPricingInfo(model, opts = {}) {
|
|
143
|
+
if (hasUnknownBackendCost(opts)) return { status: "unpriced", source: null, ...ZERO_PRICING };
|
|
142
144
|
const source = typeof opts === "string" ? opts : opts.source;
|
|
143
145
|
const sourceKey = typeof source === "string" ? source.toLowerCase() : null;
|
|
144
146
|
if (pricingInfoCache.revision !== state.revision) {
|
|
@@ -340,6 +342,16 @@ function speedTierSubsetCost(row, prefix, pricing, ctx) {
|
|
|
340
342
|
// computeRowCost in src/lib/local-api.js. Moved here so vite mock + local
|
|
341
343
|
// server share one source of truth.
|
|
342
344
|
function computeRowCost(row) {
|
|
345
|
+
if (hasUnknownBackendCost(row)) {
|
|
346
|
+
// Existing totals without provenance retain their historical estimate. Only
|
|
347
|
+
// the newly ingested custom/unknown subset is excluded from model pricing.
|
|
348
|
+
const priced = { ...row, backend_usage: undefined };
|
|
349
|
+
for (const part of row.backend_usage) {
|
|
350
|
+
if (part.backend === "anthropic") continue;
|
|
351
|
+
for (const field of TOKEN_FIELDS) priced[field] = Math.max(0, (priced[field] || 0) - (part[field] || 0));
|
|
352
|
+
}
|
|
353
|
+
return computeRowCost(priced);
|
|
354
|
+
}
|
|
343
355
|
if (LOCAL_INFERENCE_SOURCES.has(String(row?.source || "").toLowerCase())) return 0;
|
|
344
356
|
// Pi can route a turn through a subscription-backed Copilot provider. Pi's
|
|
345
357
|
// usage record reports a zero marginal cost for those turns; do not
|