tokentracker-cli 0.90.2 → 0.91.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +7 -5
- package/README.ja.md +9 -5
- package/README.ko.md +9 -5
- package/README.md +7 -5
- package/README.zh-CN.md +6 -4
- package/dashboard/dist/assets/{AchievementBadge-BJl0ibLg.js → AchievementBadge-LHOuekQ3.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-CV6aNe3g.js → AchievementsPage-DNzeYWBl.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DxLHu9EV.js → AchievementsSection-Ch5dImt8.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-ConoHhIL.js → BadgeDetailModal-CuikWkUy.js} +1 -1
- package/dashboard/dist/assets/{Card-C_VQxG6G.js → Card-Chwjmxjz.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-_Mfn4MnU.js → ClawdAnimated-BdB7aycS.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-CCRCnYY9.js → CommandPalette-BMwuSEWM.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-DuICwpdM.js → CommunityStatsModal-eEDF-xn8.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CI27robt.js → ConfirmModal-Be7f6XdL.js} +1 -1
- package/dashboard/dist/assets/{Controls-Cwu0gtgj.js → Controls-CtBFPCBY.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-GsFvPie1.js → DashboardPage-M0p-SU-f.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-BSK76UQE.js → DevicePage-CAfcUond.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BXQlPs02.js → DialogClose-kd1wIljN.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-CjJLpfCL.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DyvhHV1-.js → DialogTitle-DhB-tbnZ.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DaP6F4f8.js → FadeIn-B8eRWBdy.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CKB5puCd.js → HeaderGithubStar-DmWep8uP.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-BG6k61dF.js → HoverTooltip-DDyTZli0.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-Oqt1J9S2.js → IpCheckPage-CN8hYm4o.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-45qoUn79.js → LandingPage-B3cHASqK.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-ZgXnaI_N.js → LeaderboardAvatar-CEcLXrJL.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-C7E_LrFk.js → LeaderboardPage-H6HW9hon.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-D6PUcj3b.js → LeaderboardProfileModal-B_5PyjRA.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-VcC9DK_B.js → LeaderboardProfilePage-DR9KWLWv.js} +3 -3
- package/dashboard/dist/assets/{LimitsPage-B9K0RFQD.js → LimitsPage-Da0hRxO_.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-BGsfKEM9.js → LocalOnlyNotice-DuTdva_n.js} +1 -1
- package/dashboard/dist/assets/LoginCard-DOL3osrj.js +1 -0
- package/dashboard/dist/assets/{LoginModal-N3pWGciC.js → LoginModal-DBmaQL2L.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-TGgL0aXF.js → LoginPage-Dk08VrkC.js} +1 -1
- package/dashboard/dist/assets/{PetPage-DPi7OT_4.js → PetPage-BbCIdpzx.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-BkptZaPd.js → PopoverPopup-C1fJZ4qt.js} +1 -1
- package/dashboard/dist/assets/ProviderIcon-FfLD-f73.js +1 -0
- package/dashboard/dist/assets/{ResetPasswordPage-DQ4qdjBD.js → ResetPasswordPage-Bg7eVc6L.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-CuTrxHXS.js → SegmentedControl-CFNHcUz7.js} +1 -1
- package/dashboard/dist/assets/{Select-_Txi2j1D.js → Select-B0Q0eDcJ.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DVKhj-YN.js → SelectItemText-BVbLYxSQ.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-ChflNSK0.js → ServiceStatusPage-DCZH2kz5.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-CHv6xK2J.js → SessionsPage-DO5Et7ss.js} +2 -2
- package/dashboard/dist/assets/{SettingsPage-DkWAYALG.js → SettingsPage-afgxGfzT.js} +2 -2
- package/dashboard/dist/assets/{SkillsPage-DnTSlBl-.js → SkillsPage-DDKiHCF7.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-yAMCaU-k.js → TokenGalaxy-DV1Xoe5z.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-BeDIqB0k.js → ToolbarRootContext-DNckd9Qz.js} +1 -1
- package/dashboard/dist/assets/TrendMonitor-CliMJxCC.js +19 -0
- package/dashboard/dist/assets/{WidgetsPage-y_qKZ9Gv.js → WidgetsPage-BrwG6YMM.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BGUGI6vk.js → WrappedPage-Ft9YJd0D.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-CRvi_9hb.js → __vite-browser-external-C6_BtvDo.js} +4 -3
- package/dashboard/dist/assets/agent-logos-yYNu1ZUy.js +1 -0
- package/dashboard/dist/assets/{arrow-up-right-CjEesyJI.js → arrow-up-right-B_K57Vbg.js} +1 -1
- package/dashboard/dist/assets/{check-KDrH1gwE.js → check-xH63o2xJ.js} +1 -1
- package/dashboard/dist/assets/{chevron-right-DseqFm-D.js → chevron-right-D3F2rAfr.js} +1 -1
- package/dashboard/dist/assets/{copy-CFJ1QKAQ.js → copy-WPfJGGI0.js} +1 -1
- package/dashboard/dist/assets/{download-BQUNGUdh.js → download-BgtWtEXI.js} +1 -1
- package/dashboard/dist/assets/{external-link-BCEOLLH2.js → external-link-DSHGoZRM.js} +1 -1
- package/dashboard/dist/assets/{flame-BigtoGg2.js → flame-CNGl6Qac.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-CS1GUpAV.js → format-tokens-jVm2UX7Y.js} +1 -1
- package/dashboard/dist/assets/{icons-DpZ1a8Ct.js → icons-B1JZg-Id.js} +1 -1
- package/dashboard/dist/assets/{index-DqkwD6eO.js → index-BTZibjLi.js} +1 -1
- package/dashboard/dist/assets/{index-lGAAw1Rv.js → index-BjD81Qce.js} +1 -1
- package/dashboard/dist/assets/{index-DFU_yVc5.js → index-CUFHknAy.js} +1 -1
- package/dashboard/dist/assets/{info-Br_jtGtO.js → info-CLM460KM.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-CkqO1LMQ.js → limits-providers-CFO4O1qh.js} +1 -1
- package/dashboard/dist/assets/{link-2-D-nAhkk-.js → link-2-BHOsUnAI.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CuNPuDRR.js → loader-circle-CgcIQi92.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-dlvwitfg.js → maximize-2-DvqfuJEA.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-ORha_lPI.js → modifiers.esm-CNiwIkPh.js} +1 -1
- package/dashboard/dist/assets/module-CV1mYFsv.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-CI2qvlMB.js → monitor-smartphone-BLutpxrG.js} +1 -1
- package/dashboard/dist/assets/provider-display-BxIgWUEa.js +42 -0
- package/dashboard/dist/assets/{react-Dy9iWvqT.js → react-CM-ozVnh.js} +1 -1
- package/dashboard/dist/assets/{search-Do7tzc3k.js → search-4T07INKt.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CJ-8EFac.js → skills-api-EXMD3LD1.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BKQg8NHC.js → sortable.esm-jC6mPJiL.js} +1 -1
- package/dashboard/dist/assets/{store-SUT-n3k3.js → store-CvSEdgf2.js} +1 -1
- package/dashboard/dist/assets/{terminal-BX4DOxd6.js → terminal-D9fyE2pP.js} +1 -1
- package/dashboard/dist/assets/{trash-2-VERlG247.js → trash-2-QLUsAY3l.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-BX6_XUdu.js → use-community-stats-Chrt3kpU.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-BpC3tYRw.js → use-limits-display-prefs-BJJd750e.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-CX-LREPo.js → use-ordered-list-BBJp5lFv.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-BvXZCGFC.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-B1G8hzVM.js → use-session-efficiency-pref-BXbvx2Ht.js} +1 -1
- package/dashboard/dist/assets/{use-transform-DNCqLbUD.js → use-transform-BVp_MUsU.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-Cvbvf30m.js → use-usage-limits-Dk3EkeIs.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-EciQmJkm.js → useCurrency-Bi88Sd__.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-sLkmam8C.js → useScrollLock-if34g2H-.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-nb9na-2-.js → useTokenFormat-C2cz-5qJ.js} +1 -1
- package/dashboard/dist/assets/{zap-BQOWCMov.js → zap-D1yHmcQR.js} +1 -1
- package/dashboard/dist/index.html +28 -24
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/llms.txt +4 -2
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -2
- package/src/commands/init.js +12 -1
- package/src/commands/status.js +59 -0
- package/src/commands/sync.js +220 -4
- package/src/lib/account-usage-dedup.js +267 -0
- package/src/lib/local-api.js +4 -0
- package/src/lib/pricing/curated-overrides.json +15 -9
- package/src/lib/pricing/index.js +38 -2
- package/src/lib/pricing/matcher.js +1 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +853 -14
- package/src/lib/source-metadata.js +1 -1
- package/src/lib/trae-cn-config.js +539 -0
- package/dashboard/dist/assets/DialogDescription-M_lg2jhD.js +0 -1
- package/dashboard/dist/assets/LoginCard-j5HVFRdP.js +0 -1
- package/dashboard/dist/assets/ProviderIcon-zaz4nwxI.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-BthLuYwy.js +0 -19
- package/dashboard/dist/assets/agent-logos-DwHwn_cb.js +0 -1
- package/dashboard/dist/assets/module-BRJxSJmz.js +0 -3
- package/dashboard/dist/assets/provider-display-BNCdZ83n.js +0 -42
- package/dashboard/dist/assets/use-reduced-motion-DbebVWBw.js +0 -1
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TRAE Work CN (国内版) config + usage fetch. Phase A: macOS + test-injected
|
|
5
|
+
* paths only. Resolves the CN home, reads the `iCubeAuthInfo://icube.cloudide`
|
|
6
|
+
* blob from User/globalStorage/storage.json (plaintext JSON or Base64 tc v5),
|
|
7
|
+
* and talks to the TRAE CN usage API. JWTs / refresh tokens are never
|
|
8
|
+
* persisted, never logged, and never put into error messages; a
|
|
9
|
+
* present-but-unreadable blob fails closed with a generic error.
|
|
10
|
+
*
|
|
11
|
+
* tc v5 blob: [6-byte magic "tc\x05\x10\x00\x00"][32-byte salt][AES-128-CBC ct]
|
|
12
|
+
* kdf_buf = SHA-512(salt) || (JG xor KG) // 128 bytes
|
|
13
|
+
* kdf_out = SHA-512(kdf_buf) // 64 bytes
|
|
14
|
+
* key = kdf_out[0..16], iv = kdf_out[16..32]
|
|
15
|
+
* plaintext = [SHA-512(json)][json] // integrity prefix stripped
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const crypto = require("node:crypto");
|
|
19
|
+
const fs = require("node:fs");
|
|
20
|
+
const os = require("node:os");
|
|
21
|
+
const path = require("node:path");
|
|
22
|
+
|
|
23
|
+
const TRAE_CN_HOME_ENV = "TOKENTRACKER_TRAE_CN_HOME";
|
|
24
|
+
// The TRAE CN usage read transmits the locally stored sign-in JWT to TRAE's
|
|
25
|
+
// official endpoint, so it is strictly opt-in: nothing is sent unless the
|
|
26
|
+
// user explicitly sets this flag. Any value other than 1/true keeps it off.
|
|
27
|
+
const TRAE_CN_USAGE_ENV = "TOKENTRACKER_TRAE_CN_USAGE";
|
|
28
|
+
|
|
29
|
+
function isTraeCnUsageEnabled(env = process.env) {
|
|
30
|
+
const value = env ? env[TRAE_CN_USAGE_ENV] : undefined;
|
|
31
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
32
|
+
return normalized === "1" || normalized === "true";
|
|
33
|
+
}
|
|
34
|
+
const TRAE_CN_APP_DIR = "TRAE SOLO CN";
|
|
35
|
+
const TRAE_CN_AUTH_KEY = "iCubeAuthInfo://icube.cloudide";
|
|
36
|
+
|
|
37
|
+
const TRAE_CN_USAGE_URL = "https://api.trae.cn/trae/api/v1/pay/query_user_usage_group_by_session";
|
|
38
|
+
// usage_type [7] mirrors the constant the official TRAE client sends when its
|
|
39
|
+
// usage page queries this endpoint (observed in the client's outbound
|
|
40
|
+
// requests; the enum is undocumented server-side). Other values unverified.
|
|
41
|
+
const TRAE_CN_USAGE_TYPE = [7];
|
|
42
|
+
const TRAE_CN_USAGE_PAGE_SIZE = 20;
|
|
43
|
+
const TRAE_CN_USAGE_MAX_PAGES = 100;
|
|
44
|
+
const TRAE_CN_USAGE_DELAY_MS = 300;
|
|
45
|
+
const TRAE_CN_USAGE_TIMEOUT_MS = 30 * 1000;
|
|
46
|
+
// Capacity-adaptive window splitting: 30 days halves at most 8 times
|
|
47
|
+
// (finest sub-window ~2.8h, aggregate ceiling ~512k sessions/30d). A window
|
|
48
|
+
// still over capacity at that depth fails closed — never a partial import.
|
|
49
|
+
const TRAE_CN_USAGE_MAX_SPLIT_DEPTH = 8;
|
|
50
|
+
|
|
51
|
+
const TRAE_CN_MAGIC = Buffer.from([0x74, 0x63, 0x05, 0x10, 0x00, 0x00]);
|
|
52
|
+
const TRAE_CN_SALT_LEN = 32;
|
|
53
|
+
const TRAE_CN_HASH_LEN = 64;
|
|
54
|
+
const TRAE_CN_AES_KEY_LEN = 16;
|
|
55
|
+
const TRAE_CN_IV_LEN = 16;
|
|
56
|
+
|
|
57
|
+
// JG / KG — the two halves of the hardcoded (obfuscated) password in the TRAE
|
|
58
|
+
// client's byteCrypto.js (XOR the pair to recover the KDF secret). Failure
|
|
59
|
+
// signature when TRAE ships a new key: readTraeCnAuthFromStorage throws
|
|
60
|
+
// "Trae CN auth payload is malformed", sync skips the source, and
|
|
61
|
+
// `tokentracker status` reports auth "malformed" — re-extract the pair
|
|
62
|
+
// from the updated client to restore decryption.
|
|
63
|
+
const TRAE_CN_JG = Buffer.from([
|
|
64
|
+
82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, 124, 227, 57, 130,
|
|
65
|
+
155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, 84, 123, 148, 50, 166, 194, 35, 61,
|
|
66
|
+
238, 76, 149, 11, 66, 250, 195, 78, 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73,
|
|
67
|
+
109, 139, 209, 37,
|
|
68
|
+
]);
|
|
69
|
+
const TRAE_CN_KG = Buffer.from([
|
|
70
|
+
31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, 96, 81, 127, 169, 25,
|
|
71
|
+
181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, 160, 224, 59, 77, 174, 42, 245, 176,
|
|
72
|
+
200, 235, 187, 60, 131, 83, 153, 97, 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99,
|
|
73
|
+
85, 33, 12, 125,
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Path resolution
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
function resolveTraeCnHome({ env = process.env, home = os.homedir(), platform = process.platform } = {}) {
|
|
81
|
+
const override = env[TRAE_CN_HOME_ENV];
|
|
82
|
+
if (typeof override === "string" && override.trim()) {
|
|
83
|
+
return path.resolve(override.trim());
|
|
84
|
+
}
|
|
85
|
+
if (platform === "darwin") {
|
|
86
|
+
return path.join(home, "Library", "Application Support", TRAE_CN_APP_DIR);
|
|
87
|
+
}
|
|
88
|
+
// ponytail: this phase is macOS + test-injected paths only.
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveTraeCnStoragePath(options = {}) {
|
|
93
|
+
const home = resolveTraeCnHome(options);
|
|
94
|
+
return home ? path.join(home, "User", "globalStorage", "storage.json") : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// tc v5 decryption
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
function traeCnHardcodedKdfSecret() {
|
|
102
|
+
const secret = Buffer.alloc(64);
|
|
103
|
+
for (let i = 0; i < 64; i++) secret[i] = TRAE_CN_JG[i] ^ TRAE_CN_KG[i];
|
|
104
|
+
return secret;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function deriveTraeCnKeyIv(salt) {
|
|
108
|
+
if (!Buffer.isBuffer(salt) || salt.length !== TRAE_CN_SALT_LEN) {
|
|
109
|
+
throw new Error("Trae CN auth salt is malformed.");
|
|
110
|
+
}
|
|
111
|
+
const kdfBuf = Buffer.concat([
|
|
112
|
+
crypto.createHash("sha512").update(salt).digest(),
|
|
113
|
+
traeCnHardcodedKdfSecret(),
|
|
114
|
+
]);
|
|
115
|
+
// Vendor tc-v5 compatibility KDF, not password storage.
|
|
116
|
+
const kdfOut = crypto.createHash("sha512").update(kdfBuf).digest();
|
|
117
|
+
return {
|
|
118
|
+
key: kdfOut.subarray(0, TRAE_CN_AES_KEY_LEN),
|
|
119
|
+
iv: kdfOut.subarray(TRAE_CN_AES_KEY_LEN, TRAE_CN_AES_KEY_LEN + TRAE_CN_IV_LEN),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Decrypt a raw tc v5 blob; returns the JSON bytes (integrity prefix stripped). */
|
|
124
|
+
function decryptTraeCnBlob(blob) {
|
|
125
|
+
const minLen = TRAE_CN_MAGIC.length + TRAE_CN_SALT_LEN + 16;
|
|
126
|
+
if (!Buffer.isBuffer(blob) || blob.length < minLen) {
|
|
127
|
+
throw new Error("Trae CN auth blob is too short.");
|
|
128
|
+
}
|
|
129
|
+
if (!blob.subarray(0, TRAE_CN_MAGIC.length).equals(TRAE_CN_MAGIC)) {
|
|
130
|
+
throw new Error("Trae CN auth blob has an unknown format.");
|
|
131
|
+
}
|
|
132
|
+
const salt = blob.subarray(TRAE_CN_MAGIC.length, TRAE_CN_MAGIC.length + TRAE_CN_SALT_LEN);
|
|
133
|
+
const ciphertext = blob.subarray(TRAE_CN_MAGIC.length + TRAE_CN_SALT_LEN);
|
|
134
|
+
if (ciphertext.length % 16 !== 0) {
|
|
135
|
+
throw new Error("Trae CN auth blob is not block-aligned.");
|
|
136
|
+
}
|
|
137
|
+
const { key, iv } = deriveTraeCnKeyIv(salt);
|
|
138
|
+
const decipher = crypto.createDecipheriv("aes-128-cbc", key, iv); // PKCS7 default
|
|
139
|
+
let plaintext;
|
|
140
|
+
try {
|
|
141
|
+
plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
142
|
+
} catch (_error) {
|
|
143
|
+
throw new Error("Trae CN auth blob could not be decrypted.");
|
|
144
|
+
}
|
|
145
|
+
if (plaintext.length < TRAE_CN_HASH_LEN) {
|
|
146
|
+
throw new Error("Trae CN auth blob is missing its integrity digest.");
|
|
147
|
+
}
|
|
148
|
+
const expected = plaintext.subarray(0, TRAE_CN_HASH_LEN);
|
|
149
|
+
const data = plaintext.subarray(TRAE_CN_HASH_LEN);
|
|
150
|
+
const actual = crypto.createHash("sha512").update(data).digest();
|
|
151
|
+
if (expected.length !== actual.length || !crypto.timingSafeEqual(expected, actual)) {
|
|
152
|
+
throw new Error("Trae CN auth blob failed its integrity check.");
|
|
153
|
+
}
|
|
154
|
+
return data;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Decrypt a Base64-encoded tc v5 blob and return the plaintext UTF-8 string. */
|
|
158
|
+
function decryptTraeCnBase64(value) {
|
|
159
|
+
const blob = Buffer.from(String(value).trim(), "base64");
|
|
160
|
+
return decryptTraeCnBlob(blob).toString("utf8");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Credential reading
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse a stored auth value: a plaintext JSON object / JSON string, or a
|
|
169
|
+
* Base64-encoded tc v5 blob. Fails closed with a credential-safe error.
|
|
170
|
+
*/
|
|
171
|
+
function parseTraeCnAuthValue(value) {
|
|
172
|
+
if (value && typeof value === "object") {
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
if (typeof value === "string") {
|
|
176
|
+
const trimmed = value.trim();
|
|
177
|
+
if (!trimmed) {
|
|
178
|
+
throw new Error("Trae CN auth data is empty.");
|
|
179
|
+
}
|
|
180
|
+
if (trimmed.startsWith("{")) {
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(trimmed);
|
|
184
|
+
} catch (_error) {
|
|
185
|
+
throw new Error("Trae CN auth JSON is malformed.");
|
|
186
|
+
}
|
|
187
|
+
if (!parsed || typeof parsed !== "object") {
|
|
188
|
+
throw new Error("Trae CN auth JSON is malformed.");
|
|
189
|
+
}
|
|
190
|
+
return parsed;
|
|
191
|
+
}
|
|
192
|
+
let decrypted;
|
|
193
|
+
try {
|
|
194
|
+
decrypted = decryptTraeCnBase64(trimmed);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error && error.message.startsWith("Trae CN auth")) throw error;
|
|
197
|
+
throw new Error("Trae CN auth payload is malformed.");
|
|
198
|
+
}
|
|
199
|
+
let parsed;
|
|
200
|
+
try {
|
|
201
|
+
parsed = JSON.parse(decrypted);
|
|
202
|
+
} catch (_error) {
|
|
203
|
+
throw new Error("Trae CN auth payload is malformed.");
|
|
204
|
+
}
|
|
205
|
+
if (!parsed || typeof parsed !== "object") {
|
|
206
|
+
throw new Error("Trae CN auth payload is malformed.");
|
|
207
|
+
}
|
|
208
|
+
return parsed;
|
|
209
|
+
}
|
|
210
|
+
throw new Error("Trae CN auth data is missing or malformed.");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Read + decrypt the auth object from storage.json. Returns null when the
|
|
215
|
+
* file or the auth key is absent (not signed in). Throws a credential-safe
|
|
216
|
+
* error when present data is malformed, and a distinct unreadable error for
|
|
217
|
+
* real IO failures (permission denied, I/O error) - an unreadable storage
|
|
218
|
+
* file must not be reported as "not signed in".
|
|
219
|
+
*/
|
|
220
|
+
function readTraeCnAuthFromStorage({ env, home, platform, fsModule = fs } = {}) {
|
|
221
|
+
const storagePath = resolveTraeCnStoragePath({ env, home, platform });
|
|
222
|
+
if (!storagePath) return null;
|
|
223
|
+
let raw;
|
|
224
|
+
try {
|
|
225
|
+
raw = fsModule.readFileSync(storagePath, "utf8");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// A missing file (or missing path component) simply means the app is not
|
|
228
|
+
// installed / not signed in. Any other failure - EACCES, EISDIR, a real
|
|
229
|
+
// I/O error - fails closed with a generic, credential-safe error. Only a
|
|
230
|
+
// stable machine-readable code is attached, never the OS detail, so
|
|
231
|
+
// storage contents cannot leak through logs or error messages.
|
|
232
|
+
if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const unreadable = new Error("Trae CN storage.json could not be read.");
|
|
236
|
+
unreadable.code = "TRAE_CN_STORAGE_UNREADABLE";
|
|
237
|
+
throw unreadable;
|
|
238
|
+
}
|
|
239
|
+
let parsed;
|
|
240
|
+
try {
|
|
241
|
+
parsed = JSON.parse(raw);
|
|
242
|
+
} catch (_error) {
|
|
243
|
+
throw new Error("Trae CN storage.json is not valid JSON.");
|
|
244
|
+
}
|
|
245
|
+
const value = parsed && typeof parsed === "object" ? parsed[TRAE_CN_AUTH_KEY] : undefined;
|
|
246
|
+
if (value === undefined || value === null) return null;
|
|
247
|
+
return parseTraeCnAuthValue(value);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Extract a nonempty JWT from a parsed auth object. */
|
|
251
|
+
function extractTraeCnToken(auth) {
|
|
252
|
+
const token = auth && typeof auth === "object" ? auth.token : undefined;
|
|
253
|
+
if (typeof token === "string" && token.trim()) {
|
|
254
|
+
return token.trim();
|
|
255
|
+
}
|
|
256
|
+
throw new Error("Trae CN auth data has no token.");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Usage API
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
function usageError(message, { status, apiCode } = {}) {
|
|
264
|
+
const error = new Error(message);
|
|
265
|
+
if (status !== undefined) error.status = status;
|
|
266
|
+
if (apiCode !== undefined) error.apiCode = apiCode;
|
|
267
|
+
if (status === 401 || status === 403) error.code = "AUTH_EXPIRED";
|
|
268
|
+
return error;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function assertUsageParams({ start_time, end_time, page_size }) {
|
|
272
|
+
if (!Number.isInteger(start_time) || start_time <= 0) {
|
|
273
|
+
throw new Error("Trae CN usage start_time must be a positive integer epoch second.");
|
|
274
|
+
}
|
|
275
|
+
if (!Number.isInteger(end_time) || end_time <= 0) {
|
|
276
|
+
throw new Error("Trae CN usage end_time must be a positive integer epoch second.");
|
|
277
|
+
}
|
|
278
|
+
if (start_time > end_time) {
|
|
279
|
+
throw new Error("Trae CN usage start_time must not be after end_time.");
|
|
280
|
+
}
|
|
281
|
+
if (!Number.isInteger(page_size) || page_size <= 0 || page_size > TRAE_CN_USAGE_PAGE_SIZE) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`Trae CN usage page_size must be a positive integer no greater than ${TRAE_CN_USAGE_PAGE_SIZE}.`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Fetch one page of TRAE CN usage. Returns { sessions, total, page_num, page_size }
|
|
290
|
+
* where `total` is null when the API omits it. Errors never contain the JWT
|
|
291
|
+
* or the raw request/response body.
|
|
292
|
+
*/
|
|
293
|
+
async function fetchTraeCnUsagePage({
|
|
294
|
+
jwt,
|
|
295
|
+
start_time,
|
|
296
|
+
end_time,
|
|
297
|
+
page_num,
|
|
298
|
+
page_size = TRAE_CN_USAGE_PAGE_SIZE,
|
|
299
|
+
fetchImpl = fetch,
|
|
300
|
+
timeoutMs = TRAE_CN_USAGE_TIMEOUT_MS,
|
|
301
|
+
} = {}) {
|
|
302
|
+
if (typeof jwt !== "string" || !jwt.trim()) {
|
|
303
|
+
throw new Error("Trae CN usage request requires a token.");
|
|
304
|
+
}
|
|
305
|
+
if (!Number.isInteger(page_num) || page_num < 1) {
|
|
306
|
+
throw new Error("Trae CN usage page_num must be a positive integer.");
|
|
307
|
+
}
|
|
308
|
+
assertUsageParams({ start_time, end_time, page_size });
|
|
309
|
+
const controller = new AbortController();
|
|
310
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
311
|
+
try {
|
|
312
|
+
let response;
|
|
313
|
+
try {
|
|
314
|
+
// Security note (CodeQL "File data -> outbound network request"):
|
|
315
|
+
// the credential read from TRAE's local storage.json is sent ONLY to
|
|
316
|
+
// TRAE_CN_USAGE_URL, a compile-time constant HTTPS endpoint of the
|
|
317
|
+
// official API - callers cannot redirect the destination (the url
|
|
318
|
+
// parameter is ignored; pinned by the "sends the exact request" test).
|
|
319
|
+
// The whole flow is behind the explicit TOKENTRACKER_TRAE_CN_USAGE
|
|
320
|
+
// opt-in, the JWT is never logged, never written to the queue/cursor
|
|
321
|
+
// state, and never uploaded to the TokenTracker cloud (queue rows carry
|
|
322
|
+
// token counters only; transport exceptions are sanitized).
|
|
323
|
+
response = await fetchImpl(TRAE_CN_USAGE_URL, {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: {
|
|
326
|
+
"Content-Type": "application/json",
|
|
327
|
+
Authorization: `Cloud-IDE-JWT ${jwt.trim()}`,
|
|
328
|
+
},
|
|
329
|
+
body: JSON.stringify({
|
|
330
|
+
usage_type: TRAE_CN_USAGE_TYPE,
|
|
331
|
+
start_time,
|
|
332
|
+
end_time,
|
|
333
|
+
page_num,
|
|
334
|
+
page_size,
|
|
335
|
+
}),
|
|
336
|
+
signal: controller.signal,
|
|
337
|
+
});
|
|
338
|
+
} catch (_error) {
|
|
339
|
+
throw new Error("Trae CN usage API request failed.");
|
|
340
|
+
}
|
|
341
|
+
if (response.status === 401 || response.status === 403) {
|
|
342
|
+
throw usageError(`Trae CN usage API returned HTTP ${response.status}.`, { status: response.status });
|
|
343
|
+
}
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
throw usageError(`Trae CN usage API returned HTTP ${response.status}.`, { status: response.status });
|
|
346
|
+
}
|
|
347
|
+
let parsed;
|
|
348
|
+
try {
|
|
349
|
+
parsed = await response.json();
|
|
350
|
+
} catch (_error) {
|
|
351
|
+
throw new Error("Trae CN usage API returned a non-JSON response.");
|
|
352
|
+
}
|
|
353
|
+
const apiCode = parsed?.data?.code ?? parsed?.code;
|
|
354
|
+
if (apiCode !== undefined && Number(apiCode) !== 0) {
|
|
355
|
+
throw usageError(`Trae CN usage API returned error code ${apiCode}.`, {
|
|
356
|
+
status: response.status,
|
|
357
|
+
apiCode: Number(apiCode),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
// Documented wrapper: data.user_usage_group_by_sessions, or the top-level
|
|
361
|
+
// equivalent. Only this module parses the wrapper; token rows/buckets are
|
|
362
|
+
// normalized elsewhere. A body without the session-array wrapper fails
|
|
363
|
+
// closed as a schema error instead of being misread as an empty result;
|
|
364
|
+
// an explicit empty array remains valid.
|
|
365
|
+
const data = parsed?.data && typeof parsed.data === "object" ? parsed.data : parsed;
|
|
366
|
+
if (!Array.isArray(data?.user_usage_group_by_sessions)) {
|
|
367
|
+
throw new Error("Trae CN usage API response is missing the session list.");
|
|
368
|
+
}
|
|
369
|
+
const sessions = data.user_usage_group_by_sessions;
|
|
370
|
+
// The declared total gates pagination termination (sessions.length >=
|
|
371
|
+
// total stops the walk), so a malformed value must fail closed as a
|
|
372
|
+
// schema error - never be coerced into a legal-looking stop condition.
|
|
373
|
+
// A negative, fractional, non-finite, or non-number total (the API
|
|
374
|
+
// speaks JSON numbers; a quoted digit string is a schema anomaly) would
|
|
375
|
+
// otherwise silently truncate the snapshot after page 1 and still look
|
|
376
|
+
// authoritative.
|
|
377
|
+
const rawTotal = data?.total;
|
|
378
|
+
let total = null;
|
|
379
|
+
if (rawTotal !== undefined && rawTotal !== null) {
|
|
380
|
+
if (
|
|
381
|
+
typeof rawTotal !== "number" ||
|
|
382
|
+
!Number.isSafeInteger(rawTotal) ||
|
|
383
|
+
rawTotal < 0
|
|
384
|
+
) {
|
|
385
|
+
throw new Error("Trae CN usage API returned an invalid total.");
|
|
386
|
+
}
|
|
387
|
+
total = rawTotal;
|
|
388
|
+
}
|
|
389
|
+
return { sessions, total, page_num, page_size };
|
|
390
|
+
} finally {
|
|
391
|
+
clearTimeout(timer);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Fetch usage across pages serially. Starts at the 1-based page_num, at most
|
|
397
|
+
* maxPages pages, with a delay between pages (pass delayMs = 0 to disable in
|
|
398
|
+
* tests). Stops at the stated total when present, otherwise on an empty page.
|
|
399
|
+
* Throws if maxPages is reached while another page would still be needed.
|
|
400
|
+
*/
|
|
401
|
+
async function fetchTraeCnUsage({
|
|
402
|
+
jwt,
|
|
403
|
+
start_time,
|
|
404
|
+
end_time,
|
|
405
|
+
page_num = 1,
|
|
406
|
+
page_size = TRAE_CN_USAGE_PAGE_SIZE,
|
|
407
|
+
fetchImpl = fetch,
|
|
408
|
+
maxPages = TRAE_CN_USAGE_MAX_PAGES,
|
|
409
|
+
delayMs = TRAE_CN_USAGE_DELAY_MS,
|
|
410
|
+
pageFetcher = fetchTraeCnUsagePage,
|
|
411
|
+
} = {}) {
|
|
412
|
+
const sessions = [];
|
|
413
|
+
let total = null;
|
|
414
|
+
let pagesFetched = 0;
|
|
415
|
+
let page = page_num;
|
|
416
|
+
for (;;) {
|
|
417
|
+
if (pagesFetched >= maxPages) {
|
|
418
|
+
// A further page would be needed but the cap prevents it: fail closed
|
|
419
|
+
// rather than return a partial snapshot that looks authoritative. Cap
|
|
420
|
+
// exhaustion is the same over-capacity signal as the declared-total
|
|
421
|
+
// check below (which the API may omit) — tag it identically so window
|
|
422
|
+
// splitting engages either way.
|
|
423
|
+
const error = new Error("Trae CN usage pagination exceeded the maximum page count.");
|
|
424
|
+
error.code = "TRAE_CN_USAGE_CAPACITY_EXCEEDED";
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
const result = await pageFetcher({ jwt, start_time, end_time, page_num: page, page_size, fetchImpl });
|
|
428
|
+
pagesFetched += 1;
|
|
429
|
+
total = result.total ?? null;
|
|
430
|
+
if (pagesFetched === 1 && Number.isFinite(total) && total > page_size * maxPages) {
|
|
431
|
+
const error = new Error("Trae CN usage snapshot exceeds the supported capacity.");
|
|
432
|
+
error.code = "TRAE_CN_USAGE_CAPACITY_EXCEEDED";
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
if (Array.isArray(result.sessions)) sessions.push(...result.sessions);
|
|
436
|
+
if (result.sessions.length === 0) break;
|
|
437
|
+
if (total !== null && sessions.length >= total) break;
|
|
438
|
+
if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
439
|
+
page += 1;
|
|
440
|
+
}
|
|
441
|
+
return { sessions, total, pages_fetched: pagesFetched };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Capacity-adaptive window fetch. Probing the official API confirmed the
|
|
446
|
+
* start_time/end_time range is closed on both ends ([a,b] and [b,c] both
|
|
447
|
+
* return the row at b) and that a row returned by two overlapping windows is
|
|
448
|
+
* byte-identical (idempotent duplicate, never cumulative). Splitting therefore
|
|
449
|
+
* staggers adjacent sub-windows by one second ([start,mid] + [mid+1,end]) so
|
|
450
|
+
* the union equals the full window exactly; any residual duplicate would be
|
|
451
|
+
* absorbed by the session-level reconciliation. On TRAE_CN_USAGE_CAPACITY_
|
|
452
|
+
* EXCEEDED the window halves recursively up to maxSplitDepth levels; a window
|
|
453
|
+
* still over capacity at the finest allowed granularity re-throws so the
|
|
454
|
+
* caller keeps its fail-closed guarantee.
|
|
455
|
+
*/
|
|
456
|
+
async function fetchTraeCnUsageWindowed(options = {}, depth = 0) {
|
|
457
|
+
const { start_time, end_time } = options;
|
|
458
|
+
try {
|
|
459
|
+
return await fetchTraeCnUsage(options);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error?.code !== "TRAE_CN_USAGE_CAPACITY_EXCEEDED") throw error;
|
|
462
|
+
if (end_time - start_time < 1 || depth >= TRAE_CN_USAGE_MAX_SPLIT_DEPTH) throw error;
|
|
463
|
+
const mid = start_time + Math.floor((end_time - start_time) / 2);
|
|
464
|
+
const left = await fetchTraeCnUsageWindowed({ ...options, end_time: mid }, depth + 1);
|
|
465
|
+
const right = await fetchTraeCnUsageWindowed({ ...options, start_time: mid + 1 }, depth + 1);
|
|
466
|
+
const total =
|
|
467
|
+
Number.isFinite(left.total) && Number.isFinite(right.total) ? left.total + right.total : null;
|
|
468
|
+
return {
|
|
469
|
+
sessions: [...left.sessions, ...right.sessions],
|
|
470
|
+
total,
|
|
471
|
+
pages_fetched: left.pages_fetched + right.pages_fetched,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* High-level storage-backed fetch: read the JWT from storage.json, fetch
|
|
478
|
+
* usage, and on a 401/403 re-read + re-decrypt storage.json and retry exactly
|
|
479
|
+
* once. Never persists or logs the JWT / refresh token; no refreshToken flow.
|
|
480
|
+
*/
|
|
481
|
+
async function fetchTraeCnUsageWithAuth({
|
|
482
|
+
start_time,
|
|
483
|
+
end_time,
|
|
484
|
+
page_num = 1,
|
|
485
|
+
page_size = TRAE_CN_USAGE_PAGE_SIZE,
|
|
486
|
+
fetchImpl = fetch,
|
|
487
|
+
delayMs = TRAE_CN_USAGE_DELAY_MS,
|
|
488
|
+
maxPages = TRAE_CN_USAGE_MAX_PAGES,
|
|
489
|
+
readAuth = readTraeCnAuthFromStorage,
|
|
490
|
+
usageFetcher = fetchTraeCnUsageWindowed,
|
|
491
|
+
...storageOptions
|
|
492
|
+
} = {}) {
|
|
493
|
+
let auth = readAuth(storageOptions);
|
|
494
|
+
let jwt = auth ? extractTraeCnToken(auth) : null;
|
|
495
|
+
if (!jwt) {
|
|
496
|
+
throw new Error("Trae CN credentials are not configured.");
|
|
497
|
+
}
|
|
498
|
+
const run = () =>
|
|
499
|
+
usageFetcher({ jwt, start_time, end_time, page_num, page_size, fetchImpl, delayMs, maxPages });
|
|
500
|
+
try {
|
|
501
|
+
return await run();
|
|
502
|
+
} catch (error) {
|
|
503
|
+
const authFailure =
|
|
504
|
+
error?.code === "AUTH_EXPIRED" || error?.status === 401 || error?.status === 403;
|
|
505
|
+
if (!authFailure) throw error;
|
|
506
|
+
// Session may have rotated since the first read; re-read storage once.
|
|
507
|
+
auth = readAuth(storageOptions);
|
|
508
|
+
jwt = auth ? extractTraeCnToken(auth) : null;
|
|
509
|
+
if (!jwt) throw error;
|
|
510
|
+
return await run();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
module.exports = {
|
|
515
|
+
TRAE_CN_HOME_ENV,
|
|
516
|
+
TRAE_CN_USAGE_ENV,
|
|
517
|
+
isTraeCnUsageEnabled,
|
|
518
|
+
TRAE_CN_APP_DIR,
|
|
519
|
+
TRAE_CN_AUTH_KEY,
|
|
520
|
+
TRAE_CN_USAGE_URL,
|
|
521
|
+
TRAE_CN_USAGE_TYPE,
|
|
522
|
+
TRAE_CN_USAGE_PAGE_SIZE,
|
|
523
|
+
TRAE_CN_USAGE_MAX_PAGES,
|
|
524
|
+
TRAE_CN_USAGE_DELAY_MS,
|
|
525
|
+
TRAE_CN_MAGIC,
|
|
526
|
+
resolveTraeCnHome,
|
|
527
|
+
resolveTraeCnStoragePath,
|
|
528
|
+
traeCnHardcodedKdfSecret,
|
|
529
|
+
deriveTraeCnKeyIv,
|
|
530
|
+
decryptTraeCnBlob,
|
|
531
|
+
decryptTraeCnBase64,
|
|
532
|
+
parseTraeCnAuthValue,
|
|
533
|
+
readTraeCnAuthFromStorage,
|
|
534
|
+
extractTraeCnToken,
|
|
535
|
+
fetchTraeCnUsagePage,
|
|
536
|
+
fetchTraeCnUsage,
|
|
537
|
+
fetchTraeCnUsageWindowed,
|
|
538
|
+
fetchTraeCnUsageWithAuth,
|
|
539
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,z as a}from"./__vite-browser-external-CRvi_9hb.js";import{u as p}from"./DialogTitle-DyvhHV1-.js";import{u as c}from"./useScrollLock-sLkmam8C.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};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{m as re,a2 as ce,a as ge,r as o,c as a,j as e,e as h}from"./__vite-browser-external-CRvi_9hb.js";const ue=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],me=re("arrow-left",ue);const xe=[["path",{d:"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7",key:"132q7q"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2",key:"izxlao"}]],he=re("mail",xe),be=e.jsxs("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",children:[e.jsx("path",{d:"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1Z",fill:"#4285F4"}),e.jsx("path",{d:"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23Z",fill:"#34A853"}),e.jsx("path",{d:"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18A10.96 10.96 0 0 0 1 12c0 1.77.42 3.44 1.18 4.93l3.66-2.84Z",fill:"#FBBC05"}),e.jsx("path",{d:"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53Z",fill:"#EA4335"})]}),fe=e.jsx("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"currentColor",children:e.jsx("path",{d:"M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12Z"})}),pe={google:be,github:fe},ye={google:"Google",github:"GitHub",microsoft:"Microsoft",discord:"Discord",apple:"Apple"};function we(i){return ye[i]||String(i||"").replace(/-/g," ")}function ke(i){if(typeof i=="string"&&i.trim())return i.trim();if(i&&typeof i=="object"){if(typeof i.token=="string"&&i.token.trim())return i.token.trim();if(typeof i.otp=="string"&&i.otp.trim())return i.otp.trim()}return""}function je({title:i,subtitle:H,className:ae="",onSuccess:S,hideLogo:D=!1,initialMode:p="signin",tokenFromUrl:P="",initialEmail:y="",initialCode:v=""}){ce();const{enabled:w,signedIn:j,refreshUser:E,signInWithPassword:G,signUp:Z,signInWithOAuth:T,getPublicAuthConfig:V,sendResetPasswordEmail:$,exchangeResetPasswordToken:U,resetPassword:W}=ge(),[n,u]=o.useState(p),[c,k]=o.useState(y),[m,N]=o.useState(""),[A,L]=o.useState(""),[M,te]=o.useState(""),[q,z]=o.useState(v),[_,x]=o.useState(!1),[J,s]=o.useState(null),[C,b]=o.useState(null),[F,O]=o.useState(!1),[oe,se]=o.useState(!!(p!=="signin"||y)),[R,K]=o.useState(!0),[I,Q]=o.useState([]),[X,ie]=o.useState(8);o.useEffect(()=>{p&&u(p)},[p]),o.useEffect(()=>{y&&k(y)},[y]),o.useEffect(()=>{v&&z(v)},[v]),o.useEffect(()=>{if(!w)return;let r=!0;return K(!0),(async()=>{const{data:t,error:l}=await V();if(r){if(l||!t)Q(["google","github"]);else{const d=Array.isArray(t.oAuthProviders)?t.oAuthProviders:[],f=Array.isArray(t.customOAuthProviders)?t.customOAuthProviders:[];Q([...d,...f]),typeof t.passwordMinLength=="number"&&t.passwordMinLength>0&&ie(t.passwordMinLength)}K(!1)}})(),()=>{r=!1}},[w,V]),o.useEffect(()=>{j&&S&&S()},[j,S]);const Y=o.useMemo(()=>typeof window>"u"?"":!!window.webkit?.messageHandlers?.nativeOAuth?`${window.location.origin}/auth/callback`:`${window.location.origin}/`,[]),ee=o.useMemo(()=>typeof window>"u"?"/reset-password":`${window.location.origin}/reset-password`,[]),B=o.useCallback(async r=>{s(null),x(!0);try{const{error:t}=await T(r,Y);t&&s(t.message||String(t))}finally{x(!1)}},[T,Y]);o.useEffect(()=>{if(!w||R||j||typeof window>"u")return;const r=window.location.search;if(!r)return;const t=new URLSearchParams(r),l=t.get("native")==="1",d=t.get("provider");l&&d&&[...I].includes(d)&&B(d)},[w,R,j,I,B]);const ne=o.useCallback(async r=>{r.preventDefault(),s(null),x(!0);try{if(n==="signup"){const{data:l,error:d}=await Z({email:c.trim(),password:m,name:M.trim()||void 0});if(d){s(d.message||String(d));return}if(l?.requireEmailVerification){b(a("login.verify_email_pending")),u("signin");return}await E();return}const{error:t}=await G({email:c.trim(),password:m});if(t){s(t.message||String(t));return}await E()}finally{x(!1)}},[n,c,m,M,Z,G,E]),le=o.useCallback(async r=>{r.preventDefault(),s(null),x(!0);try{const t=c.trim(),{error:l}=await $({email:t,redirectTo:ee});if(l){s(l.message||String(l));return}k(t),b(a("reset_password.request.sent")),u("reset_confirm")}finally{x(!1)}},[c,ee,$]),de=o.useCallback(async r=>{if(r.preventDefault(),s(null),m!==A){s(a("reset_password.error.password_mismatch"));return}x(!0);try{let t=P;if(!t){const{data:d,error:f}=await U({email:c.trim(),code:q.trim()});if(f){s(f.message||String(f));return}if(t=ke(d),!t){s(a("reset_password.error.missing_token"));return}}const{error:l}=await W({newPassword:m,otp:t});if(l){s(l.message||String(l));return}O(!0),b(a("reset_password.success"))}finally{x(!1)}},[q,A,c,U,m,W,P]);if(!w)return null;const g=n==="reset_email"||n==="reset_confirm";return e.jsxs("div",{className:h("w-full bg-white dark:bg-oai-gray-950 p-6 transition-colors duration-200",ae),children:[g&&e.jsxs("button",{type:"button",onClick:()=>{u("signin"),b(null),s(null),O(!1),N(""),L("")},className:"flex items-center gap-1.5 text-xs font-medium text-oai-gray-500 hover:text-oai-black dark:hover:text-white transition-colors mb-4 focus:outline-none",children:[e.jsx(me,{className:"h-3.5 w-3.5"}),a("reset_password.back_to_sign_in")]}),e.jsxs("div",{className:h("mb-6",g?"text-left":"text-center"),children:[!D&&!g&&e.jsxs("div",{className:"flex items-center justify-center gap-2 mb-2",children:[e.jsx("img",{src:"/app-icon.png",alt:"",width:28,height:28,className:"rounded-md"}),e.jsx("span",{className:"text-lg font-semibold text-oai-black dark:text-white font-oai tracking-tight",children:a("shared.app_name")})]}),e.jsx("h2",{className:h("text-sm font-semibold text-oai-black dark:text-white tracking-tight mb-1",(D||g)&&"text-base font-bold md:text-lg"),children:g?a("reset_password.title"):i||a("login_modal.subtitle")}),!g&&H&&e.jsx("p",{className:"text-xs text-oai-gray-500 dark:text-oai-gray-400 mt-1.5 leading-relaxed max-w-[320px] mx-auto",children:H}),g&&e.jsx("p",{className:"text-xs text-oai-gray-500 dark:text-oai-gray-400 mt-1.5 leading-relaxed max-w-[320px]",children:C&&n==="reset_confirm"&&!F?C:a("reset_password.subtitle")})]}),C&&(!g||F)&&e.jsx("div",{className:"mb-4 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900/50 px-3 py-2 text-xs text-oai-gray-700 dark:text-oai-gray-300",children:C}),J&&e.jsx("p",{className:"mb-4 text-sm text-red-500 dark:text-red-400 text-center",role:"alert",children:J}),n==="reset_email"&&e.jsxs("form",{onSubmit:le,className:"space-y-3",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-reset-email",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("login.field.email")}),e.jsx("input",{id:"card-reset-email",type:"email",autoComplete:"email",required:!0,value:c,onChange:r=>k(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500"})]}),e.jsx("button",{type:"submit",disabled:_,className:"w-full h-10 rounded-lg bg-oai-gray-900 dark:bg-white text-white dark:text-oai-gray-950 text-sm font-semibold hover:bg-oai-gray-800 dark:hover:bg-oai-gray-100 transition-colors disabled:opacity-50",children:a("reset_password.request.submit")})]}),n==="reset_confirm"&&(F?e.jsx("button",{type:"button",onClick:()=>{u("signin"),O(!1),b(null),s(null),N(""),L("")},className:"w-full h-10 rounded-lg bg-oai-gray-900 dark:bg-white text-white dark:text-oai-gray-950 text-sm font-semibold hover:bg-oai-gray-800 dark:hover:bg-oai-gray-100 transition-colors text-center shadow-sm",children:a("reset_password.success_cta")}):e.jsxs("form",{onSubmit:de,className:"space-y-3",children:[!P&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-reset-email-confirm",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("login.field.email")}),e.jsx("input",{id:"card-reset-email-confirm",type:"email",autoComplete:"email",required:!0,value:c,onChange:r=>k(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500"})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-reset-code",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("reset_password.field.code")}),e.jsx("input",{id:"card-reset-code",type:"text",inputMode:"numeric",autoComplete:"one-time-code",required:!0,value:q,onChange:r=>z(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500"})]})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-reset-password",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("reset_password.field.new_password")}),e.jsx("input",{id:"card-reset-password",type:"password",autoComplete:"new-password",required:!0,minLength:8,value:m,onChange:r=>N(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500"})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-reset-password-confirm",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("reset_password.field.confirm_password")}),e.jsx("input",{id:"card-reset-password-confirm",type:"password",autoComplete:"new-password",required:!0,minLength:8,value:A,onChange:r=>L(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500"})]}),e.jsx("button",{type:"submit",disabled:_,className:"w-full h-10 rounded-lg bg-oai-gray-900 dark:bg-white text-white dark:text-oai-gray-950 text-sm font-semibold hover:bg-oai-gray-800 dark:hover:bg-oai-gray-100 transition-colors disabled:opacity-50",children:a("reset_password.reset.submit")})]})),!g&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"space-y-2.5 mb-5",children:R?e.jsxs("div",{className:"space-y-2.5",children:[e.jsx("div",{className:"h-10 rounded-lg bg-oai-gray-100 dark:bg-oai-gray-900/50 animate-pulse"}),e.jsx("div",{className:"h-10 rounded-lg bg-oai-gray-100 dark:bg-oai-gray-900/50 animate-pulse"})]}):I.map(r=>e.jsxs("button",{type:"button",disabled:_,onClick:()=>B(r),className:h("w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-700 bg-oai-gray-50 dark:bg-oai-gray-900 text-sm font-medium text-oai-black dark:text-white","hover:bg-oai-gray-100 dark:hover:bg-oai-gray-800 hover:border-oai-gray-300 dark:hover:border-oai-gray-600 transition-colors disabled:opacity-50","flex items-center justify-center gap-2.5"),children:[pe[r]||null,a("login.oauth.continue",{provider:we(r)})]},r))}),oe?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"relative mb-5",children:[e.jsx("div",{className:"absolute inset-0 flex items-center",children:e.jsx("span",{className:"w-full border-t border-oai-gray-200 dark:border-oai-gray-800"})}),e.jsx("div",{className:"relative flex justify-center text-xs uppercase tracking-wider",children:e.jsx("span",{className:"bg-white dark:bg-oai-gray-950 px-3 text-oai-gray-400 dark:text-oai-gray-600",children:a("login_modal.divider_email")})})]}),e.jsxs("div",{className:"flex rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 p-0.5 bg-oai-gray-50 dark:bg-oai-gray-900/50 mb-4",children:[e.jsx("button",{type:"button",className:h("flex-1 py-1.5 text-xs font-medium rounded-md transition-colors",n==="signin"?"bg-white dark:bg-oai-gray-800 text-oai-black dark:text-white shadow-sm":"text-oai-gray-500 hover:text-oai-gray-700 dark:hover:text-oai-gray-300"),onClick:()=>{u("signin"),s(null)},children:a("login.tab.sign_in")}),e.jsx("button",{type:"button",className:h("flex-1 py-1.5 text-xs font-medium rounded-md transition-colors",n==="signup"?"bg-white dark:bg-oai-gray-800 text-oai-black dark:text-white shadow-sm":"text-oai-gray-500 hover:text-oai-gray-700 dark:hover:text-oai-gray-300"),onClick:()=>{u("signup"),s(null)},children:a("login.tab.sign_up")})]}),e.jsxs("form",{onSubmit:ne,className:"space-y-3",children:[n==="signup"&&e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-name",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("login.field.name")}),e.jsx("input",{id:"card-name",type:"text",autoComplete:"name",value:M,onChange:r=>te(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500",placeholder:a("login.field.name_placeholder")})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-email",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("login.field.email")}),e.jsx("input",{id:"card-email",type:"email",autoComplete:"email",required:!0,value:c,onChange:r=>k(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500"})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"card-password",className:"block text-xs font-medium text-oai-gray-500 mb-1",children:a("login.field.password")}),e.jsx("input",{id:"card-password",type:"password",autoComplete:n==="signup"?"new-password":"current-password",required:!0,minLength:n==="signup"?X:void 0,value:m,onChange:r=>N(r.target.value),className:"w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-oai-gray-50 dark:bg-oai-gray-900 px-3 text-sm text-oai-black dark:text-white placeholder-oai-gray-400 dark:placeholder-oai-gray-600 focus:outline-none focus:ring-2 focus:ring-oai-brand-500"}),n==="signup"&&e.jsx("p",{className:"mt-1 text-xs text-oai-gray-400 dark:text-oai-gray-600",children:a("login.password_hint",{min:String(X)})}),n==="signin"&&e.jsx("div",{className:"mt-1 text-right",children:e.jsx("button",{type:"button",onClick:()=>{u("reset_email"),s(null),b(null)},className:"text-xs font-medium text-oai-gray-500 hover:text-oai-black dark:hover:text-white no-underline bg-transparent border-0 p-0 cursor-pointer focus:outline-none",children:a("login.forgot_password")})})]}),e.jsx("button",{type:"submit",disabled:_,className:"w-full h-10 rounded-lg bg-oai-gray-900 dark:bg-white text-white dark:text-oai-gray-950 text-sm font-semibold hover:bg-oai-gray-800 dark:hover:bg-oai-gray-100 transition-colors disabled:opacity-50",children:n==="signup"?a("login.submit.sign_up"):a("login.submit.sign_in")})]})]}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"relative mb-5",children:[e.jsx("div",{className:"absolute inset-0 flex items-center",children:e.jsx("span",{className:"w-full border-t border-oai-gray-200 dark:border-oai-gray-800"})}),e.jsx("div",{className:"relative flex justify-center text-xs uppercase tracking-wider",children:e.jsx("span",{className:"bg-white dark:bg-oai-gray-950 px-3 text-oai-gray-400 dark:text-oai-gray-600",children:a("login.divider")})})]}),e.jsxs("button",{type:"button",onClick:()=>se(!0),className:h("w-full h-10 rounded-lg border border-oai-gray-200 dark:border-oai-gray-700 bg-oai-gray-50 dark:bg-oai-gray-900 text-sm font-medium text-oai-black dark:text-white","hover:bg-oai-gray-100 dark:hover:bg-oai-gray-800 hover:border-oai-gray-300 dark:hover:border-oai-gray-600 transition-colors","flex items-center justify-center gap-2.5"),children:[e.jsx(he,{className:"h-[18px] w-[18px] text-oai-gray-500 dark:text-oai-gray-400",strokeWidth:1.75}),a("login_modal.continue_email")]})]})]})]})}export{je as L};
|