tokentracker-cli 1.4.7 → 1.4.10
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 +2 -2
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +11 -3
- package/README.zh-CN.md +3 -3
- package/bin/tracker.js +6 -0
- package/dashboard/dist/assets/{AchievementBadge-DF8q1x6R.js → AchievementBadge-BTqlXUP1.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-B8Dg5myo.js → AchievementsPage-DbKUhaY-.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-oPPiyP3y.js → AchievementsSection-El1F-OC7.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-PneMW4B4.js → BadgeDetailModal--8PySLzP.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-GOlSvEiz.js → BotAnimated-DBcOS4vh.js} +1 -1
- package/dashboard/dist/assets/{Card-BT5G0GE0.js → Card-CeGs8TaY.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-BNeL5Muo.js → CloudCheckoutPage-DCg3VVAi.js} +1 -1
- package/dashboard/dist/assets/CloudPage-CmeFDrmk.js +2 -0
- package/dashboard/dist/assets/{CommandPalette-CiMSYHNK.js → CommandPalette-CrSNoZW9.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-fK3BE9CW.js → CommunityStatsModal-ynB4j0Qb.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CrUlEHyZ.js → ConfirmModal-DDTUf-My.js} +1 -1
- package/dashboard/dist/assets/{Controls-CbjSwq6k.js → Controls-DXsJjEU1.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-fr4sgbLB.js +1 -0
- package/dashboard/dist/assets/{DateRangePopover-E-2Z6imh.js → DateRangePopover-gTiPISL_.js} +1 -1
- package/dashboard/dist/assets/DevicePage-DvHVjCCH.js +1 -0
- package/dashboard/dist/assets/{DialogClose-xJKE22Y6.js → DialogClose-CXwouQe4.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-BzbzETUC.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-f8vnnH7z.js → DialogTitle-Cd_k-Nyw.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-Ci9WZfHK.js → ElectricLogo-C4vUNTCI.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-BXqtyAmn.js → FadeIn-CGM5RBFy.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BV5SYJMi.js → HeaderGithubStar-el5-DhVb.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-D3gpW3Jh.js → HoverTooltip-C7YaWwJx.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-G7fIWXpL.js → IpCheckPage-EKhbH9hJ.js} +1 -1
- package/dashboard/dist/assets/LandingPage-DJYOv1AM.js +2 -0
- package/dashboard/dist/assets/{LeaderboardPage-D2_wXazL.js → LeaderboardPage-CwzJxA8p.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-3xJ-mH2v.js → LeaderboardProBadge-DYLp9pJY.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-23pH6Ud7.js → LeaderboardProfileModal-DqD2h9dD.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-DSezSR58.js → LeaderboardProfilePage-CarYvpYe.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-4T0BpfYS.js +6 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-Dsk-d0Hr.js → LocalOnlyNotice-CAR-mqy0.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-C7dugIIn.js → LoginCard-Czq-UgAU.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-B7RuLsTF.js → LoginModal-D4uxfzZb.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-u32vJQN9.js → LoginPage-Bg3BG0Wv.js} +1 -1
- package/dashboard/dist/assets/{PetPage-Dbc975oU.js → PetPage-BgwBxRyx.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-D-yECvH9.js → PopoverPopup-k3o_Jp2b.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-CHtZiJUN.js → ProviderIcon-Bbw6MUSf.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-ctuIsNk0.js → ResetPasswordPage-DIKPVu5K.js} +1 -1
- package/dashboard/dist/assets/{Select-DN4Kq_R1.js → Select-kA0dV5Qw.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-C-HmeD8L.js → SelectItemText-D4YCIn1x.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-BZOL3JMt.js → SelfHostPage-C3Y9qF3P.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-C0sDYa8c.js → ServiceStatusPage-o1G3SJhV.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-l0-FkcJB.js → SessionsPage-CA-efxhU.js} +3 -3
- package/dashboard/dist/assets/SettingsPage-Mr84vxQL.js +5 -0
- package/dashboard/dist/assets/{SkillsPage-Cz6G9d3o.js → SkillsPage-BtRhLSpz.js} +1 -1
- package/dashboard/dist/assets/TokenGalaxy-CcFjCCuC.js +4426 -0
- package/dashboard/dist/assets/TooltipPopup-6aTx3jDf.js +1 -0
- package/dashboard/dist/assets/TrendMonitor-DoywXr8h.js +19 -0
- package/dashboard/dist/assets/{WidgetsPage-BKo3DBxT.js → WidgetsPage-BuGSzvR0.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-Blm3aPO5.js → WrappedPage-Bm6X-SUZ.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-C9a2vCBN.css +1 -0
- package/dashboard/dist/assets/__vite-browser-external-ClVJNQu3.js +45 -0
- package/dashboard/dist/assets/{agent-logos-DMtDEx7_.js → agent-logos-Sc8Sejgx.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-Bo9y7VzF.js → arrow-left-Cm1LG2i_.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-BLMU8Y5U.js → arrow-right-WP3kIwiy.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-B4hXO4sc.js → arrow-up-right-CjyprVMj.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-D9HaEtgo.js → calendar-days-Bm88-Gcb.js} +1 -1
- package/dashboard/dist/assets/{check-BJNGKpX8.js → check-CzIxFg0I.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-CoGC_RLK.js → chevron-down-bfGK8629.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-Du5FoHqV.js → cloud-checkout-C9DFQ7yM.js} +1 -1
- package/dashboard/dist/assets/{composite-BfIUPpl8.js → composite-CdExmgTx.js} +1 -1
- package/dashboard/dist/assets/copy-CPPdujn2.js +1 -0
- package/dashboard/dist/assets/{download-C1K_v0C0.js → download-Cl6_dKeK.js} +1 -1
- package/dashboard/dist/assets/{external-link-D-xHhfj5.js → external-link-B0r6Wo4e.js} +1 -1
- package/dashboard/dist/assets/{flame-DXD6uvhM.js → flame-wEezvzXx.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-B9riGvNj.js → format-tokens-vWpBpKpM.js} +1 -1
- package/dashboard/dist/assets/{gift-LPR-nD0J.js → gift-BFjblRqU.js} +1 -1
- package/dashboard/dist/assets/host-mode-Bc_iTzHk.js +1 -0
- package/dashboard/dist/assets/{icons-DLdtLqEt.js → icons-DX3dCNJP.js} +1 -1
- package/dashboard/dist/assets/{index-CXdJ7zMN.js → index-D-TnqdBv.js} +1 -1
- package/dashboard/dist/assets/{index-CC3cXeBa.js → index-D1Zdrpom.js} +1 -1
- package/dashboard/dist/assets/index-DBF1bJlm.js +502 -0
- package/dashboard/dist/assets/{index-BL11MboX.js → index-p8Gt3NFg.js} +1 -1
- package/dashboard/dist/assets/{info-tnXKCyuK.js → info-Cqi971MA.js} +1 -1
- package/dashboard/dist/assets/{link-2-DOk9Tcg1.js → link-2-79sm__cT.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-DIl96Mw-.js → loader-circle-CDoVfOUb.js} +1 -1
- package/dashboard/dist/assets/local-usage-transfer-EcXnuwCj.js +1 -0
- package/dashboard/dist/assets/{modifiers.esm-2H_1taJ6.js → modifiers.esm-v5_BKSmY.js} +1 -1
- package/dashboard/dist/assets/module-C2SOSUuR.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-tnp-03hs.js → monitor-smartphone-CO67DffO.js} +1 -1
- package/dashboard/dist/assets/{play-BGMuGTdN.js → play-CbvuGtc3.js} +1 -1
- package/dashboard/dist/assets/{plus-XETAaErq.js → plus-jc2gXlRM.js} +1 -1
- package/dashboard/dist/assets/{provider-display-BYNT4zPc.js → provider-display-BK3EbRLM.js} +1 -1
- package/dashboard/dist/assets/{react-OW7HALVO.js → react-DMs4C4qS.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-CAfS8-ct.js → safePolygon-cY9H0GBN.js} +1 -1
- package/dashboard/dist/assets/{search-B7ZJhoau.js → search-fqTJoYYA.js} +1 -1
- package/dashboard/dist/assets/{server-CWrP7mtL.js → server-jpBtvTvr.js} +1 -1
- package/dashboard/dist/assets/{shield-check-ip5IQEK3.js → shield-check-DQiBlJbN.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CpyoNTeB.js → skills-api-CN2r_rmt.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-YvzWzPDu.js → sortable.esm-DrS52yOK.js} +1 -1
- package/dashboard/dist/assets/{store-DRK6-JBg.js → store-K2Mk6aMn.js} +1 -1
- package/dashboard/dist/assets/{terminal-DQRoJSvh.js → terminal-Q4SruAlh.js} +1 -1
- package/dashboard/dist/assets/{trash-8h_26atF.js → trash-C1Ob-g8E.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-DYlhIXsp.js → use-community-stats-C-Gxpp9D.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-AxJF9SEJ.js → use-latest-request-guard-DK-A4bBp.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-ZauQzxE_.js +1 -0
- package/dashboard/dist/assets/{use-native-settings-BQgB6_PB.js → use-native-settings-BZLEIUK8.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-ogi7Okzf.js → use-ordered-list-De_Mcm_H.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-DatTtp2N.js +1 -0
- package/dashboard/dist/assets/{use-transform-BQqoo1et.js → use-transform-XCDD5IrX.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-BncGDhoF.js +1 -0
- package/dashboard/dist/assets/{useCurrency-Bv9g8mpq.js → useCurrency-DXPclxBT.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-CQiZGknc.js → usePositioner-C-E1npCy.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-BUVirY0k.js → useTokenFormat-B70xhy9u.js} +1 -1
- package/dashboard/dist/assets/{zap-DR3VD0yY.js → zap-D7gRgGCj.js} +1 -1
- package/dashboard/dist/index.html +5 -5
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/privacy.html +8 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +4 -3
- package/src/cli.js +10 -0
- package/src/commands/sync.js +18 -3
- package/src/lib/codex-rollout-parser.js +29 -2
- package/src/lib/codex-service-tier.js +13 -0
- package/src/lib/commandcode-limits.js +27 -11
- package/src/lib/grok-limits.js +1 -0
- package/src/lib/local-api.js +122 -11
- package/src/lib/local-usage-transfer.js +271 -0
- package/src/lib/opencode-go-limits.js +17 -6
- package/src/lib/pricing/curated-overrides.json +24 -0
- package/src/lib/pricing/index.js +50 -39
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/provider-api-key-store.js +110 -0
- package/src/lib/rollout.js +27 -3
- package/src/lib/sentry.js +231 -0
- package/src/lib/session-analytics.js +15 -2
- package/src/lib/static-server.js +15 -2
- package/src/lib/usage-limits.js +74 -24
- package/src/lib/workbuddy-ipc.js +191 -0
- package/src/lib/workbuddy-limits.js +564 -0
- package/dashboard/dist/assets/CloudPage-26bhEYqI.js +0 -2
- package/dashboard/dist/assets/DashboardPage-CP_PMMga.js +0 -1
- package/dashboard/dist/assets/DevicePage-B0fLd80M.js +0 -1
- package/dashboard/dist/assets/DialogDescription-CXDxWzGi.js +0 -1
- package/dashboard/dist/assets/LandingPage-BQNOdAkR.js +0 -2
- package/dashboard/dist/assets/LimitsPage-BLJsmVVy.js +0 -9
- package/dashboard/dist/assets/SettingsPage-CrczfZiZ.js +0 -5
- package/dashboard/dist/assets/TokenGalaxy-B5IGaLQK.js +0 -4426
- package/dashboard/dist/assets/TooltipPopup-B6FqLWO7.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-B4kJUwmF.js +0 -19
- package/dashboard/dist/assets/__vite-browser-external--HDUIUZr.js +0 -45
- package/dashboard/dist/assets/__vite-browser-external-B0m8a6Te.css +0 -1
- package/dashboard/dist/assets/host-mode-C2K0MMOs.js +0 -1
- package/dashboard/dist/assets/module-DOCYJaxl.js +0 -3
- package/dashboard/dist/assets/use-limits-display-prefs-DOZzuTwo.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-D5PHT_kx.js +0 -1
- package/dashboard/dist/assets/use-session-efficiency-pref-DP3hu3Rp.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-DAf6O4aZ.js +0 -1
package/src/cli.js
CHANGED
|
@@ -10,6 +10,16 @@ const { cmdWrapped } = require("./commands/wrapped");
|
|
|
10
10
|
const { cmdSessions } = require("./commands/sessions");
|
|
11
11
|
|
|
12
12
|
async function run(argv) {
|
|
13
|
+
// Best-effort Sentry init (no-op without DSN / when telemetry opted out).
|
|
14
|
+
try {
|
|
15
|
+
const { initSentry } = require("./lib/sentry");
|
|
16
|
+
const { resolveTrackerPaths } = require("./lib/tracker-paths");
|
|
17
|
+
const { trackerDir } = await resolveTrackerPaths().catch(() => ({ trackerDir: null }));
|
|
18
|
+
await initSentry({ trackerDir });
|
|
19
|
+
} catch {
|
|
20
|
+
/* never block CLI startup on telemetry */
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
const [command, ...rest] = argv;
|
|
14
24
|
|
|
15
25
|
// No args → launch dashboard
|
package/src/commands/sync.js
CHANGED
|
@@ -202,6 +202,18 @@ const {
|
|
|
202
202
|
scanRootDirState,
|
|
203
203
|
} = require("../lib/scan-roots");
|
|
204
204
|
const { resolveRuntimeConfig, isLegacyInsforgeBaseUrl } = require("../lib/runtime-config");
|
|
205
|
+
const { resolveDeviceTokenPresence } = require("../lib/cloud-device-token");
|
|
206
|
+
|
|
207
|
+
function formatSyncUploadSummaryLine({ runtimeDeviceToken, uploadResult, tokenPresentOnDisk }) {
|
|
208
|
+
if (runtimeDeviceToken) {
|
|
209
|
+
return `- Uploaded: ${uploadResult.inserted} inserted, ${uploadResult.skipped} skipped`;
|
|
210
|
+
}
|
|
211
|
+
if (tokenPresentOnDisk) {
|
|
212
|
+
return "- Uploaded: skipped (handled by local server; device token is set)";
|
|
213
|
+
}
|
|
214
|
+
return "- Uploaded: skipped (no device token)";
|
|
215
|
+
}
|
|
216
|
+
|
|
205
217
|
const { extractTokenCount } = require("../lib/codex-rollout-parser");
|
|
206
218
|
const {
|
|
207
219
|
consumeUsageDelta,
|
|
@@ -3763,9 +3775,11 @@ async function cmdSync(argv, context = {}) {
|
|
|
3763
3775
|
"Sync finished:",
|
|
3764
3776
|
`- Parsed files: ${totalParsed}`,
|
|
3765
3777
|
`- New 30-min buckets queued: ${totalBuckets}`,
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3778
|
+
formatSyncUploadSummaryLine({
|
|
3779
|
+
runtimeDeviceToken: runtime.deviceToken,
|
|
3780
|
+
uploadResult,
|
|
3781
|
+
tokenPresentOnDisk: resolveDeviceTokenPresence({ trackerDir, config, runtime }).set,
|
|
3782
|
+
}),
|
|
3769
3783
|
runtime.deviceToken && pendingBytes > 0 && !opts.drain
|
|
3770
3784
|
? `- Remaining: ${formatBytes(pendingBytes)} pending (run sync again, or use --drain)`
|
|
3771
3785
|
: null,
|
|
@@ -3900,6 +3914,7 @@ function recordCodexColdScanAudit(
|
|
|
3900
3914
|
}
|
|
3901
3915
|
|
|
3902
3916
|
module.exports = {
|
|
3917
|
+
formatSyncUploadSummaryLine,
|
|
3903
3918
|
cmdSync,
|
|
3904
3919
|
acquireSyncLock,
|
|
3905
3920
|
readQueueBatch,
|
|
@@ -30,7 +30,7 @@ const {
|
|
|
30
30
|
currentCodexModel,
|
|
31
31
|
snapshotCodexModelAttributionState,
|
|
32
32
|
} = require("./codex-model-attribution");
|
|
33
|
-
const { readCodexServiceTier, isPriorityServiceTier } = require("./codex-service-tier");
|
|
33
|
+
const { readCodexServiceTier, isPriorityServiceTier, isUltrafastServiceTier } = require("./codex-service-tier");
|
|
34
34
|
const {
|
|
35
35
|
createUsageRecordState,
|
|
36
36
|
extractTokenUsageRecord,
|
|
@@ -731,16 +731,27 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
731
731
|
priority_long_context_cache_creation_input_tokens: 0,
|
|
732
732
|
priority_long_context_output_tokens: 0,
|
|
733
733
|
priority_long_context_reasoning_output_tokens: 0,
|
|
734
|
+
ultrafast_input_tokens: 0,
|
|
735
|
+
ultrafast_cached_input_tokens: 0,
|
|
736
|
+
ultrafast_cache_creation_input_tokens: 0,
|
|
737
|
+
ultrafast_output_tokens: 0,
|
|
738
|
+
ultrafast_reasoning_output_tokens: 0,
|
|
739
|
+
ultrafast_long_context_input_tokens: 0,
|
|
740
|
+
ultrafast_long_context_cached_input_tokens: 0,
|
|
741
|
+
ultrafast_long_context_cache_creation_input_tokens: 0,
|
|
742
|
+
ultrafast_long_context_output_tokens: 0,
|
|
743
|
+
ultrafast_long_context_reasoning_output_tokens: 0,
|
|
734
744
|
usage_events: 0,
|
|
735
745
|
rerouted_usage_events: 0,
|
|
736
746
|
long_context_usage_events: 0,
|
|
737
747
|
priority_usage_events: 0,
|
|
748
|
+
ultrafast_usage_events: 0,
|
|
738
749
|
selected_models: new Set(),
|
|
739
750
|
reroute_reasons: new Set(),
|
|
740
751
|
};
|
|
741
752
|
byModel.set(effectiveModel, row);
|
|
742
753
|
}
|
|
743
|
-
const subsetKeys = Object.keys(row).filter((key) => /^(?:long_context_|priority_)|^(?:usage_events|rerouted_usage_events)$/.test(key));
|
|
754
|
+
const subsetKeys = Object.keys(row).filter((key) => /^(?:long_context_|priority_|ultrafast_)|^(?:usage_events|rerouted_usage_events)$/.test(key));
|
|
744
755
|
const before = Object.fromEntries(subsetKeys.map((key) => [key, row[key]]));
|
|
745
756
|
addInto(row, delta);
|
|
746
757
|
row.usage_events += 1;
|
|
@@ -787,6 +798,22 @@ async function parseCodexRolloutFile(filePath, {
|
|
|
787
798
|
row.priority_long_context_reasoning_output_tokens += delta.reasoning_output_tokens;
|
|
788
799
|
}
|
|
789
800
|
}
|
|
801
|
+
if (isUltrafastServiceTier(attribution.serviceTier)) {
|
|
802
|
+
row.ultrafast_usage_events += 1;
|
|
803
|
+
row.ultrafast_input_tokens += delta.input_tokens;
|
|
804
|
+
row.ultrafast_cached_input_tokens += delta.cached_input_tokens;
|
|
805
|
+
row.ultrafast_cache_creation_input_tokens += delta.cache_creation_input_tokens;
|
|
806
|
+
row.ultrafast_output_tokens += delta.output_tokens;
|
|
807
|
+
row.ultrafast_reasoning_output_tokens += delta.reasoning_output_tokens;
|
|
808
|
+
if (isLongContext) {
|
|
809
|
+
row.ultrafast_long_context_input_tokens += delta.input_tokens;
|
|
810
|
+
row.ultrafast_long_context_cached_input_tokens += delta.cached_input_tokens;
|
|
811
|
+
row.ultrafast_long_context_cache_creation_input_tokens +=
|
|
812
|
+
delta.cache_creation_input_tokens;
|
|
813
|
+
row.ultrafast_long_context_output_tokens += delta.output_tokens;
|
|
814
|
+
row.ultrafast_long_context_reasoning_output_tokens += delta.reasoning_output_tokens;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
790
817
|
return { ...delta, ...Object.fromEntries(subsetKeys.map((key) => [key, row[key] - before[key]])),
|
|
791
818
|
selected_models: attribution.selectedModel ? [attribution.selectedModel] : [],
|
|
792
819
|
reroute_reasons: attribution.rerouteReason ? [attribution.rerouteReason] : [],
|
|
@@ -25,6 +25,13 @@ const CODEX_SERVICE_TIER_MARKER = '"thread_settings_applied"';
|
|
|
25
25
|
// does not silently fall back to Standard.
|
|
26
26
|
const PRIORITY_SERVICE_TIERS = new Set(["priority", "fast"]);
|
|
27
27
|
|
|
28
|
+
// Ultrafast is a distinct paid speed tier (service_tier "ultrafast") for
|
|
29
|
+
// gpt-6-astra and gpt-6.1-sol. It is NOT Fast: Fast uses priority_* subsets and
|
|
30
|
+
// priority_multiplier (2x); Ultrafast uses ultrafast_* subsets and
|
|
31
|
+
// ultrafast_multiplier (6x). Mixing them into the priority columns would
|
|
32
|
+
// understate Ultrafast at 2x.
|
|
33
|
+
const ULTRAFAST_SERVICE_TIERS = new Set(["ultrafast"]);
|
|
34
|
+
|
|
28
35
|
function readCodexServiceTier(obj) {
|
|
29
36
|
if (!obj || obj.type !== "event_msg") return null;
|
|
30
37
|
const payload = obj.payload;
|
|
@@ -41,9 +48,15 @@ function isPriorityServiceTier(tier) {
|
|
|
41
48
|
return typeof tier === "string" && PRIORITY_SERVICE_TIERS.has(tier);
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
function isUltrafastServiceTier(tier) {
|
|
52
|
+
return typeof tier === "string" && ULTRAFAST_SERVICE_TIERS.has(tier);
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
module.exports = {
|
|
45
56
|
CODEX_SERVICE_TIER_MARKER,
|
|
46
57
|
PRIORITY_SERVICE_TIERS,
|
|
58
|
+
ULTRAFAST_SERVICE_TIERS,
|
|
47
59
|
readCodexServiceTier,
|
|
48
60
|
isPriorityServiceTier,
|
|
61
|
+
isUltrafastServiceTier,
|
|
49
62
|
};
|
|
@@ -12,16 +12,20 @@
|
|
|
12
12
|
// (resetAt is epoch milliseconds) plus a `credits` balance object; the
|
|
13
13
|
// subscription payload carries `{ data: { planId, status, ... } }`.
|
|
14
14
|
//
|
|
15
|
-
// Auth resolution
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
15
|
+
// Auth resolution: `COMMAND_CODE_API_KEY` wins, then a key saved from
|
|
16
|
+
// TokenTracker's Limits panel (provider-api-key-store.js), then the `apiKey`
|
|
17
|
+
// field of `~/.commandcode/auth.json` (prod; written 0600 by `cmd login`,
|
|
18
|
+
// never edited by hand). The panel store is preferred over the CLI file
|
|
19
|
+
// because saving a key there is an explicit user action, while the CLI file
|
|
20
|
+
// can be a stale session. No key -> `{ configured: false }` so the Limits
|
|
21
|
+
// panel hides the section instead of erroring.
|
|
19
22
|
//
|
|
20
23
|
// The API exposes 5h + weekly rolling caps; extra pay-as-you-go credits
|
|
21
24
|
// bypass the caps server-side, so the reported used/cap needs no adjustment.
|
|
22
25
|
|
|
23
26
|
const fs = require("node:fs");
|
|
24
27
|
const path = require("node:path");
|
|
28
|
+
const { readStoredApiKey } = require("./provider-api-key-store");
|
|
25
29
|
|
|
26
30
|
const COMMANDCODE_API_BASE_URL = "https://api.commandcode.ai";
|
|
27
31
|
const COMMANDCODE_API_KEY_ENV = "COMMAND_CODE_API_KEY";
|
|
@@ -57,25 +61,33 @@ function resolveCommandcodeAuthPath({ home, env = process.env } = {}) {
|
|
|
57
61
|
return path.join(dir, COMMANDCODE_AUTH_DIRNAME, COMMANDCODE_AUTH_FILENAME);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
function
|
|
64
|
+
function readCommandcodeCredential({ home, trackerDir, env = process.env } = {}) {
|
|
61
65
|
const fromEnv =
|
|
62
66
|
env && typeof env === "object" ? env[COMMANDCODE_API_KEY_ENV] : null;
|
|
63
|
-
if (isNonEmptyString(fromEnv)) return fromEnv.trim();
|
|
67
|
+
if (isNonEmptyString(fromEnv)) return { apiKey: fromEnv.trim(), source: "env" };
|
|
68
|
+
const stored = readStoredApiKey({ provider: "command-code", trackerDir, home });
|
|
69
|
+
if (stored) return { apiKey: stored, source: "stored" };
|
|
64
70
|
const authPath = resolveCommandcodeAuthPath({ home, env });
|
|
65
|
-
if (!authPath) return null;
|
|
71
|
+
if (!authPath) return { apiKey: null, source: "none" };
|
|
66
72
|
let raw;
|
|
67
73
|
try {
|
|
68
74
|
raw = fs.readFileSync(authPath, "utf8");
|
|
69
75
|
} catch {
|
|
70
|
-
return null;
|
|
76
|
+
return { apiKey: null, source: "none" };
|
|
71
77
|
}
|
|
72
78
|
let parsed;
|
|
73
79
|
try {
|
|
74
80
|
parsed = JSON.parse(raw);
|
|
75
81
|
} catch {
|
|
76
|
-
return null;
|
|
82
|
+
return { apiKey: null, source: "none" };
|
|
77
83
|
}
|
|
78
|
-
return isNonEmptyString(parsed?.apiKey)
|
|
84
|
+
return isNonEmptyString(parsed?.apiKey)
|
|
85
|
+
? { apiKey: parsed.apiKey.trim(), source: "cli" }
|
|
86
|
+
: { apiKey: null, source: "none" };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readCommandcodeApiKey(opts = {}) {
|
|
90
|
+
return readCommandcodeCredential(opts).apiKey;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
function normalizeResetAt(value) {
|
|
@@ -246,11 +258,13 @@ function withOrgParam(route, orgId) {
|
|
|
246
258
|
// usage-limits.js converts both into the panel row.
|
|
247
259
|
async function fetchCommandcodeLimits({
|
|
248
260
|
home,
|
|
261
|
+
trackerDir,
|
|
249
262
|
env = process.env,
|
|
250
263
|
fetchImpl = fetch,
|
|
251
264
|
baseUrl = COMMANDCODE_API_BASE_URL,
|
|
252
265
|
} = {}) {
|
|
253
|
-
const
|
|
266
|
+
const credential = readCommandcodeCredential({ home, trackerDir, env });
|
|
267
|
+
const apiKey = credential.apiKey;
|
|
254
268
|
if (!apiKey) return { configured: false };
|
|
255
269
|
|
|
256
270
|
const origin = resolveCommandcodeOrigin(baseUrl);
|
|
@@ -289,6 +303,7 @@ async function fetchCommandcodeLimits({
|
|
|
289
303
|
return {
|
|
290
304
|
configured: true,
|
|
291
305
|
error: null,
|
|
306
|
+
credential_source: credential.source,
|
|
292
307
|
plan_label: deriveCommandcodePlanLabel(planId),
|
|
293
308
|
subscription_status: isNonEmptyString(subscription?.status)
|
|
294
309
|
? subscription.status.trim()
|
|
@@ -308,6 +323,7 @@ module.exports = {
|
|
|
308
323
|
resolveCommandcodeHome,
|
|
309
324
|
resolveCommandcodeAuthPath,
|
|
310
325
|
readCommandcodeApiKey,
|
|
326
|
+
readCommandcodeCredential,
|
|
311
327
|
deriveCommandcodePlanLabel,
|
|
312
328
|
normalizeResetAt,
|
|
313
329
|
normalizeCommandcodeWindow,
|
package/src/lib/grok-limits.js
CHANGED
package/src/lib/local-api.js
CHANGED
|
@@ -19,6 +19,7 @@ const { functionUrlFor, fetchFunctionResponse } = require("./function-url");
|
|
|
19
19
|
const { createCloudDeviceTokenStore } = require("./cloud-device-token");
|
|
20
20
|
const { parseBearerToken } = require("./bearer-token");
|
|
21
21
|
const { writeJsonAtomicSync } = require("./atomic-json");
|
|
22
|
+
const { createLocalUsageTransfer, MAX_BYTES: LOCAL_USAGE_MAX_BYTES } = require("./local-usage-transfer");
|
|
22
23
|
|
|
23
24
|
const SYNC_TIMEOUT_MS = 120_000;
|
|
24
25
|
// A failed account-view request should not stall every dashboard refresh
|
|
@@ -401,17 +402,6 @@ function getRequestedUsageScope(url) {
|
|
|
401
402
|
return normalizeUsageScope(url.searchParams.get("scope"));
|
|
402
403
|
}
|
|
403
404
|
|
|
404
|
-
async function scopedQueueRows(queuePath, url) {
|
|
405
|
-
const scope = getRequestedUsageScope(url);
|
|
406
|
-
const allRows = await readQueueData(queuePath);
|
|
407
|
-
return {
|
|
408
|
-
scope,
|
|
409
|
-
allRows,
|
|
410
|
-
rows: filterRowsByUsageScope(allRows, scope),
|
|
411
|
-
excludedSources: listExcludedSources(allRows, scope),
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
405
|
// ── Local achievements ───────────────────────────────────────────────────────
|
|
416
406
|
// Local-only badges (the cloud nine live in scripts/ops/user-badges.sql).
|
|
417
407
|
// Thresholds are ordered bronze → silver → gold → diamond. This module is the
|
|
@@ -1007,6 +997,17 @@ function buildProxyHeaders(headers) {
|
|
|
1007
997
|
|
|
1008
998
|
function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir: isolatedTrackerDataDir = null, syncContext = null }) {
|
|
1009
999
|
const qp = queuePath || resolveQueuePath();
|
|
1000
|
+
const localUsage = createLocalUsageTransfer({ queuePath: qp, normalizeRow: normalizeQueueRow,
|
|
1001
|
+
identity: () => ({ id: getOrCreateMachineId(qp), name: getSystemDeviceName(), platform: process.platform }) });
|
|
1002
|
+
async function scopedQueueRows(queuePath, url) {
|
|
1003
|
+
const scope = getRequestedUsageScope(url);
|
|
1004
|
+
const ownRows = await readQueueData(queuePath);
|
|
1005
|
+
const selected = url.searchParams.get("device_id") || "";
|
|
1006
|
+
// Only offline IDs select a snapshot; other IDs retain the local fallback.
|
|
1007
|
+
const device = selected.startsWith("local:") ? selected : "";
|
|
1008
|
+
const allRows = url.pathname.startsWith("/functions/tokentracker-usage-") ? await localUsage.view(ownRows, device) : ownRows;
|
|
1009
|
+
return { scope, allRows, rows: filterRowsByUsageScope(allRows, scope), excludedSources: listExcludedSources(allRows, scope) };
|
|
1010
|
+
}
|
|
1010
1011
|
const normalizedServerVersion = typeof serverVersion === "string" ? serverVersion.trim() : "";
|
|
1011
1012
|
|
|
1012
1013
|
// Server-side cookie relay: captures auth cookies from InsForge cloud responses
|
|
@@ -1639,6 +1640,32 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
1639
1640
|
|
|
1640
1641
|
return async function handleLocalApi(req, res, url) {
|
|
1641
1642
|
const p = url.pathname;
|
|
1643
|
+
if (p === "/api/local-usage") {
|
|
1644
|
+
if (!isAuthorizedLocalMutation(req)) { json(res, { code: "unauthorized" }, 401); return true; }
|
|
1645
|
+
try {
|
|
1646
|
+
if (req.method === "GET") {
|
|
1647
|
+
const from = url.searchParams.get("from") || "";
|
|
1648
|
+
const to = url.searchParams.get("to") || "";
|
|
1649
|
+
const context = getTimeZoneContext(url);
|
|
1650
|
+
const inRange = (row) => { const day = rowDayKey(row, context); return day && (!from || day >= from) && (!to || day <= to); };
|
|
1651
|
+
json(res, await localUsage.devices(await readQueueData(qp), inRange));
|
|
1652
|
+
} else if (req.method === "POST") {
|
|
1653
|
+
const body = JSON.parse((await readBodyLimited(req, LOCAL_USAGE_MAX_BYTES + 4096)).toString("utf8"));
|
|
1654
|
+
if (body.action === "export") json(res, { snapshot: await localUsage.exportSnapshot() });
|
|
1655
|
+
else if (body.action === "preview") json(res, await localUsage.preview(body.snapshot));
|
|
1656
|
+
else if (body.action === "import") json(res, await localUsage.importSnapshot(body.snapshot, body.token));
|
|
1657
|
+
else if (body.action === "remove") { await localUsage.remove(body.machine_id); json(res, { ok: true }); }
|
|
1658
|
+
else json(res, { code: "invalid_snapshot" }, 400);
|
|
1659
|
+
} else json(res, { code: "method_not_allowed" }, 405);
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
json(res, { code: error.code && error.status ? error.code : "transfer_failed" }, error.status || 400);
|
|
1662
|
+
}
|
|
1663
|
+
return true;
|
|
1664
|
+
}
|
|
1665
|
+
if (p.startsWith("/functions/tokentracker-usage-") && url.searchParams.get("device_id")?.startsWith("local:")) {
|
|
1666
|
+
try { await localUsage.view([], url.searchParams.get("device_id")); }
|
|
1667
|
+
catch (error) { json(res, { code: error.code || "transfer_failed" }, error.status || 500); return true; }
|
|
1668
|
+
}
|
|
1642
1669
|
// Session recovery and the one-time native callback marker belong to local
|
|
1643
1670
|
// clients. Reject foreign requests before consuming either, including preflights.
|
|
1644
1671
|
const nativeMarkerRead = p === "/api/auth-bridge/verifier" && String(req.method).toUpperCase() === "GET";
|
|
@@ -2714,6 +2741,11 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
2714
2741
|
const from = url.searchParams.get("from") || "";
|
|
2715
2742
|
const to = url.searchParams.get("to") || "";
|
|
2716
2743
|
const requestedSource = (url.searchParams.get("source") || "claude").trim().toLowerCase();
|
|
2744
|
+
const selected = url.searchParams.get("device_id") || "";
|
|
2745
|
+
if (selected.startsWith("local:") ? selected !== `local:${getOrCreateMachineId(qp)}` : !selected && await localUsage.hasImports()) {
|
|
2746
|
+
json(res, { from, to, ...unsupportedCategoryPayload(requestedSource), reason: "local_history_only" });
|
|
2747
|
+
return true;
|
|
2748
|
+
}
|
|
2717
2749
|
if (requestedSource === "claude") {
|
|
2718
2750
|
try {
|
|
2719
2751
|
const result = await computeClaudeCategoryBreakdown({ from, to, projectDir: process.cwd() });
|
|
@@ -2978,6 +3010,84 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
2978
3010
|
return true;
|
|
2979
3011
|
}
|
|
2980
3012
|
|
|
3013
|
+
// --- provider API keys saved from the Limits panel ---
|
|
3014
|
+
// The key lives in a 0600 file next to the queue, never in the launch
|
|
3015
|
+
// environment: a global `launchctl setenv` reaches every launchd app on
|
|
3016
|
+
// macOS, which is exactly the cross-app leak in #837. GET only reports
|
|
3017
|
+
// whether a key exists and where it came from; it never echoes the secret.
|
|
3018
|
+
if (p === "/functions/tokentracker-provider-key") {
|
|
3019
|
+
const {
|
|
3020
|
+
envNameForProvider,
|
|
3021
|
+
isProviderId,
|
|
3022
|
+
normalizeApiKey,
|
|
3023
|
+
readStoredApiKey,
|
|
3024
|
+
writeStoredApiKey,
|
|
3025
|
+
} = require("./provider-api-key-store");
|
|
3026
|
+
const method = String(req.method || "GET").toUpperCase();
|
|
3027
|
+
if (method === "GET") {
|
|
3028
|
+
const provider = String(url.searchParams.get("provider") || "").trim();
|
|
3029
|
+
if (!isProviderId(provider)) {
|
|
3030
|
+
json(res, { error: "Unknown provider" }, 400);
|
|
3031
|
+
return true;
|
|
3032
|
+
}
|
|
3033
|
+
const envName = envNameForProvider(provider);
|
|
3034
|
+
const envValue = envName ? process.env[envName] : "";
|
|
3035
|
+
const hasEnvKey = typeof envValue === "string" && Boolean(envValue.trim());
|
|
3036
|
+
const hasStoredKey = Boolean(readStoredApiKey({ provider, trackerDir: trackerDataDir }));
|
|
3037
|
+
json(res, {
|
|
3038
|
+
provider,
|
|
3039
|
+
configured: hasEnvKey || hasStoredKey,
|
|
3040
|
+
source: hasEnvKey ? "env" : hasStoredKey ? "stored" : "none",
|
|
3041
|
+
});
|
|
3042
|
+
return true;
|
|
3043
|
+
}
|
|
3044
|
+
if (method === "POST" || method === "PUT") {
|
|
3045
|
+
if (!isAuthorizedLocalMutation(req)) {
|
|
3046
|
+
json(res, { ok: false, error: "Unauthorized" }, 401);
|
|
3047
|
+
return true;
|
|
3048
|
+
}
|
|
3049
|
+
let body;
|
|
3050
|
+
try {
|
|
3051
|
+
body = await readJsonBody(req);
|
|
3052
|
+
} catch {
|
|
3053
|
+
json(res, { ok: false, error: "Invalid JSON body" }, 400);
|
|
3054
|
+
return true;
|
|
3055
|
+
}
|
|
3056
|
+
const provider = typeof body?.provider === "string" ? body.provider.trim() : "";
|
|
3057
|
+
if (!isProviderId(provider)) {
|
|
3058
|
+
json(res, { ok: false, error: "Unknown provider" }, 400);
|
|
3059
|
+
return true;
|
|
3060
|
+
}
|
|
3061
|
+
if (!normalizeApiKey(body?.apiKey)) {
|
|
3062
|
+
json(res, { ok: false, error: "Invalid API key" }, 400);
|
|
3063
|
+
return true;
|
|
3064
|
+
}
|
|
3065
|
+
try {
|
|
3066
|
+
await writeStoredApiKey({ provider, trackerDir: trackerDataDir, apiKey: body.apiKey });
|
|
3067
|
+
} catch {
|
|
3068
|
+
json(res, { ok: false, error: "Could not save the API key" }, 500);
|
|
3069
|
+
return true;
|
|
3070
|
+
}
|
|
3071
|
+
// The in-memory aggregate would otherwise keep serving the previous
|
|
3072
|
+
// credential's row (or "not configured") until its TTL expires.
|
|
3073
|
+
try {
|
|
3074
|
+
const { resetUsageLimitsCache, resolveOpencodeGoLimitsCachePath } = require("./usage-limits");
|
|
3075
|
+
resetUsageLimitsCache({ invalidateInFlight: true });
|
|
3076
|
+
if (provider === "opencode-go") {
|
|
3077
|
+
// The disk snapshot is the stale-bars fallback; it must not
|
|
3078
|
+
// outlive the key that produced it.
|
|
3079
|
+
await fs.promises.unlink(resolveOpencodeGoLimitsCachePath({ trackerDir: trackerDataDir }));
|
|
3080
|
+
}
|
|
3081
|
+
} catch {
|
|
3082
|
+
/* cache invalidation is best effort; the stored key is authoritative */
|
|
3083
|
+
}
|
|
3084
|
+
json(res, { ok: true, configured: true, source: "stored" });
|
|
3085
|
+
return true;
|
|
3086
|
+
}
|
|
3087
|
+
json(res, { error: "Method Not Allowed" }, 405);
|
|
3088
|
+
return true;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
2981
3091
|
// --- cloud-sync preference mirror ---
|
|
2982
3092
|
// The dashboard's Settings → Account → Cloud sync toggle (a WebView
|
|
2983
3093
|
// localStorage flag) is mirrored here so the auth-unaware native popover can
|
|
@@ -3547,6 +3657,7 @@ function createLocalApiHandler({ queuePath, serverVersion = null, trackerDataDir
|
|
|
3547
3657
|
}
|
|
3548
3658
|
const data = await getUsageLimits({
|
|
3549
3659
|
home: os.homedir(),
|
|
3660
|
+
trackerDir: trackerDataDir,
|
|
3550
3661
|
env: process.env,
|
|
3551
3662
|
platform: process.platform,
|
|
3552
3663
|
// Punches through the Claude disk fresh-cache (but not the 429
|