tokentracker-cli 0.91.0 → 0.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +7 -5
- package/README.ja.md +9 -5
- package/README.ko.md +9 -5
- package/README.md +7 -5
- package/README.zh-CN.md +6 -4
- package/dashboard/dist/assets/{AchievementBadge-iyRg0rYF.js → AchievementBadge-o8hl3my2.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-CkBm3bN3.js → AchievementsPage-CjDEyZfD.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-a4Z7VtlP.js → AchievementsSection-Cwv1Zmyf.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-DHTN0Ky6.js → BadgeDetailModal-BDxTgOvT.js} +1 -1
- package/dashboard/dist/assets/{Card-CdygGEqz.js → Card-Cp5v7Y19.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-D-Z823Nc.js → ClawdAnimated-6Ibb4qCZ.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-VGkj8w2X.js → CommandPalette-CIf9oAqq.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BDT4xqfF.js → CommunityStatsModal-UwiOPFu0.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-BIKv4AlL.js → ConfirmModal-xkYsrh0v.js} +1 -1
- package/dashboard/dist/assets/{Controls-BkZVm9t1.js → Controls-C2Z2X0ZQ.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-BFkdjUdd.js → DashboardPage-Do_NMUFK.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-BGw861bu.js → DevicePage-DL5OjugJ.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-CSEl6ru8.js → DialogClose-D0w08oR2.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-rhGVm7N2.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-CWPdu5V-.js → DialogTitle-IKAPm5Pk.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CdCANtvs.js → FadeIn-Dve77aEj.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-dQQnH85f.js → HeaderGithubStar-BVYbLWDi.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-CqJUI-WG.js → HoverTooltip-BrF9-z7f.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BRqTXlSu.js → IpCheckPage-BZ1VyVsm.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-DhMTgDng.js → LandingPage-jkjtQl58.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-BrGcByG1.js → LeaderboardAvatar-D9kUudjQ.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BmdopPjo.js → LeaderboardPage-DXg7Qv8T.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-DRZqPo-A.js → LeaderboardProfileModal-jTlaOi-j.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-CTbC39KX.js → LeaderboardProfilePage-DsKQ_Of8.js} +3 -3
- package/dashboard/dist/assets/{LimitsPage-B7AuLq1S.js → LimitsPage-CMqZAQCa.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-DyIAspoq.js → LocalOnlyNotice-CkLs3CF_.js} +1 -1
- package/dashboard/dist/assets/LoginCard-SmOXM_PX.js +1 -0
- package/dashboard/dist/assets/{LoginModal-D3bmr_jI.js → LoginModal-CvtEBkrO.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-D7SgmEhZ.js → LoginPage-Zm-dRt_Y.js} +1 -1
- package/dashboard/dist/assets/{PetPage-5A3Q0zlw.js → PetPage-Bcj7hzNz.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-ajEhdI8m.js → PopoverPopup-CnteSROW.js} +1 -1
- package/dashboard/dist/assets/ProviderIcon-DcQmGbfz.js +1 -0
- package/dashboard/dist/assets/{ResetPasswordPage-Bwbt8tH2.js → ResetPasswordPage-zMb_m3Cx.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-BSa5hMnN.js → SegmentedControl-D3KkbQ7T.js} +1 -1
- package/dashboard/dist/assets/{Select-kPKDGb7M.js → Select-0RiFEISo.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DNgLclTV.js → SelectItemText-BRV3DFEB.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-C1j7rPDp.js → ServiceStatusPage-CWF_vKOC.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-BgoKs_Yq.js → SessionsPage-DIqoT7MU.js} +2 -2
- package/dashboard/dist/assets/{SettingsPage-BUaPftV0.js → SettingsPage-DQZIqiE5.js} +2 -2
- package/dashboard/dist/assets/{SkillsPage-C5GkzfWW.js → SkillsPage-BIIOUHnq.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-DZffAa_0.js → TokenGalaxy-Bulaqjbm.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-CuLP92P_.js → ToolbarRootContext-Dg-zL8fE.js} +1 -1
- package/dashboard/dist/assets/TrendMonitor-DfafDX6b.js +19 -0
- package/dashboard/dist/assets/{WidgetsPage-BUhiCpcq.js → WidgetsPage-F_2yLE7n.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-sNFISXVG.js → WrappedPage-BFxYKGQs.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-Cyw7ZNtj.js → __vite-browser-external-CZMdPW0g.js} +4 -3
- package/dashboard/dist/assets/agent-logos-DjyCpabA.js +1 -0
- package/dashboard/dist/assets/{arrow-up-right-BcRkt7hr.js → arrow-up-right-CIAd6sXA.js} +1 -1
- package/dashboard/dist/assets/{check-qhr4U2S5.js → check-Bt3Yu07t.js} +1 -1
- package/dashboard/dist/assets/{chevron-right-iLGEAP7_.js → chevron-right-BiXULxma.js} +1 -1
- package/dashboard/dist/assets/{copy-BhEPCq-q.js → copy-D3k1Xbfl.js} +1 -1
- package/dashboard/dist/assets/{download-D7O1ZzsX.js → download-co2WHJvo.js} +1 -1
- package/dashboard/dist/assets/{external-link-CY2kK2hC.js → external-link-D5az2QwL.js} +1 -1
- package/dashboard/dist/assets/{flame-D4FPuBNn.js → flame-CSCUyn3Z.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-CVHJ7Hdh.js → format-tokens-BebRLYzr.js} +1 -1
- package/dashboard/dist/assets/{icons-B_-51UXa.js → icons-C_y5qr6T.js} +1 -1
- package/dashboard/dist/assets/{index-ClVWat-1.js → index-CXOOOeSl.js} +1 -1
- package/dashboard/dist/assets/{index-cwdr25Zm.js → index-DP3XlgDm.js} +1 -1
- package/dashboard/dist/assets/{index-Djv-aJy0.js → index-R488h3mD.js} +1 -1
- package/dashboard/dist/assets/{info-DOab0eZz.js → info-CD_G_DZp.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-CESm2d6B.js → limits-providers-BlAlATd7.js} +1 -1
- package/dashboard/dist/assets/{link-2-B4LMsC4m.js → link-2-DIn-EC5g.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CPx6wpSn.js → loader-circle-B5Az_VQe.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-C9z8mbdn.js → maximize-2-ptnkCbEN.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-5xRgAl_R.js → modifiers.esm-DEN3VPKe.js} +1 -1
- package/dashboard/dist/assets/module-CV1mYFsv.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-B9HAspdR.js → monitor-smartphone-Dqr_CnOQ.js} +1 -1
- package/dashboard/dist/assets/provider-display-BOAoggwg.js +42 -0
- package/dashboard/dist/assets/{react-C45OGvDd.js → react-B3eL33ha.js} +1 -1
- package/dashboard/dist/assets/{search-Bg1qyEdz.js → search-CNNSHW9P.js} +1 -1
- package/dashboard/dist/assets/{skills-api-a8pi7UTd.js → skills-api-BShLlV2m.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-CtPp8cFA.js → sortable.esm-C6kDS9DZ.js} +1 -1
- package/dashboard/dist/assets/{store-b8zXM1yr.js → store-B4atnDzu.js} +1 -1
- package/dashboard/dist/assets/{terminal-CPf0K0Fn.js → terminal-CXMttuiJ.js} +1 -1
- package/dashboard/dist/assets/{trash-2-uorzBdA0.js → trash-2-aXKBjf5M.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-DaVwywsM.js → use-community-stats-rGR2J-Vi.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-UIzz7PJ3.js → use-limits-display-prefs-CazeqdNI.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-C2XOfMH3.js → use-ordered-list-Bx-enbl8.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-2NcjtyzY.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-D5KHakYg.js → use-session-efficiency-pref-B7RHUtup.js} +1 -1
- package/dashboard/dist/assets/{use-transform-Cfo_2_id.js → use-transform-CWVOY76a.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CANTpxrA.js → use-usage-limits-C9XRiYNi.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CjvWekKH.js → useCurrency-D7ICju5j.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-ChQdbuJg.js → useScrollLock-DXWPQF_D.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-4MY04JPl.js → useTokenFormat-wDhHrsAL.js} +1 -1
- package/dashboard/dist/assets/{zap-BSXHFUBb.js → zap-BZDqgZR1.js} +1 -1
- package/dashboard/dist/brand-logos/dots.png +0 -0
- package/dashboard/dist/brand-logos/pi.svg +8 -15
- package/dashboard/dist/index.html +31 -26
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/llms.txt +4 -2
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/init.js +3 -1
- package/src/commands/status.js +46 -0
- package/src/commands/sync.js +188 -4
- package/src/lib/account-usage-dedup.js +267 -0
- package/src/lib/local-api.js +4 -0
- package/src/lib/pricing/curated-overrides.json +15 -9
- package/src/lib/pricing/index.js +32 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +730 -1
- package/src/lib/source-metadata.js +1 -1
- package/src/lib/trae-cn-config.js +539 -0
- package/dashboard/dist/assets/DialogDescription-DfNXnqay.js +0 -1
- package/dashboard/dist/assets/LoginCard-LcOaKaCK.js +0 -1
- package/dashboard/dist/assets/ProviderIcon-C4yuu9FG.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-BKLAKJmT.js +0 -19
- package/dashboard/dist/assets/agent-logos-Bwf2YFHS.js +0 -1
- package/dashboard/dist/assets/module-BRJxSJmz.js +0 -3
- package/dashboard/dist/assets/provider-display-C0NS0EvN.js +0 -42
- package/dashboard/dist/assets/use-reduced-motion-CNyMWBog.js +0 -1
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
]
|
|
166
166
|
}
|
|
167
167
|
</script>
|
|
168
|
-
<script type="module" crossorigin src="/assets/__vite-browser-external-
|
|
168
|
+
<script type="module" crossorigin src="/assets/__vite-browser-external-CZMdPW0g.js"></script>
|
|
169
169
|
<link rel="stylesheet" crossorigin href="/assets/__vite-browser-external-CRYifSM4.css">
|
|
170
170
|
</head>
|
|
171
171
|
<body>
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
]
|
|
137
137
|
}
|
|
138
138
|
</script>
|
|
139
|
-
<script type="module" crossorigin src="/assets/__vite-browser-external-
|
|
139
|
+
<script type="module" crossorigin src="/assets/__vite-browser-external-CZMdPW0g.js"></script>
|
|
140
140
|
<link rel="stylesheet" crossorigin href="/assets/__vite-browser-external-CRYifSM4.css">
|
|
141
141
|
</head>
|
|
142
142
|
<body>
|
package/dashboard/dist/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Token Tracker LLMs Index
|
|
2
2
|
|
|
3
3
|
Token Tracker is a free, open-source, local-first AI coding usage and cost
|
|
4
|
-
tracker for
|
|
4
|
+
tracker for 34 tools. Its distinctive desktop experience includes a pixel pet
|
|
5
5
|
powered by real token activity, four desktop widgets, and achievements. The
|
|
6
6
|
same data powers a cross-platform CLI, a native macOS menu bar app, a native
|
|
7
7
|
Windows system tray app, and a dashboard. It records token counts and
|
|
@@ -23,7 +23,7 @@ timestamps, never prompts, responses, or source code.
|
|
|
23
23
|
- [npm package](https://www.npmjs.com/package/tokentracker-cli)
|
|
24
24
|
- [Latest release](https://github.com/xiufengsun/TokenTracker/releases/latest)
|
|
25
25
|
|
|
26
|
-
## Supported AI coding tools (
|
|
26
|
+
## Supported AI coding tools (34)
|
|
27
27
|
- Claude Code
|
|
28
28
|
- Codex CLI
|
|
29
29
|
- Cursor
|
|
@@ -41,6 +41,7 @@ timestamps, never prompts, responses, or source code.
|
|
|
41
41
|
- Grok Build
|
|
42
42
|
- oh-my-pi
|
|
43
43
|
- pi
|
|
44
|
+
- Dots
|
|
44
45
|
- Prime Agent
|
|
45
46
|
- Craft Agents
|
|
46
47
|
- Reasonix
|
|
@@ -56,6 +57,7 @@ timestamps, never prompts, responses, or source code.
|
|
|
56
57
|
- AnythingLLM Desktop
|
|
57
58
|
- Claude Science
|
|
58
59
|
- DeepSeek Harness
|
|
60
|
+
- TRAE Work CN
|
|
59
61
|
|
|
60
62
|
## Capabilities
|
|
61
63
|
- Desktop pet that reacts to real token burn, streaks, and rest
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
76
76
|
}
|
|
77
77
|
</script>
|
|
78
|
-
<script type="module" crossorigin src="/assets/__vite-browser-external-
|
|
78
|
+
<script type="module" crossorigin src="/assets/__vite-browser-external-CZMdPW0g.js"></script>
|
|
79
79
|
<link rel="stylesheet" crossorigin href="/assets/__vite-browser-external-CRYifSM4.css">
|
|
80
80
|
</head>
|
|
81
81
|
<body>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Local-first AI coding token usage and cost tracker for
|
|
3
|
+
"version": "0.92.0",
|
|
4
|
+
"description": "Local-first AI coding token usage and cost tracker for 34 tools, with a dashboard, desktop pet, widgets, achievements, and native macOS/Windows apps.",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tokentracker-cli": "bin/tracker.js",
|
package/src/commands/init.js
CHANGED
|
@@ -103,7 +103,7 @@ const DIVIDER = "----------------------------------------------";
|
|
|
103
103
|
const DEFAULT_DASHBOARD_URL = "https://www.tokentracker.cc";
|
|
104
104
|
|
|
105
105
|
// Single source of truth for the welcome screen's provider count + sample list.
|
|
106
|
-
// test/discovery-metadata.test.js keeps this aligned with public
|
|
106
|
+
// test/discovery-metadata.test.js keeps this aligned with public 32-tool copy.
|
|
107
107
|
const SUPPORTED_PROVIDERS = [
|
|
108
108
|
"Claude Code",
|
|
109
109
|
"Codex CLI",
|
|
@@ -122,6 +122,7 @@ const SUPPORTED_PROVIDERS = [
|
|
|
122
122
|
"Grok Build",
|
|
123
123
|
"oh-my-pi",
|
|
124
124
|
"pi",
|
|
125
|
+
"Dots",
|
|
125
126
|
"Prime Agent",
|
|
126
127
|
"Craft Agents",
|
|
127
128
|
"Reasonix",
|
|
@@ -137,6 +138,7 @@ const SUPPORTED_PROVIDERS = [
|
|
|
137
138
|
"AnythingLLM Desktop",
|
|
138
139
|
"Claude Science",
|
|
139
140
|
"DeepSeek Harness",
|
|
141
|
+
"TRAE Work CN",
|
|
140
142
|
];
|
|
141
143
|
|
|
142
144
|
async function cmdInit(argv) {
|
package/src/commands/status.js
CHANGED
|
@@ -89,6 +89,13 @@ const {
|
|
|
89
89
|
resolveCopilotAppDbPaths,
|
|
90
90
|
probeWslDistros,
|
|
91
91
|
} = require("../lib/rollout");
|
|
92
|
+
const {
|
|
93
|
+
TRAE_CN_USAGE_ENV,
|
|
94
|
+
isTraeCnUsageEnabled,
|
|
95
|
+
resolveTraeCnStoragePath,
|
|
96
|
+
readTraeCnAuthFromStorage,
|
|
97
|
+
extractTraeCnToken,
|
|
98
|
+
} = require("../lib/trae-cn-config");
|
|
92
99
|
const wsl = require("../lib/wsl-probe");
|
|
93
100
|
const { getWslMode, isInvalidWslMode, shouldProbeWsl, discoverWslHome } = wsl;
|
|
94
101
|
const { resolveInstallPaths, resolveZcodeNativeDbPath } = require("../lib/install-resolver");
|
|
@@ -642,6 +649,34 @@ async function cmdStatus(argv = []) {
|
|
|
642
649
|
? readTraeEntitlementFromStorage(traeStoragePath)
|
|
643
650
|
: null;
|
|
644
651
|
|
|
652
|
+
// Trae SOLO CN — opt-in usage-API reader. Unlike the passive entitlement
|
|
653
|
+
// reader above, this source sends the locally stored sign-in JWT to TRAE's
|
|
654
|
+
// official API, so status surfaces the opt-in flag, the resolved storage
|
|
655
|
+
// path, and whether the auth blob decrypts — the three inputs a "why is my
|
|
656
|
+
// TRAE CN data missing" diagnosis needs.
|
|
657
|
+
// A resolvable default path does NOT mean the app is installed - the macOS
|
|
658
|
+
// resolver always derives one. Match the sync path semantics exactly:
|
|
659
|
+
// installed iff the storage file actually exists on disk.
|
|
660
|
+
const traeCnStoragePath = resolveTraeCnStoragePath({ env: process.env, home });
|
|
661
|
+
const traeCnInstalled = Boolean(
|
|
662
|
+
traeCnStoragePath && fssync.existsSync(traeCnStoragePath),
|
|
663
|
+
);
|
|
664
|
+
let traeCnAuthState = "not-signed-in";
|
|
665
|
+
if (traeCnInstalled) {
|
|
666
|
+
try {
|
|
667
|
+
const traeCnAuth = readTraeCnAuthFromStorage({ env: process.env, home, platform: process.platform });
|
|
668
|
+
if (traeCnAuth) {
|
|
669
|
+
extractTraeCnToken(traeCnAuth);
|
|
670
|
+
traeCnAuthState = "readable";
|
|
671
|
+
}
|
|
672
|
+
} catch (error) {
|
|
673
|
+
// Distinguish a real IO failure (unreadable) from present-but-bad
|
|
674
|
+
// data (malformed); neither ever carries storage contents.
|
|
675
|
+
traeCnAuthState = error?.code === "TRAE_CN_STORAGE_UNREADABLE" ? "unreadable" : "malformed";
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const traeCnUsageOptIn = isTraeCnUsageEnabled(process.env);
|
|
679
|
+
|
|
645
680
|
// Grok Build (xAI TUI)
|
|
646
681
|
const grokHookState = await probeGrokHookState({ home, trackerDir, env: process.env });
|
|
647
682
|
const grokSessions = grokHookState.hasGrokInstall || grokHookState.sessionsDir
|
|
@@ -933,6 +968,14 @@ async function cmdStatus(argv = []) {
|
|
|
933
968
|
...(traeEntitlement ? { entitlement: traeEntitlement } : {}),
|
|
934
969
|
}
|
|
935
970
|
: { installed: false },
|
|
971
|
+
"trae-cn": traeCnInstalled
|
|
972
|
+
? {
|
|
973
|
+
installed: true,
|
|
974
|
+
detail: traeCnStoragePath,
|
|
975
|
+
auth: traeCnAuthState,
|
|
976
|
+
usage_opt_in: traeCnUsageOptIn,
|
|
977
|
+
}
|
|
978
|
+
: { installed: false },
|
|
936
979
|
grok_build: grokInstalled
|
|
937
980
|
? {
|
|
938
981
|
installed: true,
|
|
@@ -1105,6 +1148,9 @@ async function cmdStatus(argv = []) {
|
|
|
1105
1148
|
traeEntitlement
|
|
1106
1149
|
? `- Trae SOLO plan: ${formatTraeEntitlementLine(traeEntitlement)}`
|
|
1107
1150
|
: null,
|
|
1151
|
+
traeCnInstalled
|
|
1152
|
+
? `- Trae SOLO CN: usage sync ${traeCnUsageOptIn ? "opted in" : `off (set ${TRAE_CN_USAGE_ENV}=1 to enable)`}, auth ${traeCnAuthState} (${traeCnStoragePath})`
|
|
1153
|
+
: null,
|
|
1108
1154
|
...(() => {
|
|
1109
1155
|
if (!hermesInstalled) return [];
|
|
1110
1156
|
return [`- Hermes Agent: state.db found (${hermesPath})`];
|
package/src/commands/sync.js
CHANGED
|
@@ -112,6 +112,7 @@ const {
|
|
|
112
112
|
resolveDroidModel,
|
|
113
113
|
resolveDshSessionFiles,
|
|
114
114
|
parseDshIncremental,
|
|
115
|
+
parseTraeCnApiIncremental,
|
|
115
116
|
bucketKey,
|
|
116
117
|
toUtcHalfHourStart,
|
|
117
118
|
totalsKey,
|
|
@@ -134,6 +135,11 @@ const {
|
|
|
134
135
|
parseCursorCsv,
|
|
135
136
|
} = require("../lib/cursor-config");
|
|
136
137
|
const { purgeProjectUsage } = require("../lib/project-usage-purge");
|
|
138
|
+
const {
|
|
139
|
+
fetchTraeCnUsageWithAuth,
|
|
140
|
+
resolveTraeCnStoragePath,
|
|
141
|
+
isTraeCnUsageEnabled,
|
|
142
|
+
} = require("../lib/trae-cn-config");
|
|
137
143
|
const {
|
|
138
144
|
isCodexSessionCursorPath,
|
|
139
145
|
isCursorStoreRetry,
|
|
@@ -291,6 +297,7 @@ const AUTO_SYNC_SOURCES = new Set([
|
|
|
291
297
|
"qoder-cn",
|
|
292
298
|
"reasonix",
|
|
293
299
|
"roocode",
|
|
300
|
+
"trae-cn",
|
|
294
301
|
"workbuddy",
|
|
295
302
|
"zcode",
|
|
296
303
|
"zed",
|
|
@@ -452,6 +459,15 @@ async function cmdSync(argv, context = {}) {
|
|
|
452
459
|
const lockWaitOptions = context && typeof context === "object"
|
|
453
460
|
? context.lockWaitOptions
|
|
454
461
|
: undefined;
|
|
462
|
+
// Narrow test-only seam for TRAE Work CN: deterministic injected fetch + a
|
|
463
|
+
// fixed "now" for the rolling range. Production keeps the global fetch and
|
|
464
|
+
// current time; no public CLI flag/config is introduced.
|
|
465
|
+
const traeCnFetchImpl = context && typeof context === "object"
|
|
466
|
+
? context.traeCnFetchImpl
|
|
467
|
+
: undefined;
|
|
468
|
+
const traeCnNowMs = context && typeof context === "object"
|
|
469
|
+
? context.traeCnNowMs
|
|
470
|
+
: undefined;
|
|
455
471
|
const syncDiagnostics = diagnostics && typeof diagnostics === "object" ? diagnostics : null;
|
|
456
472
|
const home = os.homedir();
|
|
457
473
|
const { trackerDir } = await resolveTrackerPaths({ home });
|
|
@@ -1663,6 +1679,93 @@ async function cmdSync(argv, context = {}) {
|
|
|
1663
1679
|
}
|
|
1664
1680
|
}
|
|
1665
1681
|
|
|
1682
|
+
// ── Trae Work CN (国内版) — account-level usage API ──
|
|
1683
|
+
// A simple explicit rolling 30-day window captured once per sync (no
|
|
1684
|
+
// cursor checkpoints / full-history import / page retries / background
|
|
1685
|
+
// requests). Runs only when the user has opted in via
|
|
1686
|
+
// TOKENTRACKER_TRAE_CN_USAGE=1 (the read transmits the locally stored
|
|
1687
|
+
// sign-in JWT to TRAE's official endpoint, so it is never default-on),
|
|
1688
|
+
// the sync is non-lightweight, the source is allowed, and the CN storage
|
|
1689
|
+
// file exists — which excludes both ordinary background and `--auto
|
|
1690
|
+
// --background --all-local-sources`. Fetching goes through the
|
|
1691
|
+
// storage-backed helper so its single 401/403 reread/retry is used; an
|
|
1692
|
+
// empty response is a successful no-op; each window is bounded to 100
|
|
1693
|
+
// pages/2,000 rows and an over-capacity window is split into staggered
|
|
1694
|
+
// sub-windows (still over capacity at the finest allowed granularity, or
|
|
1695
|
+
// any other fetch/page/schema/auth failure) skips the parser and leaves
|
|
1696
|
+
// prior data untouched while unrelated providers continue.
|
|
1697
|
+
let traeCnResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1698
|
+
if (
|
|
1699
|
+
!isBackgroundLightweightSync &&
|
|
1700
|
+
isTraeCnUsageEnabled(process.env) &&
|
|
1701
|
+
sourceAllowed("trae-cn")
|
|
1702
|
+
) {
|
|
1703
|
+
const nowMs = Number.isFinite(traeCnNowMs) ? traeCnNowMs : Date.now();
|
|
1704
|
+
const fetchImpl = typeof traeCnFetchImpl === "function" ? traeCnFetchImpl : fetch;
|
|
1705
|
+
const endTime = Math.floor(nowMs / 1000);
|
|
1706
|
+
// Align the REAL fetch start down to a half-hour boundary so the API
|
|
1707
|
+
// range fully contains every bucket it can touch (a raw 08:37 start
|
|
1708
|
+
// would only partially cover the 08:30 bucket) - session bucket floors
|
|
1709
|
+
// never straddle the queried range.
|
|
1710
|
+
const HALF_HOUR_SEC = 30 * 60;
|
|
1711
|
+
const startTime = Math.max(
|
|
1712
|
+
1,
|
|
1713
|
+
Math.floor((endTime - 30 * 24 * 60 * 60) / HALF_HOUR_SEC) * HALF_HOUR_SEC,
|
|
1714
|
+
);
|
|
1715
|
+
try {
|
|
1716
|
+
// Absent storage means "not signed in" — a silent skip, not an error.
|
|
1717
|
+
const traeCnStoragePath = resolveTraeCnStoragePath({ env: process.env, home });
|
|
1718
|
+
if (traeCnStoragePath && fssync.existsSync(traeCnStoragePath)) {
|
|
1719
|
+
if (progress?.enabled) {
|
|
1720
|
+
progress.start(`Fetching TRAE Work CN usage...`);
|
|
1721
|
+
}
|
|
1722
|
+
const traeCnUsage = await fetchTraeCnUsageWithAuth({
|
|
1723
|
+
start_time: startTime,
|
|
1724
|
+
end_time: endTime,
|
|
1725
|
+
fetchImpl,
|
|
1726
|
+
env: process.env,
|
|
1727
|
+
home,
|
|
1728
|
+
});
|
|
1729
|
+
const traeCnSessions = Array.isArray(traeCnUsage?.sessions) ? traeCnUsage.sessions : [];
|
|
1730
|
+
// An empty payload parses as a pure no-op: the TRAE absence
|
|
1731
|
+
// contract is NOT PROVEN (no evidence that a missing session means
|
|
1732
|
+
// deleted/zero), so an empty response asserts nothing - no usage
|
|
1733
|
+
// mutation and no session states. Non-empty snapshots append one
|
|
1734
|
+
// canonical session-state observation per CHANGED session (the
|
|
1735
|
+
// cloud LWW upsert reconciles cross-device versions).
|
|
1736
|
+
{
|
|
1737
|
+
if (progress?.enabled && traeCnSessions.length > 0) {
|
|
1738
|
+
progress.start(
|
|
1739
|
+
`Parsing TRAE Work CN ${renderBar(0)} 0/${formatNumber(
|
|
1740
|
+
traeCnSessions.length,
|
|
1741
|
+
)} records | buckets 0`,
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
traeCnResult = await parseTraeCnApiIncremental({
|
|
1745
|
+
sessions: traeCnSessions,
|
|
1746
|
+
cursors,
|
|
1747
|
+
queuePath,
|
|
1748
|
+
onProgress: makeProviderProgress("TRAE Work CN"),
|
|
1749
|
+
windowStartMs: startTime * 1000,
|
|
1750
|
+
windowEndMs: endTime * 1000,
|
|
1751
|
+
// The LOGICAL fetch stamp carried into every session state's
|
|
1752
|
+
// snapshot_verified_at: the same clock that produced the query
|
|
1753
|
+
// range, not the later enqueue moment (append-only queue
|
|
1754
|
+
// replays it verbatim, so transport retries never fake
|
|
1755
|
+
// freshness).
|
|
1756
|
+
snapshotVerifiedAtMs: nowMs,
|
|
1757
|
+
});
|
|
1758
|
+
// A partially malformed snapshot throws inside the parser (fail
|
|
1759
|
+
// closed - it must not become the authoritative window state);
|
|
1760
|
+
// warnProviderParseFailure below reports it without sensitive
|
|
1761
|
+
// data while unrelated providers continue.
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
warnProviderParseFailure("TRAE Work CN", err, opts);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1666
1769
|
// ── Kiro (SQLite-based, with JSONL fallback; dual-install aware) ──
|
|
1667
1770
|
let kiroResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1668
1771
|
if (sourceAllowed("kiro")) {
|
|
@@ -2157,7 +2260,12 @@ async function cmdSync(argv, context = {}) {
|
|
|
2157
2260
|
}
|
|
2158
2261
|
|
|
2159
2262
|
// ── Grok Build (xAI) ──
|
|
2160
|
-
let grokResult = {
|
|
2263
|
+
let grokResult = {
|
|
2264
|
+
recordsProcessed: 0,
|
|
2265
|
+
eventsAggregated: 0,
|
|
2266
|
+
bucketsQueued: 0,
|
|
2267
|
+
projectBucketsQueued: 0,
|
|
2268
|
+
};
|
|
2161
2269
|
// Full passive scan of all Grok sessions (historical + any not covered by hook)
|
|
2162
2270
|
const grokSessions = sourceAllowed("grok") ? resolveGrokBuildSessions(process.env) : [];
|
|
2163
2271
|
const grokSessionInputs = [...grokSessions];
|
|
@@ -2179,6 +2287,11 @@ async function cmdSync(argv, context = {}) {
|
|
|
2179
2287
|
sessionId: hookSessionId,
|
|
2180
2288
|
sessionDir:
|
|
2181
2289
|
typeof grokHookSignal.sessionDir === "string" ? grokHookSignal.sessionDir : undefined,
|
|
2290
|
+
cwd: typeof grokHookSignal.cwd === "string" ? grokHookSignal.cwd : undefined,
|
|
2291
|
+
encodedCwd:
|
|
2292
|
+
typeof grokHookSignal.sessionDir === "string" && grokHookSignal.sessionDir
|
|
2293
|
+
? path.basename(path.dirname(grokHookSignal.sessionDir))
|
|
2294
|
+
: undefined,
|
|
2182
2295
|
updatesPath:
|
|
2183
2296
|
typeof grokHookSignal.updatesPath === "string" ? grokHookSignal.updatesPath : undefined,
|
|
2184
2297
|
signalsPath:
|
|
@@ -2208,6 +2321,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2208
2321
|
sessions: grokSessionInputs,
|
|
2209
2322
|
cursors,
|
|
2210
2323
|
queuePath,
|
|
2324
|
+
projectQueuePath,
|
|
2211
2325
|
env: process.env,
|
|
2212
2326
|
onProgress: (p) => {
|
|
2213
2327
|
if (!progress?.enabled) return;
|
|
@@ -2224,6 +2338,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
2224
2338
|
recordsProcessed: grokResult.recordsProcessed + grokScanResult.recordsProcessed,
|
|
2225
2339
|
eventsAggregated: grokResult.eventsAggregated + grokScanResult.eventsAggregated,
|
|
2226
2340
|
bucketsQueued: grokResult.bucketsQueued + grokScanResult.bucketsQueued,
|
|
2341
|
+
projectBucketsQueued:
|
|
2342
|
+
(grokResult.projectBucketsQueued || 0) + (grokScanResult.projectBucketsQueued || 0),
|
|
2227
2343
|
};
|
|
2228
2344
|
}
|
|
2229
2345
|
if (isFullSourceScan && opts.repairGrok) {
|
|
@@ -2584,6 +2700,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2584
2700
|
qoderCnResult.recordsProcessed +
|
|
2585
2701
|
claudeScienceResult.recordsProcessed +
|
|
2586
2702
|
cursorResult.recordsProcessed +
|
|
2703
|
+
traeCnResult.recordsProcessed +
|
|
2587
2704
|
kiroResult.recordsProcessed +
|
|
2588
2705
|
kiroCliResult.recordsProcessed +
|
|
2589
2706
|
hermesResult.recordsProcessed +
|
|
@@ -2619,6 +2736,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2619
2736
|
qoderCnResult.bucketsQueued +
|
|
2620
2737
|
claudeScienceResult.bucketsQueued +
|
|
2621
2738
|
cursorResult.bucketsQueued +
|
|
2739
|
+
traeCnResult.bucketsQueued +
|
|
2622
2740
|
kiroResult.bucketsQueued +
|
|
2623
2741
|
kiroCliResult.bucketsQueued +
|
|
2624
2742
|
hermesResult.bucketsQueued +
|
|
@@ -2650,6 +2768,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2650
2768
|
cursorStore.requiresCommit !== true &&
|
|
2651
2769
|
totalParsed === 0 &&
|
|
2652
2770
|
totalBuckets === 0 &&
|
|
2771
|
+
!(grokResult.projectBucketsQueued > 0) &&
|
|
2653
2772
|
!codexColdAuditDue &&
|
|
2654
2773
|
!codexFallbackRetryRan &&
|
|
2655
2774
|
!grokHookSignalConsumed &&
|
|
@@ -2972,6 +3091,8 @@ function recordCodexColdScanAudit(
|
|
|
2972
3091
|
module.exports = {
|
|
2973
3092
|
cmdSync,
|
|
2974
3093
|
acquireSyncLock,
|
|
3094
|
+
readQueueBatch,
|
|
3095
|
+
drainQueueToCloud,
|
|
2975
3096
|
migrateCursorUnknownBuckets,
|
|
2976
3097
|
migrateRolloutCumulativeDeltaBuckets,
|
|
2977
3098
|
repairCodebuddyLogJsonlOverlap,
|
|
@@ -3472,7 +3593,10 @@ async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePa
|
|
|
3472
3593
|
for (let batch = 0; batch < maxBatches; batch++) {
|
|
3473
3594
|
if (offset >= queueSize) break;
|
|
3474
3595
|
const result = await readQueueBatch(queuePath, offset, limit);
|
|
3475
|
-
|
|
3596
|
+
// A states-only tail (e.g. a retry batch whose bucket rows already
|
|
3597
|
+
// uploaded) is still a meaningful upload: it advances the canonical
|
|
3598
|
+
// session corrections cloud-side.
|
|
3599
|
+
if (result.buckets.length === 0 && result.sessionStates.length === 0) break;
|
|
3476
3600
|
|
|
3477
3601
|
const root = baseUrl.replace(/\/$/, "");
|
|
3478
3602
|
const anonKey = process.env.TOKENTRACKER_INSFORGE_ANON_KEY || "";
|
|
@@ -3485,7 +3609,12 @@ async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePa
|
|
|
3485
3609
|
const res = await fetch(`${root}/functions/${INGEST_SLUG}`, {
|
|
3486
3610
|
method: "POST",
|
|
3487
3611
|
headers,
|
|
3488
|
-
body: JSON.stringify({
|
|
3612
|
+
body: JSON.stringify({
|
|
3613
|
+
hourly: result.buckets,
|
|
3614
|
+
...(result.sessionStates.length > 0
|
|
3615
|
+
? { account_session_states: result.sessionStates }
|
|
3616
|
+
: {}),
|
|
3617
|
+
}),
|
|
3489
3618
|
});
|
|
3490
3619
|
|
|
3491
3620
|
const rawText = await res.text().catch(() => "");
|
|
@@ -3522,6 +3651,26 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
3522
3651
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
3523
3652
|
|
|
3524
3653
|
const bucketMap = new Map();
|
|
3654
|
+
// Account session states (kind: "account_session_state") are queue
|
|
3655
|
+
// control records, not usage rows: collect them separately so they ride
|
|
3656
|
+
// the same upload (after the bucket rows in file order) without entering
|
|
3657
|
+
// the bucket stream. Keyed by (source, session_id) LAST-WINS: the
|
|
3658
|
+
// append-only queue writes a session's newer observation after its older
|
|
3659
|
+
// one, and only the latest per session may reach the ingest batch (a
|
|
3660
|
+
// duplicate would make the cloud ON CONFLICT affect one row twice).
|
|
3661
|
+
// snapshot_verified_at rides along verbatim from the ORIGINAL fetch, so a
|
|
3662
|
+
// transport retry never fakes logical freshness.
|
|
3663
|
+
//
|
|
3664
|
+
// Batching: session-state records count toward the SAME per-batch record
|
|
3665
|
+
// cap (maxBuckets) as bucket rows. The ingest edge rejects a request with
|
|
3666
|
+
// more than 500 states (HTTP 400); before they counted, a states-heavy
|
|
3667
|
+
// queue (a fresh device's first 30-day TRAE sync appends one record per
|
|
3668
|
+
// observed session) was read in ONE uncapped batch - the 400 left the
|
|
3669
|
+
// queue offset untouched and every retry re-read the identical oversized
|
|
3670
|
+
// request, a permanent upload failure. With the default batchSize of 200
|
|
3671
|
+
// (and the MAX_INGEST_BUCKETS=500 clamp above), every request stays under
|
|
3672
|
+
// the edge cap while batching follows the existing design.
|
|
3673
|
+
const sessionStateMap = new Map();
|
|
3525
3674
|
let offset = startOffset;
|
|
3526
3675
|
let linesRead = 0;
|
|
3527
3676
|
for await (const line of rl) {
|
|
@@ -3534,6 +3683,37 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
3534
3683
|
} catch (_e) {
|
|
3535
3684
|
continue;
|
|
3536
3685
|
}
|
|
3686
|
+
if (bucket?.kind === "account_session_state") {
|
|
3687
|
+
if (typeof bucket.source === "string" && typeof bucket.session_id === "string" && bucket.session_id.trim()) {
|
|
3688
|
+
const ssSource = bucket.source.trim().toLowerCase();
|
|
3689
|
+
sessionStateMap.set(ssSource + "|" + bucket.session_id.trim(), {
|
|
3690
|
+
source: ssSource,
|
|
3691
|
+
session_id: bucket.session_id.trim(),
|
|
3692
|
+
model: typeof bucket.model === "string" ? bucket.model : "",
|
|
3693
|
+
bucket_start: typeof bucket.bucket_start === "string" ? bucket.bucket_start : "",
|
|
3694
|
+
input_tokens: Number(bucket.input_tokens) || 0,
|
|
3695
|
+
output_tokens: Number(bucket.output_tokens) || 0,
|
|
3696
|
+
cached_input_tokens: Number(bucket.cached_input_tokens) || 0,
|
|
3697
|
+
cache_creation_input_tokens: Number(bucket.cache_creation_input_tokens) || 0,
|
|
3698
|
+
reasoning_output_tokens: Number(bucket.reasoning_output_tokens) || 0,
|
|
3699
|
+
total_tokens: Number(bucket.total_tokens) || 0,
|
|
3700
|
+
...(typeof bucket.snapshot_verified_at === "string" && bucket.snapshot_verified_at
|
|
3701
|
+
? { snapshot_verified_at: bucket.snapshot_verified_at }
|
|
3702
|
+
: {}),
|
|
3703
|
+
});
|
|
3704
|
+
// Counted like a bucket row: the record rides THIS batch and its
|
|
3705
|
+
// bytes are covered by nextOffset, so the next batch resumes right
|
|
3706
|
+
// after it.
|
|
3707
|
+
linesRead += 1;
|
|
3708
|
+
if (linesRead >= maxBuckets) break;
|
|
3709
|
+
}
|
|
3710
|
+
continue;
|
|
3711
|
+
}
|
|
3712
|
+
// Pre-release watermark records from this feature branch's dev queues
|
|
3713
|
+
// are obsolete control records: drop them instead of letting them enter
|
|
3714
|
+
// the bucket stream (they carry no hour_start and would be skipped
|
|
3715
|
+
// downstream anyway, but be explicit).
|
|
3716
|
+
if (bucket?.kind === "account_sync_watermark") continue;
|
|
3537
3717
|
const hourStart = typeof bucket?.hour_start === "string" ? bucket.hour_start : null;
|
|
3538
3718
|
if (!hourStart) continue;
|
|
3539
3719
|
const source = (typeof bucket?.source === "string" ? bucket.source.trim().toLowerCase() : "") || "codex";
|
|
@@ -3553,7 +3733,11 @@ async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
|
3553
3733
|
|
|
3554
3734
|
rl.close();
|
|
3555
3735
|
stream.close?.();
|
|
3556
|
-
return {
|
|
3736
|
+
return {
|
|
3737
|
+
buckets: Array.from(bucketMap.values()),
|
|
3738
|
+
sessionStates: Array.from(sessionStateMap.values()),
|
|
3739
|
+
nextOffset: offset,
|
|
3740
|
+
};
|
|
3557
3741
|
}
|
|
3558
3742
|
|
|
3559
3743
|
function normalizeGrokRepairSource(value) {
|