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
|
@@ -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
|
+
};
|