tokentracker-cli 1.4.9 → 1.4.11

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 (128) hide show
  1. package/README.de.md +3 -3
  2. package/README.ja.md +3 -3
  3. package/README.ko.md +3 -3
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +3 -3
  6. package/dashboard/dist/assets/{AchievementBadge-_dRLgxnp.js → AchievementBadge-DALWiLQQ.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-DtvlZLlv.js → AchievementsPage-CjWobmn1.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-OgzKkbes.js → AchievementsSection-CTryGWII.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-D4Rlm_um.js → BadgeDetailModal-D4lMxewA.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-errJ5IvB.js → BotAnimated-CsLNf9WH.js} +1 -1
  11. package/dashboard/dist/assets/{Card-esELVQcD.js → Card-BOnNTrmF.js} +1 -1
  12. package/dashboard/dist/assets/{CloudCheckoutPage-3sH4Dbx9.js → CloudCheckoutPage-CrY15xqT.js} +1 -1
  13. package/dashboard/dist/assets/{CloudPage-Bi_vgiqE.js → CloudPage-hNSCMzVG.js} +2 -2
  14. package/dashboard/dist/assets/{CommandPalette-I0aze2EJ.js → CommandPalette-CE4UfPXk.js} +1 -1
  15. package/dashboard/dist/assets/{CommunityStatsModal-CEn2g4J9.js → CommunityStatsModal-CLUegC6z.js} +1 -1
  16. package/dashboard/dist/assets/{ConfirmModal-D7SfFCaf.js → ConfirmModal-D3wwfiYB.js} +1 -1
  17. package/dashboard/dist/assets/{Controls-Dyb5Vmk_.js → Controls-B_ta_8zw.js} +1 -1
  18. package/dashboard/dist/assets/{DashboardPage-CMHX8zDq.js → DashboardPage-RevVPZFt.js} +1 -1
  19. package/dashboard/dist/assets/{DateRangePopover-CgmxsHVR.js → DateRangePopover-CYud1FKu.js} +1 -1
  20. package/dashboard/dist/assets/DevicePage-D_eSLfM3.js +1 -0
  21. package/dashboard/dist/assets/{DialogClose-DzR109OA.js → DialogClose-Bu1ct4DA.js} +1 -1
  22. package/dashboard/dist/assets/DialogDescription-GUHQVNGf.js +1 -0
  23. package/dashboard/dist/assets/{DialogTitle-BLH1MgOX.js → DialogTitle-BJxM52J1.js} +1 -1
  24. package/dashboard/dist/assets/{ElectricLogo-DcpBcJIy.js → ElectricLogo-DYVZebgD.js} +1 -1
  25. package/dashboard/dist/assets/{FadeIn-CBAXe_zb.js → FadeIn-CVaq5ALh.js} +1 -1
  26. package/dashboard/dist/assets/{HeaderGithubStar-BZjQ3uJo.js → HeaderGithubStar-BCwYSguq.js} +1 -1
  27. package/dashboard/dist/assets/{HoverTooltip-Bq4HuWOZ.js → HoverTooltip-YsG-TdOX.js} +1 -1
  28. package/dashboard/dist/assets/{IpCheckPage-Oiah1-YB.js → IpCheckPage-C0qiyZXr.js} +1 -1
  29. package/dashboard/dist/assets/{LandingPage-Cc1FfkWg.js → LandingPage-BlRJWwvx.js} +2 -2
  30. package/dashboard/dist/assets/{LeaderboardPage-Bc2j_DQY.js → LeaderboardPage-DcCCKL2q.js} +2 -2
  31. package/dashboard/dist/assets/{LeaderboardProBadge-w1olVHVp.js → LeaderboardProBadge-I_oW0KCc.js} +1 -1
  32. package/dashboard/dist/assets/{LeaderboardProfileModal-mgG7mdl8.js → LeaderboardProfileModal-DM5Vc7MB.js} +3 -3
  33. package/dashboard/dist/assets/{LeaderboardProfilePage-DqxyooT5.js → LeaderboardProfilePage-DnYBBGNe.js} +1 -1
  34. package/dashboard/dist/assets/LimitsPage-Cyy3PI8p.js +6 -0
  35. package/dashboard/dist/assets/{LocalOnlyNotice-DZLgI75v.js → LocalOnlyNotice-083BoEX9.js} +1 -1
  36. package/dashboard/dist/assets/{LoginCard-DVNU_QtS.js → LoginCard-CN1db2Rj.js} +1 -1
  37. package/dashboard/dist/assets/{LoginModal-yxi5pCwC.js → LoginModal-Dfi7_ory.js} +1 -1
  38. package/dashboard/dist/assets/{LoginPage-CW9pu4mf.js → LoginPage-BzMdiYlx.js} +1 -1
  39. package/dashboard/dist/assets/{PetPage-DwZsq4nC.js → PetPage-CsE95lfJ.js} +2 -2
  40. package/dashboard/dist/assets/{PopoverPopup-DYGmmoD8.js → PopoverPopup-U7DTTTE0.js} +1 -1
  41. package/dashboard/dist/assets/{ProviderIcon-BKfj89GN.js → ProviderIcon-CnLfT5d9.js} +1 -1
  42. package/dashboard/dist/assets/{ResetPasswordPage-gQdgCp9s.js → ResetPasswordPage-De2RzCj-.js} +1 -1
  43. package/dashboard/dist/assets/{Select-DvSR56Lj.js → Select-BS5MWHdB.js} +1 -1
  44. package/dashboard/dist/assets/{SelectItemText-DXo30s3O.js → SelectItemText-D3i379K-.js} +1 -1
  45. package/dashboard/dist/assets/{SelfHostPage-jA51Nckw.js → SelfHostPage-CTL0dnp5.js} +1 -1
  46. package/dashboard/dist/assets/{ServiceStatusPage-DEkRRPx8.js → ServiceStatusPage-BmEvOk3B.js} +1 -1
  47. package/dashboard/dist/assets/{SessionsPage-J7hvGPCj.js → SessionsPage-DIog3JA_.js} +2 -2
  48. package/dashboard/dist/assets/{SettingsPage-ewH1Mzsm.js → SettingsPage-Cgpiwh-C.js} +3 -3
  49. package/dashboard/dist/assets/{SkillsPage-BaeBHhU7.js → SkillsPage-DpagshiB.js} +1 -1
  50. package/dashboard/dist/assets/{TokenGalaxy-D52pHpzj.js → TokenGalaxy-Do1U_-E3.js} +1 -1
  51. package/dashboard/dist/assets/{TooltipPopup-mLpESQcv.js → TooltipPopup-MOAnEqf3.js} +1 -1
  52. package/dashboard/dist/assets/{TrendMonitor-ErowR5GE.js → TrendMonitor-B55RUbQm.js} +1 -1
  53. package/dashboard/dist/assets/{WidgetsPage-Bf1uuKE7.js → WidgetsPage-CehbGk-6.js} +1 -1
  54. package/dashboard/dist/assets/{WrappedPage-C8qacvpR.js → WrappedPage-Cqt-U0Zx.js} +1 -1
  55. package/dashboard/dist/assets/__vite-browser-external-C9a2vCBN.css +1 -0
  56. package/dashboard/dist/assets/{__vite-browser-external-CIpYjfAT.js → __vite-browser-external-CR0vo15z.js} +3 -3
  57. package/dashboard/dist/assets/{agent-logos-tmdKvCcK.js → agent-logos-DuW-DWAl.js} +1 -1
  58. package/dashboard/dist/assets/{arrow-left-CRKi420c.js → arrow-left-DIvQjuji.js} +1 -1
  59. package/dashboard/dist/assets/{arrow-right-C_o_iQbK.js → arrow-right-BURLycHb.js} +1 -1
  60. package/dashboard/dist/assets/{arrow-up-right-N5YqKKnI.js → arrow-up-right-Bl1QMMkY.js} +1 -1
  61. package/dashboard/dist/assets/{calendar-days-BJh4FDj9.js → calendar-days-CmIWJC3A.js} +1 -1
  62. package/dashboard/dist/assets/{check-BfHGb92g.js → check-DB2ZTWor.js} +1 -1
  63. package/dashboard/dist/assets/{chevron-down-D1pO8Xgc.js → chevron-down-BflN3NJt.js} +1 -1
  64. package/dashboard/dist/assets/{cloud-checkout-Ds13TmYz.js → cloud-checkout-BugQolSC.js} +1 -1
  65. package/dashboard/dist/assets/{composite-DDEsQKPD.js → composite-DJsqXsjA.js} +1 -1
  66. package/dashboard/dist/assets/{copy-CJTosYLe.js → copy-CtB-GPN2.js} +1 -1
  67. package/dashboard/dist/assets/{download-Bkqx3c3y.js → download-x5Botpqc.js} +1 -1
  68. package/dashboard/dist/assets/{external-link-IYUo5LcK.js → external-link-DH9sRkJW.js} +1 -1
  69. package/dashboard/dist/assets/{flame-BD_hiMfM.js → flame-DEcsokoL.js} +1 -1
  70. package/dashboard/dist/assets/{format-tokens-DxwbhPag.js → format-tokens-BH0jc1MG.js} +1 -1
  71. package/dashboard/dist/assets/{gift-DLzFdL-1.js → gift-BZk9muk9.js} +1 -1
  72. package/dashboard/dist/assets/{icons-CbrWeFPS.js → icons-BMf9-bfS.js} +1 -1
  73. package/dashboard/dist/assets/{index-Dj1QnoPF.js → index-BEtrik3j.js} +1 -1
  74. package/dashboard/dist/assets/{index-DfzCE-FL.js → index-Cm-9apOR.js} +1 -1
  75. package/dashboard/dist/assets/{index-AVEWo-aY.js → index-TErx-xPb.js} +1 -1
  76. package/dashboard/dist/assets/{index-3xGpnZnP.js → index-axJYocSc.js} +1 -1
  77. package/dashboard/dist/assets/{info-odlIlCnm.js → info-DWdPAxu8.js} +1 -1
  78. package/dashboard/dist/assets/{link-2-Bt28Ey9r.js → link-2-c3ZZd-w-.js} +1 -1
  79. package/dashboard/dist/assets/{loader-circle-lGnIhIFS.js → loader-circle-BKdtxfky.js} +1 -1
  80. package/dashboard/dist/assets/{local-usage-transfer-DSS0WaL-.js → local-usage-transfer-BFTJyQl3.js} +1 -1
  81. package/dashboard/dist/assets/{modifiers.esm-CitRa_Z1.js → modifiers.esm-C3yIfOiP.js} +1 -1
  82. package/dashboard/dist/assets/{monitor-smartphone-1T0GFcOz.js → monitor-smartphone-DbdL7Mei.js} +1 -1
  83. package/dashboard/dist/assets/{play-d6YYOmED.js → play-qtWt2ArI.js} +1 -1
  84. package/dashboard/dist/assets/{plus-GrGV9Fkm.js → plus-C_-__5vi.js} +1 -1
  85. package/dashboard/dist/assets/{provider-display-D1v3cgDm.js → provider-display-IKHnhvaj.js} +1 -1
  86. package/dashboard/dist/assets/{react-DYCAyQbo.js → react-H6DfeMUm.js} +1 -1
  87. package/dashboard/dist/assets/{safePolygon-iPu060Qe.js → safePolygon-OEQJQTVa.js} +1 -1
  88. package/dashboard/dist/assets/{search-AQhOTwzm.js → search-MZE76qNU.js} +1 -1
  89. package/dashboard/dist/assets/{server-uYTbWPMd.js → server-CUWYLMjG.js} +1 -1
  90. package/dashboard/dist/assets/{shield-check-DB1NVCDf.js → shield-check-lA0Ja0RB.js} +1 -1
  91. package/dashboard/dist/assets/{skills-api-D7X10n0t.js → skills-api-CJJIPvSe.js} +1 -1
  92. package/dashboard/dist/assets/{sortable.esm-B13KhrCd.js → sortable.esm-DTnvt1IN.js} +1 -1
  93. package/dashboard/dist/assets/{store-D6XHiq8o.js → store-CC0YKrxA.js} +1 -1
  94. package/dashboard/dist/assets/{terminal-t37F8qyF.js → terminal-yc2l_-3m.js} +1 -1
  95. package/dashboard/dist/assets/{trash-CDHRwGsy.js → trash-Dl9f-o2h.js} +1 -1
  96. package/dashboard/dist/assets/{use-community-stats--kaGg0EW.js → use-community-stats-CZCKWCO7.js} +1 -1
  97. package/dashboard/dist/assets/{use-latest-request-guard-ChyODWU3.js → use-latest-request-guard-DTrOu0gD.js} +1 -1
  98. package/dashboard/dist/assets/use-limits-display-prefs-DGqP231e.js +1 -0
  99. package/dashboard/dist/assets/{use-native-settings-i3O4GTY1.js → use-native-settings-BEATnxB6.js} +1 -1
  100. package/dashboard/dist/assets/{use-ordered-list-BtewEEX_.js → use-ordered-list-C_HTFP5C.js} +1 -1
  101. package/dashboard/dist/assets/use-reduced-motion-CfCWjE-a.js +1 -0
  102. package/dashboard/dist/assets/{use-transform-NBAyuphu.js → use-transform-DM0QGq_O.js} +1 -1
  103. package/dashboard/dist/assets/use-usage-limits-BnjBG2fM.js +1 -0
  104. package/dashboard/dist/assets/{useCurrency-BdsxkAID.js → useCurrency-xQrRFokb.js} +1 -1
  105. package/dashboard/dist/assets/{usePositioner-BAN79miB.js → usePositioner-DkVdsypM.js} +1 -1
  106. package/dashboard/dist/assets/{useTokenFormat-CGEO_mX1.js → useTokenFormat-CvA_kIPe.js} +1 -1
  107. package/dashboard/dist/assets/{zap-BDHWp-gL.js → zap-BJNS4i_u.js} +1 -1
  108. package/dashboard/dist/index.html +5 -5
  109. package/dashboard/dist/ip-check.html +2 -2
  110. package/dashboard/dist/leaderboard.html +2 -2
  111. package/dashboard/dist/share.html +2 -2
  112. package/package.json +2 -2
  113. package/src/lib/grok-limits.js +1 -0
  114. package/src/lib/omp-session.js +480 -0
  115. package/src/lib/pricing/curated-overrides.json +35 -0
  116. package/src/lib/pricing/matcher.js +44 -15
  117. package/src/lib/pricing/seed-snapshot.json +1 -1
  118. package/src/lib/session-analytics.js +54 -8
  119. package/src/lib/usage-limits.js +20 -8
  120. package/src/lib/workbuddy-ipc.js +191 -0
  121. package/src/lib/workbuddy-limits.js +564 -0
  122. package/dashboard/dist/assets/DevicePage-9R2MfK2Z.js +0 -1
  123. package/dashboard/dist/assets/DialogDescription-CdNjUDLZ.js +0 -1
  124. package/dashboard/dist/assets/LimitsPage-BIvK0GFj.js +0 -6
  125. package/dashboard/dist/assets/__vite-browser-external-CnVnsbqV.css +0 -1
  126. package/dashboard/dist/assets/use-limits-display-prefs-katFAxOA.js +0 -1
  127. package/dashboard/dist/assets/use-reduced-motion-DPacKl9n.js +0 -1
  128. package/dashboard/dist/assets/use-usage-limits-hsv-93TF.js +0 -1
@@ -0,0 +1,564 @@
1
+ // WorkBuddy subscription-plan quota.
2
+ //
3
+ // The desktop app already reads this from its billing meter. China
4
+ // (WorkBuddy, ~/.workbuddy) and international (WorkBuddy AI, ~/.workbuddy-ai)
5
+ // share the path shape and differ by host:
6
+ //
7
+ // POST {origin}/billing/meter/get-user-resource-summary
8
+ // POST {origin}/billing/meter/get-user-resource-paid-packages
9
+ // POST {origin}/billing/meter/get-user-resource-free-packages
10
+ //
11
+ // Prefer each edition's wbipc/endpoint.json -> wb.request/http.fetch proxy.
12
+ // WorkBuddy adds its own credentials, including encrypted desktop sessions.
13
+ // Legacy plaintext credentials remain a fallback when no local IPC exists.
14
+ //
15
+ // Only the subscription plan package is returned — the row the app calls
16
+ // 套餐基础积分, with that package's cycle end as the reset. Reward grants
17
+ // (活动 / 成长计划 / bonus / extra / gift) are dropped. Display names are
18
+ // English regardless of the backend's localized package names.
19
+
20
+ const fs = require("node:fs");
21
+ const path = require("node:path");
22
+ const { resolveWorkbuddyHome } = require("./rollout");
23
+ const { UNAVAILABLE, readWorkbuddyIpcDiscovery, openWorkbuddyIpc } = require("./workbuddy-ipc");
24
+
25
+ const PLAN_PRIORITY = Object.freeze({
26
+ TCACA_code_027_0FCGVA6vSa: 4, // flagship
27
+ TCACA_code_026_BaESVICNoi: 3, // advanced
28
+ TCACA_code_002_AkiJS3ZHF5: 2, // pro monthly
29
+ TCACA_code_005_maRGyrHhw1: 2, // pro monthly plus
30
+ TCACA_code_003_FAnt7lcmRT: 2, // pro yearly
31
+ TCACA_code_023_4xbGhMrE6q: 1, // youth
32
+ TCACA_code_035_ArVxJcGDsm: 0, // international monthly
33
+ TCACA_code_039_KRcQj7wUat: 0, // trial monthly
34
+ TCACA_code_040_mi9rCYg46x: 0, // trial yearly
35
+ TCACA_code_008_cfWoLwvjU4: 0, // China monthly trial
36
+ });
37
+
38
+ // Verbatim English edition labels from WorkBuddy Desktop 5.8.3's
39
+ // renderer/assets/edition-config-BLwrE-d8.js (getEditionConfigMap), with the
40
+ // package -> edition mapping in common-DRZ6FwkK.js (resolveEditionFromResources).
41
+ // These are official UI fallbacks, not English names supplied by the CN API.
42
+ const PLAN_LABELS = Object.freeze({
43
+ TCACA_code_027_0FCGVA6vSa: "Ultimate",
44
+ TCACA_code_026_BaESVICNoi: "Advanced",
45
+ TCACA_code_002_AkiJS3ZHF5: "STANDARD",
46
+ TCACA_code_005_maRGyrHhw1: "STANDARD",
47
+ TCACA_code_003_FAnt7lcmRT: "STANDARD",
48
+ TCACA_code_023_4xbGhMrE6q: "Youth",
49
+ TCACA_code_035_ArVxJcGDsm: "Free",
50
+ TCACA_code_039_KRcQj7wUat: "STANDARD TRIAL",
51
+ TCACA_code_040_mi9rCYg46x: "STANDARD TRIAL",
52
+ TCACA_code_008_cfWoLwvjU4: "Free",
53
+ });
54
+
55
+ // Platform reward credits. Never selected, even when the summary names one
56
+ // as a subscription package.
57
+ const REWARD_CODES = new Set([
58
+ "TCACA_code_001_PqouKr6QWV", // free, not the plan
59
+ "TCACA_code_006_DbXS0lrypC", // gift
60
+ "TCACA_code_007_nzdH5h4Nl0", // activity / 官方活动
61
+ "TCACA_code_009_0XmEQc2xOf", // extra credit package
62
+ "TCACA_code_028_NtpWi0jzXs", // bonus
63
+ "TCACA_code_029_6wCGEWquYy",
64
+ "TCACA_code_030_BjSt89qTvr",
65
+ "TCACA_code_036_lupO5WgNdG", // international extra
66
+ "TCACA_code_037_WxOD3MpI2o", // international bonus
67
+ "TCACA_code_038_OhvqZtiPKr", // extra
68
+ ]);
69
+
70
+ const VARIANTS = Object.freeze({
71
+ workbuddy: {
72
+ id: "workbuddy",
73
+ source: "workbuddy",
74
+ envToken: "WORKBUDDY_ACCESS_TOKEN",
75
+ defaultOrigin: "https://www.workbuddy.cn",
76
+ allowedHostSuffixes: ["workbuddy.cn", "copilot.tencent.com", "wb.tencentbuddy.com"],
77
+ authFileNames: ["workbuddy-desktop.info"],
78
+ brand: "WorkBuddy",
79
+ },
80
+ workbuddyAi: {
81
+ id: "workbuddyAi",
82
+ source: "workbuddy-ai",
83
+ envToken: "WORKBUDDY_AI_ACCESS_TOKEN",
84
+ defaultOrigin: "https://www.workbuddy.ai",
85
+ allowedHostSuffixes: ["workbuddy.ai"],
86
+ authFileNames: ["workbuddy-ai.info", "workbuddy-desktop.info"],
87
+ brand: "WorkBuddy AI",
88
+ },
89
+ });
90
+
91
+ function isNonEmptyString(value) {
92
+ return typeof value === "string" && value.trim().length > 0;
93
+ }
94
+
95
+ function hostOf(value) {
96
+ if (!isNonEmptyString(value)) return "";
97
+ const trimmed = value.trim();
98
+ try {
99
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
100
+ return new URL(withScheme).hostname.toLowerCase();
101
+ } catch {
102
+ return "";
103
+ }
104
+ }
105
+
106
+ function hostAllowed(hostname, suffixes) {
107
+ if (!hostname) return false;
108
+ return suffixes.some((suffix) => hostname === suffix || hostname.endsWith(`.${suffix}`));
109
+ }
110
+
111
+ function originAllowed(origin, variant) {
112
+ return hostAllowed(hostOf(origin), variant.allowedHostSuffixes);
113
+ }
114
+
115
+ function toCount(value) {
116
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
117
+ if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) {
118
+ const n = Number(value);
119
+ return n >= 0 ? n : null;
120
+ }
121
+ return null;
122
+ }
123
+
124
+ function firstCount(row, keys) {
125
+ for (const key of keys) {
126
+ const n = toCount(row?.[key]);
127
+ if (n !== null) return n;
128
+ }
129
+ return null;
130
+ }
131
+
132
+ function firstString(row, keys) {
133
+ for (const key of keys) {
134
+ if (isNonEmptyString(row?.[key])) return row[key].trim();
135
+ }
136
+ return "";
137
+ }
138
+
139
+ function normalizeResetAt(value) {
140
+ if (value === null || value === undefined || value === "") return null;
141
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
142
+ const ms = value > 10_000_000_000 ? value : value * 1000;
143
+ const date = new Date(ms);
144
+ return Number.isFinite(date.getTime()) ? date.toISOString() : null;
145
+ }
146
+ const text = String(value).trim();
147
+ if (!text) return null;
148
+ // "2026-10-31 23:59:59" is the cycle boundary the app shows. Without a
149
+ // zone, Date parses the T form as local time, matching the desktop app.
150
+ const normalized = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(text)
151
+ ? text.replace(" ", "T")
152
+ : text;
153
+ const parsed = Date.parse(normalized);
154
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
155
+ }
156
+
157
+ function clampPercent(value) {
158
+ if (!Number.isFinite(value)) return null;
159
+ if (value <= 0) return 0;
160
+ if (value >= 100) return 100;
161
+ return value;
162
+ }
163
+
164
+ function normalizePackageRow(row) {
165
+ if (!row || typeof row !== "object") return null;
166
+ const code = firstString(row, ["PackageCode", "packageCode", "package_code"]);
167
+ if (!code) return null;
168
+ const total = firstCount(row, [
169
+ "CycleTotalCapacity",
170
+ "cycleTotal",
171
+ "CycleCapacitySizePrecise",
172
+ "total",
173
+ ]);
174
+ const remain = firstCount(row, [
175
+ "CycleRemainCapacity",
176
+ "cycleRemain",
177
+ "CycleCapacityRemainPrecise",
178
+ "left",
179
+ ]);
180
+ const used = firstCount(row, ["CycleUsedCapacity", "cycleUsed", "used"]);
181
+ return {
182
+ code,
183
+ name: firstString(row, ["PackageName", "packageName", "name"]),
184
+ total,
185
+ remain,
186
+ used,
187
+ cycleEnd: firstString(row, ["CycleEndTime", "cycleEnd", "cycle_end"])
188
+ || firstString(row, ["DeductionEndTime", "expireAt"]),
189
+ };
190
+ }
191
+
192
+ function unwrapBillingBody(payload) {
193
+ if (!payload || typeof payload !== "object") return null;
194
+ const code = payload.code ?? payload.Code;
195
+ if (code !== undefined && code !== 0 && code !== "0") return null;
196
+ const data = payload.data ?? payload.Data;
197
+ if (data && typeof data === "object") return data;
198
+ return payload;
199
+ }
200
+
201
+ function collectPackageRows(payload) {
202
+ const data = unwrapBillingBody(payload);
203
+ if (!data) return [];
204
+ const lists = [];
205
+ for (const key of ["Packages", "packages", "Accounts", "accounts", "Resources", "resources"]) {
206
+ if (Array.isArray(data[key])) lists.push(data[key]);
207
+ }
208
+ if (Array.isArray(data)) lists.push(data);
209
+ const rows = [];
210
+ for (const list of lists) {
211
+ for (const item of list) {
212
+ const row = normalizePackageRow(item);
213
+ if (row) rows.push(row);
214
+ }
215
+ }
216
+ return rows;
217
+ }
218
+
219
+ function readSubscriptionIdentity(payload) {
220
+ const data = unwrapBillingBody(payload);
221
+ if (!data) return { code: "", name: "" };
222
+ return {
223
+ code: firstString(data, ["SubscriptionPackageCode", "subscriptionPackageCode"]),
224
+ name: firstString(data, ["SubscriptionPackageName", "subscriptionPackageName"]),
225
+ };
226
+ }
227
+
228
+ function mergePackageRows(rows) {
229
+ const byCode = new Map();
230
+ for (const row of rows) {
231
+ const prev = byCode.get(row.code);
232
+ if (!prev) {
233
+ byCode.set(row.code, { ...row });
234
+ continue;
235
+ }
236
+ byCode.set(row.code, {
237
+ code: row.code,
238
+ name: row.name || prev.name,
239
+ total: row.total ?? prev.total,
240
+ remain: row.remain ?? prev.remain,
241
+ used: row.used ?? prev.used,
242
+ cycleEnd: row.cycleEnd || prev.cycleEnd,
243
+ });
244
+ }
245
+ return [...byCode.values()];
246
+ }
247
+
248
+ function isRewardCode(code) {
249
+ return REWARD_CODES.has(code);
250
+ }
251
+
252
+ function isSelectablePlan(row, subscriptionPackageCode) {
253
+ if (!row || isRewardCode(row.code)) return false;
254
+ if (Object.prototype.hasOwnProperty.call(PLAN_PRIORITY, row.code)) return true;
255
+ return Boolean(subscriptionPackageCode) && row.code === subscriptionPackageCode;
256
+ }
257
+
258
+ function planScore(row) {
259
+ const priority = PLAN_PRIORITY[row.code] ?? 0;
260
+ const live = row.total > 0 && row.remain > 0 ? 1 : 0;
261
+ const total = Number.isFinite(row.total) ? row.total : 0;
262
+ return priority * 1_000_000_000 + live * 1_000_000 + total;
263
+ }
264
+
265
+ function planWindow(row) {
266
+ const total = row.total;
267
+ const remain = row.remain;
268
+ const used = row.used ?? (total !== null && remain !== null ? Math.max(0, total - remain) : null);
269
+ if (total === null || total <= 0 || used === null) return null;
270
+ const usedPercent = clampPercent((used / total) * 100);
271
+ if (usedPercent === null) return null;
272
+ return {
273
+ used_percent: usedPercent,
274
+ reset_at: normalizeResetAt(row.cycleEnd),
275
+ used_credits: used,
276
+ limit_credits: total,
277
+ remaining_credits: remain ?? Math.max(0, total - used),
278
+ };
279
+ }
280
+
281
+ // One product row, one account's balance. Prefer a usable quota; when both
282
+ // editions are connected China wins deterministically. Never add their pools.
283
+ function selectWorkbuddyLimits(china, international) {
284
+ const editions = [china, international].filter(Boolean);
285
+ const usable = (value) => value.configured === true && !value.error;
286
+ return editions.find((value) => usable(value) && value.primary_window)
287
+ || editions.find(usable)
288
+ || editions.find((value) => value.configured === true)
289
+ || { configured: false };
290
+ }
291
+
292
+ // Pick the one subscription-plan package. Reward rows are ignored. When
293
+ // several plan packages exist, the highest tier that still has credit wins,
294
+ // matching the desktop app's pickActivePlanBase.
295
+ function selectWorkbuddyPlanQuota(rows, identity = {}, variantId = "workbuddy") {
296
+ const merged = mergePackageRows(rows.filter(Boolean));
297
+ const subscriptionPackageCode = identity.code || "";
298
+ const candidates = merged.filter((row) => isSelectablePlan(row, subscriptionPackageCode));
299
+ if (candidates.length === 0) return null;
300
+ candidates.sort((a, b) => planScore(b) - planScore(a));
301
+ const chosen = candidates[0];
302
+ const window = planWindow(chosen);
303
+ if (!window) return null;
304
+ const planLabel = englishPlanLabel(chosen, identity, variantId);
305
+ return {
306
+ plan_label: planLabel || null,
307
+ primary_window: window,
308
+ package_code: chosen.code,
309
+ };
310
+ }
311
+
312
+ function englishPlanLabel(row, identity, variantId) {
313
+ // An actual English subscription/package name takes precedence over the
314
+ // client's edition fallback. Unknown localized tiers stay unnamed.
315
+ for (const name of [identity.name, row.name]) {
316
+ if (typeof name === "string" && /^[\x20-\x7e]+$/.test(name.trim())
317
+ && /[a-z]/i.test(name) && !/^TCACA_code_/i.test(name.trim())) return name.trim();
318
+ }
319
+ const known = PLAN_LABELS[row.code];
320
+ if (variantId === "workbuddyAi") {
321
+ if (known === "STANDARD") return "Pro";
322
+ if (known === "STANDARD TRIAL") return "Pro-Trial";
323
+ }
324
+ return known || null;
325
+ }
326
+
327
+ function variantById(id) {
328
+ return VARIANTS[id] || null;
329
+ }
330
+
331
+ function directoryExists(dir) {
332
+ if (!dir) return false;
333
+ try {
334
+ return fs.statSync(dir).isDirectory();
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+
340
+ function authSearchDirs(env, workbuddyHome) {
341
+ const home = isNonEmptyString(env?.HOME) ? env.HOME.trim() : "";
342
+ const dirs = [];
343
+ if (home && process.platform === "darwin") {
344
+ dirs.push(path.join(home, "Library", "Application Support", "CodeBuddyExtension", "Data", "Public", "auth"));
345
+ } else if (process.platform === "win32") {
346
+ const appData = isNonEmptyString(env?.APPDATA) ? env.APPDATA.trim() : (home ? path.join(home, "AppData", "Roaming") : "");
347
+ if (appData) dirs.push(path.join(appData, "CodeBuddyExtension", "Data", "Public", "auth"));
348
+ } else if (home) {
349
+ dirs.push(path.join(home, ".config", "CodeBuddyExtension", "Data", "Public", "auth"));
350
+ }
351
+ if (workbuddyHome) dirs.push(path.join(workbuddyHome, "auth"));
352
+ return dirs;
353
+ }
354
+
355
+ function readAuthFile(filePath) {
356
+ let parsed;
357
+ try {
358
+ parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
359
+ } catch {
360
+ return null;
361
+ }
362
+ const auth = parsed?.auth;
363
+ if (!auth || typeof auth !== "object") return null;
364
+ const domain = isNonEmptyString(auth.domain) ? auth.domain.trim() : "";
365
+ const token = auth.accessToken;
366
+ if (isNonEmptyString(token)) return { token: token.trim(), domain, encrypted: false };
367
+ if (token && typeof token === "object" && token.$wbEncrypted === 1) {
368
+ return { token: null, domain, encrypted: true };
369
+ }
370
+ return { token: null, domain, encrypted: false };
371
+ }
372
+
373
+ function resolveSession(variant, { env, authFiles, workbuddyHome }) {
374
+ const files = Array.isArray(authFiles)
375
+ ? authFiles
376
+ : authSearchDirs(env, workbuddyHome).flatMap((dir) => variant.authFileNames.map((name) => path.join(dir, name)));
377
+ let encryptedForVariant = false;
378
+ for (const filePath of files) {
379
+ if (!filePath) continue;
380
+ const session = readAuthFile(filePath);
381
+ if (!session) continue;
382
+ if (!hostAllowed(hostOf(session.domain), variant.allowedHostSuffixes)) continue;
383
+ if (session.token) return { token: session.token, encrypted: false, matched: true };
384
+ if (session.encrypted) encryptedForVariant = true;
385
+ }
386
+ return { token: null, encrypted: encryptedForVariant, matched: encryptedForVariant };
387
+ }
388
+
389
+ function readProductEndpoint(workbuddyHome) {
390
+ if (!workbuddyHome) return "";
391
+ const storage = path.join(workbuddyHome, "storage");
392
+ const found = [];
393
+ const walk = (dir, depth) => {
394
+ if (depth > 5 || found.length > 4) return;
395
+ let entries;
396
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
397
+ for (const entry of entries) {
398
+ if (found.length > 4) return;
399
+ const full = path.join(dir, entry.name);
400
+ if (entry.isDirectory()) walk(full, depth + 1);
401
+ else if (entry.name === "wb-product-config.json") found.push(full);
402
+ }
403
+ };
404
+ walk(storage, 0);
405
+ for (const filePath of found) {
406
+ try {
407
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
408
+ const data = parsed?.["product-config-snapshot"]?.data;
409
+ if (isNonEmptyString(data?.endpoint)) return data.endpoint.trim();
410
+ } catch {
411
+ // The next snapshot may still be readable.
412
+ }
413
+ }
414
+ return "";
415
+ }
416
+
417
+ function resolveOrigin(variant, { origin, workbuddyHome }) {
418
+ if (isNonEmptyString(origin) && originAllowed(origin, variant)) {
419
+ return origin.trim().replace(/\/+$/, "");
420
+ }
421
+ const configured = readProductEndpoint(workbuddyHome);
422
+ if (configured && originAllowed(configured, variant)) return configured.replace(/\/+$/, "");
423
+ return variant.defaultOrigin;
424
+ }
425
+
426
+ function resolveToken(variant, env) {
427
+ const fromEnv = env && typeof env === "object" ? env[variant.envToken] : null;
428
+ return isNonEmptyString(fromEnv) ? fromEnv.trim() : "";
429
+ }
430
+
431
+ async function postBilling(fetchImpl, url, token, body = {}, signal) {
432
+ const response = await fetchImpl(url, {
433
+ method: "POST",
434
+ headers: {
435
+ Accept: "application/json",
436
+ "Content-Type": "application/json",
437
+ Authorization: `Bearer ${token}`,
438
+ },
439
+ body: JSON.stringify(body),
440
+ signal,
441
+ });
442
+ if (response.status === 401 || response.status === 403) {
443
+ const error = new Error(`${VARIANTS.workbuddy.brand} sign-in expired.`);
444
+ error.code = "AUTH_EXPIRED";
445
+ throw error;
446
+ }
447
+ if (!response.ok) {
448
+ throw new Error(`WorkBuddy quota request failed (${response.status}).`);
449
+ }
450
+ return response.json();
451
+ }
452
+
453
+ async function fetchPlanQuota(post, variantId) {
454
+ const prefix = "/billing/meter/";
455
+ const summary = await post(`${prefix}get-user-resource-summary`, {});
456
+ if (!unwrapBillingBody(summary)) throw new Error("WorkBuddy quota request was rejected.");
457
+ const identity = readSubscriptionIdentity(summary);
458
+ const planCodes = [...new Set([...Object.keys(PLAN_PRIORITY),
459
+ ...(identity.code && !isRewardCode(identity.code) ? [identity.code] : [])])];
460
+ const packageRows = await Promise.all(["paid", "free"].map(async (kind) => {
461
+ const rows = [];
462
+ for (let page = 1; page <= 10; page++) {
463
+ const payload = await post(`${prefix}get-user-resource-${kind}-packages`, {
464
+ PageNumber: page, PageSize: 100, PackageCodes: planCodes,
465
+ Status: [0, 3], ...(kind === "paid" ? { NeedRenewInfo: true } : {}),
466
+ });
467
+ const data = unwrapBillingBody(payload);
468
+ if (!data) throw new Error("WorkBuddy quota request was rejected.");
469
+ rows.push(...collectPackageRows(payload));
470
+ const count = Array.isArray(data.Accounts) ? data.Accounts.length : 0;
471
+ if (count < 100 || (Number(data.TotalCount) > 0 && page * 100 >= Number(data.TotalCount))) return rows;
472
+ }
473
+ throw new Error("WorkBuddy quota package list was incomplete.");
474
+ }));
475
+ const quota = selectWorkbuddyPlanQuota([
476
+ ...collectPackageRows(summary), ...packageRows.flat(),
477
+ ], identity, variantId);
478
+ return {
479
+ configured: true, error: null,
480
+ plan_label: quota?.plan_label || englishPlanLabel({ code: identity.code }, identity, variantId),
481
+ primary_window: quota?.primary_window || null,
482
+ };
483
+ }
484
+
485
+ function scopedEnv(env, home) {
486
+ const next = { ...(env || {}) };
487
+ // An explicit home is the whole lookup root. Tests pass a temp directory
488
+ // while process.env.HOME still points at the developer, and a China
489
+ // session must not leak into that lookup.
490
+ if (isNonEmptyString(home)) {
491
+ next.HOME = home.trim();
492
+ if (next.USERPROFILE) next.USERPROFILE = home.trim();
493
+ }
494
+ return next;
495
+ }
496
+
497
+ async function fetchVariant(variantId, opts = {}) {
498
+ const variant = variantById(variantId);
499
+ if (!variant) return { configured: false };
500
+ const env = scopedEnv(opts.env || process.env, opts.home);
501
+ const workbuddyHome = opts.workbuddyHome || resolveWorkbuddyHome(env, variant.source);
502
+ const installed = directoryExists(workbuddyHome);
503
+ const discovery = readWorkbuddyIpcDiscovery(workbuddyHome);
504
+ if (discovery) {
505
+ // Discovery is edition-local. Never fall back to the other edition's
506
+ // socket or allow an explicitly redirected product to be mislabelled.
507
+ const productEndpoint = readProductEndpoint(workbuddyHome);
508
+ if (productEndpoint && !originAllowed(productEndpoint, variant)) {
509
+ throw new Error("WorkBuddy quota host does not match this edition.");
510
+ }
511
+ const client = await openWorkbuddyIpc({
512
+ discovery, signal: opts.signal, timeoutMs: opts.timeoutMs, connectImpl: opts.connectImpl,
513
+ });
514
+ try {
515
+ return { ...await fetchPlanQuota((apiPath, body) => client.postBilling(apiPath, body), variantId), source: "ipc" };
516
+ } finally {
517
+ client.close();
518
+ }
519
+ }
520
+ const explicitToken = resolveToken(variant, env);
521
+ const session = explicitToken
522
+ ? { token: explicitToken, encrypted: false, matched: true }
523
+ : resolveSession(variant, { env, authFiles: opts.authFiles, workbuddyHome });
524
+ if (!session.token) {
525
+ if (session.encrypted || installed) return { configured: true, error: UNAVAILABLE };
526
+ if (!installed) return { configured: false };
527
+ return { configured: false };
528
+ }
529
+
530
+ const fetchImpl = opts.fetchImpl || fetch;
531
+ // An explicit origin is the caller's host. Otherwise use this edition's
532
+ // own product-config endpoint, and never the other edition's default.
533
+ const origin = resolveOrigin(variant, {
534
+ origin: opts.origin,
535
+ workbuddyHome: opts.origin ? null : workbuddyHome,
536
+ });
537
+ if (!originAllowed(origin, variant)) {
538
+ throw new Error("WorkBuddy quota host does not match this edition.");
539
+ }
540
+ return { ...await fetchPlanQuota((apiPath, body) => postBilling(fetchImpl,
541
+ `${origin}${apiPath}`, session.token, body, opts.signal), variantId), source: "api" };
542
+ }
543
+
544
+ function fetchWorkbuddyLimits(opts) {
545
+ return fetchVariant("workbuddy", opts);
546
+ }
547
+
548
+ function fetchWorkbuddyAiLimits(opts) {
549
+ return fetchVariant("workbuddyAi", opts);
550
+ }
551
+
552
+ module.exports = {
553
+ PLAN_PRIORITY,
554
+ REWARD_CODES,
555
+ VARIANTS,
556
+ selectWorkbuddyPlanQuota,
557
+ selectWorkbuddyLimits,
558
+ collectPackageRows,
559
+ readSubscriptionIdentity,
560
+ originAllowed,
561
+ hostAllowed,
562
+ fetchWorkbuddyLimits,
563
+ fetchWorkbuddyAiLimits,
564
+ };
@@ -1 +0,0 @@
1
- import{a as b,r as l,j as r,c as e,f as v}from"./__vite-browser-external-CIpYjfAT.js";const f={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1,VITE_APP_VERSION:"1.4.9",VITE_INSFORGE_ANON_KEY:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3OC0xMjM0LTU2NzgtOTBhYi1jZGVmMTIzNDU2NzgiLCJlbWFpbCI6ImFub25AaW5zZm9yZ2UuY29tIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODExNDU5NDd9.T0auta_IrVIh0uXW1bob5QSnzvsnJmN28r5XkSGEuQY",VITE_INSFORGE_BASE_URL:"https://srctyff5.us-east.insforge.app",VITE_SENTRY_DSN:"https://5f9fc9cc76a1d9320197c63a4cbef478@o4512274440781824.ingest.us.sentry.io/4512286762139648"};function x(){return v(),`${(f&&"https://srctyff5.us-east.insforge.app"||"https://srctyff5.us-east.insforge.app").replace(/\/$/,"")}/functions/tokentracker-device-flow-grant`}function p(s){return(s||"").toUpperCase().replace(/\s+/g,"").replace(/[^A-Z0-9-]/g,"")}function h(){const s=b(),[m,g]=l.useState(""),[i,a]=l.useState({kind:"idle",message:""});l.useEffect(()=>{try{const d=new URLSearchParams(window.location.search).get("user_code");d&&g(p(d))}catch{}},[]);async function u(c){if(c?.preventDefault?.(),!s?.signedIn){a({kind:"error",message:e("device.approval.error.sign_in")});return}const d=p(m);if(!/^[A-HJKMNP-Z2-9]{4}-[A-HJKMNP-Z2-9]{4}$/.test(d)){a({kind:"error",message:e("device.approval.error.invalid_code")});return}a({kind:"working",message:e("device.approval.action.approving")});try{const n=await s.getAccessToken?.();if(!n){a({kind:"error",message:e("device.approval.error.no_token")});return}const o=await fetch(x(),{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({user_code:d})}),t=await o.json().catch(()=>({}));o.ok?a({kind:"success",message:t.status==="already_approved"?t.client_info?e("device.approval.success.already_client",{client:t.client_info}):e("device.approval.success.already"):t.client_info?e("device.approval.success.approved_client",{client:t.client_info}):e("device.approval.success.approved")}):o.status===404?a({kind:"error",message:e("device.approval.error.not_found")}):o.status===410?a({kind:"error",message:e("device.approval.error.expired")}):o.status===401?a({kind:"error",message:e("device.approval.error.session_expired")}):a({kind:"error",message:t?.error?e("device.approval.error.server_detail",{message:t.error}):e("device.approval.error.server",{status:o.status})})}catch(n){a({kind:"error",message:e("device.approval.error.network",{message:n?.message||n})})}}return r.jsx("div",{className:"min-h-screen flex items-center justify-center bg-white dark:bg-oai-gray-950 px-6 py-12",children:r.jsxs("div",{className:"w-full max-w-md rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 p-8",children:[r.jsx("h1",{className:"text-2xl font-semibold text-oai-gray-900 dark:text-white mb-2",children:e("device.approval.title")}),r.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400 mb-6",children:e("device.approval.description")}),s?.signedIn?null:r.jsx("div",{className:"rounded-md border border-amber-200 bg-amber-50 dark:bg-amber-900/20 dark:border-amber-800 p-4 mb-4",children:r.jsx("p",{className:"text-sm text-amber-700 dark:text-amber-300",children:e("device.approval.sign_in_banner")})}),r.jsxs("form",{onSubmit:u,className:"space-y-4",children:[r.jsxs("label",{className:"block",children:[r.jsx("span",{className:"block text-xs font-medium uppercase tracking-wide text-oai-gray-500 dark:text-oai-gray-400 mb-1",children:e("device.approval.code_label")}),r.jsx("input",{type:"text",value:m,onChange:c=>g(p(c.target.value)),placeholder:"XXXX-XXXX",spellCheck:!1,autoCapitalize:"characters",maxLength:9,className:"w-full font-mono text-xl tracking-widest text-center rounded-md border border-oai-gray-300 dark:border-oai-gray-700 bg-white dark:bg-oai-gray-900 text-oai-gray-900 dark:text-white px-4 py-3 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-emerald-600"})]}),r.jsx("button",{type:"submit",disabled:i.kind==="working"||!s?.signedIn,className:"w-full rounded-md bg-emerald-600 hover:bg-emerald-500 disabled:bg-oai-gray-300 disabled:cursor-not-allowed text-white font-medium py-3 transition-colors",children:i.kind==="working"?e("device.approval.action.approving"):e("device.approval.action.approve")})]}),i.kind==="success"&&r.jsx("div",{className:"mt-4 rounded-md border border-emerald-200 bg-emerald-50 dark:bg-emerald-900/20 dark:border-emerald-800 p-4",children:r.jsx("p",{className:"text-sm text-emerald-700 dark:text-emerald-300",children:i.message})}),i.kind==="error"&&r.jsx("div",{className:"mt-4 rounded-md border border-red-200 bg-red-50 dark:bg-red-900/20 dark:border-red-800 p-4",children:r.jsx("p",{className:"text-sm text-red-700 dark:text-red-300",children:i.message})})]})})}export{h as default};
@@ -1 +0,0 @@
1
- import{r as n,J as a}from"./__vite-browser-external-CIpYjfAT.js";import{u as p}from"./DialogTitle-BLH1MgOX.js";import{u as c}from"./composite-DDEsQKPD.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,i=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};