tokentracker-cli 0.68.0 → 0.69.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.
Files changed (78) hide show
  1. package/README.ja.md +2 -0
  2. package/README.ko.md +2 -0
  3. package/README.md +2 -0
  4. package/README.zh-CN.md +2 -0
  5. package/dashboard/dist/assets/ActivityHeatmap-Bqy-meR1.js +42 -0
  6. package/dashboard/dist/assets/{Card--_I4rbLK.js → Card-DbzltTXg.js} +1 -1
  7. package/dashboard/dist/assets/CommunityStatsModal-CumafhRo.js +1 -0
  8. package/dashboard/dist/assets/ConfirmModal-CfmWYFAl.js +1 -0
  9. package/dashboard/dist/assets/DashboardPage-beIJMVvO.js +19 -0
  10. package/dashboard/dist/assets/DevicePage-Cb_cMnJj.js +1 -0
  11. package/dashboard/dist/assets/DialogClose-245t1wj3.js +1 -0
  12. package/dashboard/dist/assets/DialogDescription-CMG2f0Xr.js +1 -0
  13. package/dashboard/dist/assets/DialogTitle-C_S_W-qN.js +1 -0
  14. package/dashboard/dist/assets/{FadeIn-CJo3CY8P.js → FadeIn-9qSEABKW.js} +1 -1
  15. package/dashboard/dist/assets/{HeaderGithubStar-DlIkA0ge.js → HeaderGithubStar-C42NExcO.js} +1 -1
  16. package/dashboard/dist/assets/{IpCheckPage-D11sDL_F.js → IpCheckPage-C_LVvRK3.js} +3 -3
  17. package/dashboard/dist/assets/LandingPage-1ng5HTtU.js +4252 -0
  18. package/dashboard/dist/assets/LeaderboardAvatar-Dud2isij.js +1 -0
  19. package/dashboard/dist/assets/LeaderboardPage-BVLHknpa.js +2 -0
  20. package/dashboard/dist/assets/{LeaderboardProfileModal-BFCT2cD1.js → LeaderboardProfileModal-creqSCBC.js} +3 -3
  21. package/dashboard/dist/assets/{LeaderboardProfilePage-kYZ1Pk0J.js → LeaderboardProfilePage-CtCiW1ZR.js} +3 -3
  22. package/dashboard/dist/assets/{LimitsPage-wsAp4jGy.js → LimitsPage-DXhNfD6c.js} +3 -3
  23. package/dashboard/dist/assets/{LocalOnlyNotice-DH4MorD1.js → LocalOnlyNotice-CupEXgLA.js} +1 -1
  24. package/dashboard/dist/assets/LoginPage-Bvb6khA_.js +1 -0
  25. package/dashboard/dist/assets/PopoverPopup-1RwMOuZR.js +1 -0
  26. package/dashboard/dist/assets/ResetPasswordPage-C1t2TVSo.js +1 -0
  27. package/dashboard/dist/assets/Select-35dakfdf.js +1 -0
  28. package/dashboard/dist/assets/SelectItemText-Bn80bCzm.js +1 -0
  29. package/dashboard/dist/assets/SettingsPage-BtNI2EB5.js +1 -0
  30. package/dashboard/dist/assets/SkillsPage-DrtaOCt8.js +1 -0
  31. package/dashboard/dist/assets/WidgetsPage-DBP3CcAv.js +1 -0
  32. package/dashboard/dist/assets/{WrappedPage-CA9NmFM8.js → WrappedPage-Dxzewd0b.js} +1 -1
  33. package/dashboard/dist/assets/agent-logos-DXWdLSyr.js +1 -0
  34. package/dashboard/dist/assets/{arrow-up-right-DfeFj5sx.js → arrow-up-right-CAv_ofGl.js} +1 -1
  35. package/dashboard/dist/assets/check-BzPtb9x7.js +1 -0
  36. package/dashboard/dist/assets/copy-CkAl55Gc.js +1 -0
  37. package/dashboard/dist/assets/{download-CAPdZzcq.js → download-CHuHPQRG.js} +1 -1
  38. package/dashboard/dist/assets/format-tokens-BhF7ktma.js +1 -0
  39. package/dashboard/dist/assets/info-4RqgE-Sk.js +1 -0
  40. package/dashboard/dist/assets/main-CjU-7igC.css +1 -0
  41. package/dashboard/dist/assets/{main-QK2wZyt4.js → main-Cy1-zTjI.js} +69 -40
  42. package/dashboard/dist/assets/use-community-stats-BHARwgjJ.js +1 -0
  43. package/dashboard/dist/assets/{use-limits-display-prefs-CC1SjRQA.js → use-limits-display-prefs-D6TuBuXc.js} +1 -1
  44. package/dashboard/dist/assets/use-native-settings-8aQ6BEe6.js +1 -0
  45. package/dashboard/dist/assets/{use-ordered-list-DykwKTTq.js → use-ordered-list-B0XApham.js} +1 -1
  46. package/dashboard/dist/assets/{use-quality-per-dollar-pref-VMVZhzOm.js → use-quality-per-dollar-pref-DNa5uaGP.js} +1 -1
  47. package/dashboard/dist/assets/{use-usage-limits-CIwmIjBw.js → use-usage-limits-C1V3apqc.js} +1 -1
  48. package/dashboard/dist/assets/{useCurrency-Bm3doFX6.js → useCurrency-BEkNDMgc.js} +1 -1
  49. package/dashboard/dist/assets/useScrollLock-BRpWM_0H.js +1 -0
  50. package/dashboard/dist/brand-logos/deepseek.svg +1 -0
  51. package/dashboard/dist/index.html +2 -2
  52. package/dashboard/dist/share.html +2 -2
  53. package/package.json +1 -1
  54. package/src/commands/serve.js +32 -1
  55. package/src/lib/browser-auth.js +120 -20
  56. package/src/lib/cloud-account.js +8 -6
  57. package/src/lib/local-api.js +98 -56
  58. package/src/lib/pricing/seed-snapshot.json +1 -1
  59. package/dashboard/dist/assets/ActivityHeatmap-Cd928Ptz.js +0 -42
  60. package/dashboard/dist/assets/ConfirmModal-BJryA878.js +0 -1
  61. package/dashboard/dist/assets/DashboardPage-CikzR9-n.js +0 -19
  62. package/dashboard/dist/assets/DevicePage-Cucg8IJm.js +0 -1
  63. package/dashboard/dist/assets/DialogTitle-CBT-8O9t.js +0 -1
  64. package/dashboard/dist/assets/LandingPage-BR1-nUHp.js +0 -4415
  65. package/dashboard/dist/assets/LeaderboardAvatar-DbGYLk_s.js +0 -1
  66. package/dashboard/dist/assets/LeaderboardPage-BFPX67-5.js +0 -2
  67. package/dashboard/dist/assets/LoginPage-BNXijxzk.js +0 -1
  68. package/dashboard/dist/assets/PopoverPopup-Dqz11HEi.js +0 -1
  69. package/dashboard/dist/assets/ResetPasswordPage-CWvCPu5G.js +0 -1
  70. package/dashboard/dist/assets/Select-B1n0PW4j.js +0 -1
  71. package/dashboard/dist/assets/SelectItemText-BYGw7q1R.js +0 -1
  72. package/dashboard/dist/assets/SettingsPage-CCwxdKVg.js +0 -1
  73. package/dashboard/dist/assets/SkillsPage-ds_IoVAo.js +0 -1
  74. package/dashboard/dist/assets/WidgetsPage-DCI26iOU.js +0 -1
  75. package/dashboard/dist/assets/agent-logos-BASBNeBF.js +0 -1
  76. package/dashboard/dist/assets/main-B6BudUsC.css +0 -1
  77. package/dashboard/dist/assets/use-native-settings-BeV60g8D.js +0 -1
  78. package/dashboard/dist/assets/useScrollLock-DecJNW0u.js +0 -1
@@ -768,6 +768,8 @@ function createLocalApiHandler({ queuePath }) {
768
768
  const localAuthToken = crypto.randomBytes(24).toString("hex");
769
769
  const trackerDataDir = path.join(os.homedir(), ".tokentracker", "tracker");
770
770
  const cookiePath = path.join(trackerDataDir, "relay-cookies.json");
771
+ const localSyncDeviceTokenCache = new Map();
772
+ const localSyncDeviceTokenInflight = new Map();
771
773
 
772
774
  // Load persisted cookies on startup
773
775
  try {
@@ -941,65 +943,93 @@ function createLocalApiHandler({ queuePath }) {
941
943
  }
942
944
  }
943
945
 
944
- async function issueDeviceTokenForDrainSync(queuePathForMachineId) {
946
+ function localSyncDeviceTokenCacheKey(refreshToken, machineId, baseUrl) {
947
+ return `${baseUrl}\0${refreshToken}\0${machineId}`;
948
+ }
949
+
950
+ async function issueDeviceTokenForLocalSync(queuePathForMachineId, options = {}) {
945
951
  if (!getCloudSyncPref()) return null;
946
952
  const refreshToken = getRefreshTokenForCloud();
947
953
  if (!refreshToken) return null;
948
-
949
- const runtime = resolveRuntimeConfig();
950
- const baseUrl = runtime.baseUrl || DEFAULT_BASE_URL;
951
- const minted = await mintAccessToken({
952
- baseUrl,
953
- anonKey: runtime.anonKey,
954
- refreshToken,
955
- timeoutMs: runtime.httpTimeoutMs,
956
- });
957
- if (!minted?.accessToken) return null;
958
- if (minted.refreshToken) setRelayRefreshToken(minted.refreshToken);
959
- if (minted.csrfToken) setRelayCsrfToken(minted.csrfToken);
960
-
961
954
  const machineId = getOrCreateMachineId(queuePathForMachineId);
962
955
  if (!machineId) return null;
963
956
 
964
- const root = String(baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
965
- const headers = {
966
- "Content-Type": "application/json",
967
- Accept: "application/json",
968
- Authorization: `Bearer ${minted.accessToken}`,
969
- };
970
- if (runtime.anonKey) headers.apikey = runtime.anonKey;
957
+ const runtime = resolveRuntimeConfig();
958
+ const baseUrl =
959
+ normalizeRemoteHttpBaseUrl(options.baseUrl) ||
960
+ normalizeRemoteHttpBaseUrl(runtime.baseUrl) ||
961
+ normalizeRemoteHttpBaseUrl(DEFAULT_BASE_URL);
962
+ const cacheKey = localSyncDeviceTokenCacheKey(refreshToken, machineId, baseUrl);
963
+ const cachedToken = localSyncDeviceTokenCache.get(cacheKey);
964
+ if (cachedToken) return cachedToken;
965
+ const inflightToken = localSyncDeviceTokenInflight.get(cacheKey);
966
+ if (inflightToken) return inflightToken;
967
+
968
+ const issuePromise = (async () => {
969
+ const minted = await mintAccessToken({
970
+ baseUrl,
971
+ anonKey: runtime.anonKey,
972
+ refreshToken,
973
+ timeoutMs: runtime.httpTimeoutMs,
974
+ });
975
+ if (!minted?.accessToken) return null;
976
+ const rotatedRefreshToken =
977
+ typeof minted.refreshToken === "string" && minted.refreshToken.trim()
978
+ ? minted.refreshToken.trim()
979
+ : "";
980
+ if (rotatedRefreshToken) setRelayRefreshToken(rotatedRefreshToken);
981
+ if (minted.csrfToken) setRelayCsrfToken(minted.csrfToken);
982
+
983
+ const root = String(baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
984
+ const headers = {
985
+ "Content-Type": "application/json",
986
+ Accept: "application/json",
987
+ Authorization: `Bearer ${minted.accessToken}`,
988
+ };
989
+ if (runtime.anonKey) headers.apikey = runtime.anonKey;
971
990
 
972
- let timeoutId;
973
- const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
974
- if (controller && runtime.httpTimeoutMs > 0) {
975
- timeoutId = setTimeout(() => controller.abort(), runtime.httpTimeoutMs);
976
- }
991
+ let timeoutId;
992
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
993
+ if (controller && runtime.httpTimeoutMs > 0) {
994
+ timeoutId = setTimeout(() => controller.abort(), runtime.httpTimeoutMs);
995
+ }
977
996
 
978
- const dashboardPlatform =
979
- process.platform === "darwin" ? "MacIntel" :
980
- process.platform === "win32" ? "Win32" :
981
- process.platform === "linux" ? "Linux x86_64" :
982
- "web";
983
-
984
- const res = await fetch(`${root}/functions/tokentracker-device-token-issue`, {
985
- method: "POST",
986
- headers,
987
- signal: controller ? controller.signal : undefined,
988
- body: JSON.stringify({
989
- // 必须和 dashboard/src/lib/cloud-sync.ts 使用同一个设备身份。
990
- // 旧云端设备按 (platform, device_name) 认领;如果这里发明
991
- // local-sync 身份,会多出一个 active device,账户视图会把历史求和两次。
992
- device_name: `Token Tracker (dashboard) #${machineId.slice(0, 8)}`,
993
- platform: dashboardPlatform,
994
- machine_id: machineId,
995
- }),
996
- }).finally(() => {
997
- if (timeoutId) clearTimeout(timeoutId);
998
- });
999
- if (!res.ok) return null;
1000
- const data = await res.json().catch(() => null);
1001
- const token = typeof data?.token === "string" ? data.token.trim() : "";
1002
- return token || null;
997
+ const dashboardPlatform =
998
+ process.platform === "darwin" ? "MacIntel" :
999
+ process.platform === "win32" ? "Win32" :
1000
+ process.platform === "linux" ? "Linux x86_64" :
1001
+ "web";
1002
+
1003
+ const res = await fetch(`${root}/functions/tokentracker-device-token-issue`, {
1004
+ method: "POST",
1005
+ headers,
1006
+ signal: controller ? controller.signal : undefined,
1007
+ body: JSON.stringify({
1008
+ // 必须和 dashboard/src/lib/cloud-sync.ts 使用同一个设备身份。
1009
+ // 旧云端设备按 (platform, device_name) 认领;如果这里发明
1010
+ // local-sync 身份,会多出一个 active device,账户视图会把历史求和两次。
1011
+ device_name: `Token Tracker (dashboard) #${machineId.slice(0, 8)}`,
1012
+ platform: dashboardPlatform,
1013
+ machine_id: machineId,
1014
+ }),
1015
+ }).finally(() => {
1016
+ if (timeoutId) clearTimeout(timeoutId);
1017
+ });
1018
+ if (!res.ok) return null;
1019
+ const data = await res.json().catch(() => null);
1020
+ const token = typeof data?.token === "string" ? data.token.trim() : "";
1021
+ if (token) {
1022
+ const activeRefreshToken = rotatedRefreshToken || refreshToken;
1023
+ localSyncDeviceTokenCache.set(localSyncDeviceTokenCacheKey(activeRefreshToken, machineId, baseUrl), token);
1024
+ }
1025
+ return token || null;
1026
+ })();
1027
+ localSyncDeviceTokenInflight.set(cacheKey, issuePromise);
1028
+ try {
1029
+ return await issuePromise;
1030
+ } finally {
1031
+ localSyncDeviceTokenInflight.delete(cacheKey);
1032
+ }
1003
1033
  }
1004
1034
 
1005
1035
  // Returns "served" when the cross-device aggregate was written to `res`, or
@@ -1462,6 +1492,7 @@ function createLocalApiHandler({ queuePath }) {
1462
1492
  if (typeof body.deviceToken === "string" && body.deviceToken.trim()) {
1463
1493
  extraEnv.TOKENTRACKER_DEVICE_TOKEN = body.deviceToken.trim();
1464
1494
  }
1495
+ let localSyncBaseUrl = null;
1465
1496
  if (body.insforgeBaseUrl != null) {
1466
1497
  const allowedBaseUrl = resolveAllowedInsforgeBaseUrl(body.insforgeBaseUrl);
1467
1498
  if (!allowedBaseUrl) {
@@ -1469,14 +1500,25 @@ function createLocalApiHandler({ queuePath }) {
1469
1500
  return true;
1470
1501
  }
1471
1502
  extraEnv.TOKENTRACKER_INSFORGE_BASE_URL = allowedBaseUrl;
1503
+ localSyncBaseUrl = allowedBaseUrl;
1472
1504
  }
1473
- if (drain && !extraEnv.TOKENTRACKER_DEVICE_TOKEN && getCloudSyncPref() && getRefreshTokenForCloud()) {
1474
- const issuedToken = await issueDeviceTokenForDrainSync(qp).catch(() => null);
1505
+ if (!extraEnv.TOKENTRACKER_DEVICE_TOKEN && getCloudSyncPref() && getRefreshTokenForCloud()) {
1506
+ let issuedToken = null;
1507
+ try {
1508
+ issuedToken = await issueDeviceTokenForLocalSync(qp, { baseUrl: localSyncBaseUrl });
1509
+ } catch (e) {
1510
+ if (resolveRuntimeConfig().debug) {
1511
+ console.warn("[LocalAPI] local sync device token issue failed:", e?.message || e);
1512
+ }
1513
+ }
1475
1514
  if (!issuedToken) {
1476
- json(res, { ok: false, error: "Unable to issue cloud device token for drain sync" }, 502);
1477
- return true;
1515
+ if (drain) {
1516
+ json(res, { ok: false, error: "Unable to issue cloud device token for local sync" }, 502);
1517
+ return true;
1518
+ }
1519
+ } else {
1520
+ extraEnv.TOKENTRACKER_DEVICE_TOKEN = issuedToken;
1478
1521
  }
1479
- extraEnv.TOKENTRACKER_DEVICE_TOKEN = issuedToken;
1480
1522
  }
1481
1523
  const result = await runSyncCommand(extraEnv, { drain });
1482
1524
  try {