tokentracker-cli 0.72.1 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +5 -4
- package/README.ja.md +5 -4
- package/README.ko.md +5 -4
- package/README.md +5 -4
- package/README.zh-CN.md +5 -4
- package/dashboard/dist/assets/{ActivityHeatmap-CtDL5OPO.js → ActivityHeatmap-DkoSqxrd.js} +1 -1
- package/dashboard/dist/assets/{Card-C5lKqKNT.js → Card-DleS-r54.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-XnEef7Ra.js → CommunityStatsModal-DV41-F9D.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CkZvn-sV.js → ConfirmModal-Dgp07Uka.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-TmZBLaB9.js → DashboardPage-EKy2BzUx.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-BhDh3VIP.js → DevicePage-eJInQ2UX.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-CddI1gcT.js → DialogClose-DEfesqFq.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-D6txiEc4.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-Cvpx9Lfv.js → DialogTitle-v1JrTvr4.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-BojLJcFD.js → FadeIn-hqIBbKaJ.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BrSt1RIj.js → HeaderGithubStar-C4zauuP2.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DxuF-1tF.js → IpCheckPage-CiRcccks.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-DmCjJVwh.js → LandingPage-CbOHycnd.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-B8SGUidW.js → LeaderboardAvatar-CQL1iOTS.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-D7CQCdDr.js → LeaderboardPage-BuEp247d.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-Dv7CQVqB.js → LeaderboardProfileModal-DT8hrPkI.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-BtGLbQnT.js → LeaderboardProfilePage-B-M0AXMb.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-DRf6h5_2.js → LimitsPage-Df2es3Xs.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-BnlODmxA.js → LocalOnlyNotice-DJIfKTKo.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CYXdV0X0.js → LoginPage-DtaaIy_8.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-x5vYN3uF.js → PopoverPopup-CqWH6aB9.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-D8ifLVwK.js → ResetPasswordPage-DCH5GU1c.js} +1 -1
- package/dashboard/dist/assets/{Select-CaaIuo5M.js → Select-BwyliVKJ.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-CdJ-E-d1.js → SelectItemText-B5_DKKEv.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-Dc7v5PEC.js → SettingsPage-BoAFdOUa.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-CRbEMatQ.js → SkillsPage-BVdre-kF.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-xgy8mLMV.js → WidgetsPage-CtjV405P.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BdpfOWJu.js → WrappedPage-DGmPdtHt.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-CrxAAMko.js → agent-logos-DqevdWvx.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-D23bZC75.js → arrow-up-right-DA5Pybq1.js} +1 -1
- package/dashboard/dist/assets/check-BWzAT1oX.js +1 -0
- package/dashboard/dist/assets/{copy-hiVk9JEl.js → copy-CsQRTd89.js} +1 -1
- package/dashboard/dist/assets/{download-CqLiBDG-.js → download-TEHkAKT0.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-Ck5lRlFd.js → format-tokens-CtFO_jkz.js} +1 -1
- package/dashboard/dist/assets/{info-CxDHBJXB.js → info-Dxn3fiaX.js} +1 -1
- package/dashboard/dist/assets/main-Dxi4sSXf.js +1128 -0
- package/dashboard/dist/assets/{use-community-stats-CeaLHlCk.js → use-community-stats-BLNxr-bQ.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CvDWYfjp.js → use-limits-display-prefs-CC7gb303.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-HuxDwmn6.js → use-native-settings-SYtjLlxZ.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-KIV2M5tf.js → use-ordered-list-_O8onJV7.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-BbsMhRJt.js → use-quality-per-dollar-pref-tuspH_6n.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-COBOepfU.js → use-usage-limits-whCVzQbf.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DbBq0Fal.js → useCurrency-BiDjYxfm.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-C4eBSnib.js → useScrollLock-DK-ndHcw.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/status.js +47 -4
- package/src/commands/sync.js +72 -18
- package/src/lib/local-api.js +24 -2
- package/src/lib/pricing/curated-overrides.json +7 -2
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +385 -0
- package/src/lib/sqlite-reader.js +24 -6
- package/src/lib/usage-limits.js +200 -29
- package/src/lib/zcode-limits.js +375 -40
- package/dashboard/dist/assets/DialogDescription-DeEet5bz.js +0 -1
- package/dashboard/dist/assets/check-fkZxgcVQ.js +0 -1
- package/dashboard/dist/assets/main-B0LYKUIY.js +0 -1127
package/src/lib/zcode-limits.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
|
+
const crypto = require("node:crypto");
|
|
2
3
|
const os = require("node:os");
|
|
3
4
|
const path = require("node:path");
|
|
4
5
|
|
|
5
6
|
const DEFAULT_BILLING_BASE_URL = "https://zcode.z.ai/api/v1/zcode-plan";
|
|
7
|
+
const DEFAULT_ZCODE_APP_VERSION = "3.2.5";
|
|
8
|
+
const DEFAULT_ZCODE_LOG_FALLBACK_MAX_AGE_MS = 6 * 60 * 60 * 1000;
|
|
6
9
|
|
|
7
10
|
function resolveZcodeHome({ home, env = process.env } = {}) {
|
|
8
11
|
if (typeof env.TOKENTRACKER_ZCODE_HOME === "string" && env.TOKENTRACKER_ZCODE_HOME.trim()) {
|
|
@@ -23,6 +26,51 @@ function resolveZcodeBillingBaseUrl(env = process.env) {
|
|
|
23
26
|
return DEFAULT_BILLING_BASE_URL;
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
function parsePositiveInteger(value, fallback) {
|
|
30
|
+
const n = Number(value);
|
|
31
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readZcodeAppVersionFromPlist(plistPath) {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(plistPath)) return null;
|
|
37
|
+
const text = fs.readFileSync(plistPath, "utf8");
|
|
38
|
+
const keyIndex = text.indexOf("<key>CFBundleShortVersionString</key>");
|
|
39
|
+
if (keyIndex < 0) return null;
|
|
40
|
+
const match = text.slice(keyIndex).match(/<string>([^<]+)<\/string>/);
|
|
41
|
+
const version = typeof match?.[1] === "string" ? match[1].trim() : "";
|
|
42
|
+
return version || null;
|
|
43
|
+
} catch (_error) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveZcodeAppVersion({ home, env = process.env } = {}) {
|
|
49
|
+
const explicit =
|
|
50
|
+
typeof env.TOKENTRACKER_ZCODE_APP_VERSION === "string"
|
|
51
|
+
? env.TOKENTRACKER_ZCODE_APP_VERSION.trim()
|
|
52
|
+
: "";
|
|
53
|
+
if (explicit) return explicit;
|
|
54
|
+
|
|
55
|
+
const appPath =
|
|
56
|
+
typeof env.TOKENTRACKER_ZCODE_APP_PATH === "string" && env.TOKENTRACKER_ZCODE_APP_PATH.trim()
|
|
57
|
+
? env.TOKENTRACKER_ZCODE_APP_PATH.trim()
|
|
58
|
+
: null;
|
|
59
|
+
const candidates = appPath
|
|
60
|
+
? [path.join(appPath, "Contents", "Info.plist")]
|
|
61
|
+
: [
|
|
62
|
+
"/Applications/ZCode.app/Contents/Info.plist",
|
|
63
|
+
path.join(home || os.homedir(), "Applications", "ZCode.app", "Contents", "Info.plist"),
|
|
64
|
+
];
|
|
65
|
+
for (const plistPath of candidates) {
|
|
66
|
+
const version = readZcodeAppVersionFromPlist(plistPath);
|
|
67
|
+
if (version) return version;
|
|
68
|
+
}
|
|
69
|
+
// The API currently rejects balance requests without app_version. The value is
|
|
70
|
+
// not used for local accounting, so keep a conservative fallback for CLI-only installs.
|
|
71
|
+
return DEFAULT_ZCODE_APP_VERSION;
|
|
72
|
+
}
|
|
73
|
+
|
|
26
74
|
function isZcodeInstalled({ home, env } = {}) {
|
|
27
75
|
const zcodeHome = resolveZcodeHome({ home, env });
|
|
28
76
|
const configPath = path.join(zcodeHome, "v2", "config.json");
|
|
@@ -31,34 +79,173 @@ function isZcodeInstalled({ home, env } = {}) {
|
|
|
31
79
|
return fs.existsSync(dbPath);
|
|
32
80
|
}
|
|
33
81
|
|
|
34
|
-
function
|
|
82
|
+
function loadZcodeProviderAvailability({ home, env } = {}) {
|
|
83
|
+
const zcodeHome = resolveZcodeHome({ home, env });
|
|
84
|
+
const cachePath = path.join(zcodeHome, "v2", "coding-plan-cache.json");
|
|
85
|
+
if (!fs.existsSync(cachePath)) return {};
|
|
86
|
+
try {
|
|
87
|
+
const cache = JSON.parse(fs.readFileSync(cachePath, "utf8"));
|
|
88
|
+
const items = cache?.entryStatus?.items;
|
|
89
|
+
return items && typeof items === "object" ? items : {};
|
|
90
|
+
} catch (_error) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveZcodeCredentialsPath({ home, env } = {}) {
|
|
96
|
+
return path.join(resolveZcodeHome({ home, env }), "v2", "credentials.json");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createZcodeCredentialSecret({ home, env = process.env } = {}) {
|
|
100
|
+
if (typeof env.ZCODE_CREDENTIAL_SECRET === "string" && env.ZCODE_CREDENTIAL_SECRET) {
|
|
101
|
+
return env.ZCODE_CREDENTIAL_SECRET;
|
|
102
|
+
}
|
|
103
|
+
let username = "";
|
|
104
|
+
try {
|
|
105
|
+
username = os.userInfo().username || "";
|
|
106
|
+
} catch (_error) {
|
|
107
|
+
username = "";
|
|
108
|
+
}
|
|
109
|
+
return `zcode-credential-fallback:${process.platform}:${home || os.homedir()}:${username}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function decryptZcodeCredentialValue(value, { home, env } = {}) {
|
|
113
|
+
if (typeof value !== "string") return null;
|
|
114
|
+
if (!value.startsWith("enc:v1:")) return value;
|
|
115
|
+
const encoded = value.slice("enc:v1:".length);
|
|
116
|
+
const parts = encoded.split(".");
|
|
117
|
+
if (parts.length !== 3) return null;
|
|
118
|
+
try {
|
|
119
|
+
const [ivPart, tagPart, encryptedPart] = parts;
|
|
120
|
+
const iv = Buffer.from(ivPart, "base64url");
|
|
121
|
+
const encrypted = Buffer.from(encryptedPart, "base64url");
|
|
122
|
+
const tag = Buffer.from(tagPart, "base64url");
|
|
123
|
+
const key = crypto.createHash("sha256").update(createZcodeCredentialSecret({ home, env })).digest();
|
|
124
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
125
|
+
decipher.setAuthTag(tag);
|
|
126
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
|
|
127
|
+
} catch (_error) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function loadZcodeCredentials({ home, env } = {}) {
|
|
133
|
+
const credentialsPath = resolveZcodeCredentialsPath({ home, env });
|
|
134
|
+
if (!fs.existsSync(credentialsPath)) return {};
|
|
135
|
+
try {
|
|
136
|
+
const raw = JSON.parse(fs.readFileSync(credentialsPath, "utf8"));
|
|
137
|
+
return raw && typeof raw === "object" ? raw : {};
|
|
138
|
+
} catch (_error) {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function loadZcodeCredential(name, { home, env } = {}) {
|
|
144
|
+
const credentials = loadZcodeCredentials({ home, env });
|
|
145
|
+
const decrypted = decryptZcodeCredentialValue(credentials?.[name], { home, env });
|
|
146
|
+
return typeof decrypted === "string" && decrypted.trim() ? decrypted.trim() : "";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function loadZcodeActiveProvider({ home, env } = {}) {
|
|
150
|
+
return loadZcodeCredential("oauth:active_provider", { home, env });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function resolveZcodeCredentialAuth(providerKey, { home, env } = {}) {
|
|
154
|
+
const activeProvider = loadZcodeActiveProvider({ home, env });
|
|
155
|
+
if (
|
|
156
|
+
(providerKey === "builtin:zai-start-plan" && activeProvider === "zai") ||
|
|
157
|
+
(providerKey === "builtin:bigmodel-start-plan" && activeProvider === "bigmodel")
|
|
158
|
+
) {
|
|
159
|
+
return loadZcodeCredential("zcodejwttoken", { home, env });
|
|
160
|
+
}
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isZcodeBuiltinPlanProvider(providerKey) {
|
|
165
|
+
return /^builtin:(bigmodel|zai)-(start|coding)-plan$/.test(providerKey);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resolveZcodeProviderBillingBaseUrl(providerKey, provider, env = process.env) {
|
|
169
|
+
const explicit = resolveZcodeBillingBaseUrl(env);
|
|
170
|
+
if (explicit !== DEFAULT_BILLING_BASE_URL) return explicit;
|
|
171
|
+
if (isZcodeBuiltinPlanProvider(providerKey)) return DEFAULT_BILLING_BASE_URL;
|
|
172
|
+
const baseUrl = typeof provider?.options?.baseURL === "string" ? provider.options.baseURL.trim() : "";
|
|
173
|
+
if (/\/zcode-plan\/anthropic\/?$/i.test(baseUrl)) return baseUrl.replace(/\/anthropic\/?$/i, "");
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function loadZcodeAuthCandidates({ home, env } = {}) {
|
|
35
178
|
const zcodeHome = resolveZcodeHome({ home, env });
|
|
36
179
|
const configPath = path.join(zcodeHome, "v2", "config.json");
|
|
37
|
-
if (!fs.existsSync(configPath)) return
|
|
180
|
+
if (!fs.existsSync(configPath)) return [];
|
|
38
181
|
try {
|
|
39
182
|
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
40
|
-
if (!config || typeof config !== "object") return
|
|
183
|
+
if (!config || typeof config !== "object") return [];
|
|
41
184
|
const providers = config.provider || {};
|
|
42
|
-
|
|
43
|
-
const candidates = [
|
|
185
|
+
const defaultCandidates = [
|
|
44
186
|
"builtin:bigmodel-start-plan",
|
|
45
187
|
"builtin:zai-start-plan",
|
|
46
188
|
"builtin:bigmodel-coding-plan",
|
|
47
189
|
"builtin:zai-coding-plan",
|
|
48
190
|
];
|
|
191
|
+
const availability = loadZcodeProviderAvailability({ home, env });
|
|
192
|
+
const hasAvailability = Object.keys(availability).length > 0;
|
|
193
|
+
const availableCandidates = defaultCandidates.filter((key) => availability?.[key]?.status === "available");
|
|
194
|
+
const candidates = [...availableCandidates, ...defaultCandidates.filter((key) => !availableCandidates.includes(key))];
|
|
195
|
+
const auths = [];
|
|
49
196
|
for (const key of candidates) {
|
|
50
197
|
const provider = providers[key];
|
|
51
198
|
if (!provider || typeof provider !== "object") continue;
|
|
52
199
|
if (provider.enabled === false) continue;
|
|
200
|
+
if (hasAvailability && availability?.[key]?.status && availability[key].status !== "available") continue;
|
|
53
201
|
const apiKey = typeof provider?.options?.apiKey === "string" ? provider.options.apiKey.trim() : "";
|
|
54
|
-
|
|
202
|
+
const billingBaseUrl = resolveZcodeProviderBillingBaseUrl(key, provider, env);
|
|
203
|
+
const credentialApiKey = resolveZcodeCredentialAuth(key, { home, env });
|
|
204
|
+
const authEntries = [
|
|
205
|
+
credentialApiKey ? { apiKey: credentialApiKey, authSource: "credential:zcodejwttoken" } : null,
|
|
206
|
+
apiKey ? { apiKey, authSource: "provider:config" } : null,
|
|
207
|
+
].filter(Boolean);
|
|
208
|
+
const seenKeys = new Set();
|
|
209
|
+
for (const entry of authEntries) {
|
|
210
|
+
if (!billingBaseUrl || seenKeys.has(entry.apiKey)) continue;
|
|
211
|
+
seenKeys.add(entry.apiKey);
|
|
212
|
+
auths.push({
|
|
213
|
+
apiKey: entry.apiKey,
|
|
214
|
+
auth_source: entry.authSource,
|
|
215
|
+
providerKey: key,
|
|
216
|
+
baseUrl: provider?.options?.baseURL || null,
|
|
217
|
+
billingBaseUrl,
|
|
218
|
+
availability: availability?.[key]?.status || null,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
55
221
|
}
|
|
56
|
-
return
|
|
222
|
+
return auths;
|
|
57
223
|
} catch (_error) {
|
|
58
|
-
return
|
|
224
|
+
return [];
|
|
59
225
|
}
|
|
60
226
|
}
|
|
61
227
|
|
|
228
|
+
function loadZcodeApiKey({ home, env } = {}) {
|
|
229
|
+
return loadZcodeAuthCandidates({ home, env })[0] || null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildZcodeSourceHeaders({ home, env } = {}) {
|
|
233
|
+
const headers = {
|
|
234
|
+
"User-Agent": `ZCode/${resolveZcodeAppVersion({ home, env })}`,
|
|
235
|
+
"HTTP-Referer": "https://zcode.z.ai/",
|
|
236
|
+
"X-ZCode-App-Version": resolveZcodeAppVersion({ home, env }),
|
|
237
|
+
"X-Platform": process.platform,
|
|
238
|
+
"X-Release-Channel": "stable",
|
|
239
|
+
"X-Client-Language": Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
|
|
240
|
+
"X-Client-Timezone": Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
|
|
241
|
+
"X-Os-Category": process.platform,
|
|
242
|
+
"X-Os-Version": os.release(),
|
|
243
|
+
};
|
|
244
|
+
const deviceMid = loadZcodeCredential("zcodefeedbackclientid", { home, env });
|
|
245
|
+
if (deviceMid) headers["X-Device-Mid"] = deviceMid;
|
|
246
|
+
return headers;
|
|
247
|
+
}
|
|
248
|
+
|
|
62
249
|
function zcodeValNumber(value) {
|
|
63
250
|
if (value == null) return null;
|
|
64
251
|
const n = Number(value);
|
|
@@ -108,7 +295,14 @@ function normalizeZcodeBalanceResponse(body) {
|
|
|
108
295
|
|
|
109
296
|
const balances = Array.isArray(data.balances) ? data.balances : [];
|
|
110
297
|
if (!balances.length) {
|
|
111
|
-
|
|
298
|
+
return {
|
|
299
|
+
server_time: zcodeValNumber(data.server_time),
|
|
300
|
+
plan_id: null,
|
|
301
|
+
plan_label: null,
|
|
302
|
+
buckets: [],
|
|
303
|
+
primary_window: null,
|
|
304
|
+
secondary_window: null,
|
|
305
|
+
};
|
|
112
306
|
}
|
|
113
307
|
|
|
114
308
|
const serverTime = zcodeValNumber(data.server_time);
|
|
@@ -150,61 +344,202 @@ function normalizeZcodeBalanceResponse(body) {
|
|
|
150
344
|
};
|
|
151
345
|
}
|
|
152
346
|
|
|
153
|
-
async function fetchZcodeBilling(apiKey, { fetchImpl = fetch, baseUrl, env } = {}) {
|
|
347
|
+
async function fetchZcodeBilling(apiKey, { fetchImpl = fetch, baseUrl, env, home } = {}) {
|
|
154
348
|
const root = (baseUrl || resolveZcodeBillingBaseUrl(env)).replace(/\/$/, "");
|
|
155
|
-
const
|
|
349
|
+
const url = new URL(`${root}/billing/balance`);
|
|
350
|
+
const appVersion = resolveZcodeAppVersion({ home, env });
|
|
351
|
+
if (appVersion) url.searchParams.set("app_version", appVersion);
|
|
352
|
+
const headers = {
|
|
353
|
+
Authorization: `Bearer ${apiKey}`,
|
|
354
|
+
Accept: "application/json",
|
|
355
|
+
};
|
|
356
|
+
if (url.origin === new URL(DEFAULT_BILLING_BASE_URL).origin) {
|
|
357
|
+
Object.assign(headers, buildZcodeSourceHeaders({ home, env }));
|
|
358
|
+
}
|
|
359
|
+
const res = await fetchImpl(url.toString(), {
|
|
156
360
|
method: "GET",
|
|
157
|
-
headers
|
|
158
|
-
Authorization: `Bearer ${apiKey}`,
|
|
159
|
-
Accept: "application/json",
|
|
160
|
-
},
|
|
361
|
+
headers,
|
|
161
362
|
});
|
|
162
363
|
if (res.status === 401 || res.status === 403) {
|
|
163
364
|
throw new Error("Not authenticated with ZCode. Run `zcode` in Terminal to log in.");
|
|
164
365
|
}
|
|
165
366
|
if (!res.ok) {
|
|
166
|
-
|
|
367
|
+
let detail = "";
|
|
368
|
+
try {
|
|
369
|
+
const body = await res.json();
|
|
370
|
+
if (body && typeof body === "object") {
|
|
371
|
+
const code = body.code != null ? ` code=${body.code}` : "";
|
|
372
|
+
const msg = body.msg ? ` msg=${body.msg}` : "";
|
|
373
|
+
detail = `${code}${msg}`;
|
|
374
|
+
}
|
|
375
|
+
} catch (_error) {
|
|
376
|
+
detail = "";
|
|
377
|
+
}
|
|
378
|
+
throw new Error(`ZCode billing API returned ${res.status}${detail}`);
|
|
167
379
|
}
|
|
168
380
|
return res.json();
|
|
169
381
|
}
|
|
170
382
|
|
|
171
|
-
|
|
383
|
+
function parseZcodeLogTimestamp(line) {
|
|
384
|
+
const match = line.match(/^\[(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})\]/);
|
|
385
|
+
if (!match) return null;
|
|
386
|
+
const [, year, month, day, hour, minute, second, ms] = match;
|
|
387
|
+
const timestamp = new Date(
|
|
388
|
+
Number(year),
|
|
389
|
+
Number(month) - 1,
|
|
390
|
+
Number(day),
|
|
391
|
+
Number(hour),
|
|
392
|
+
Number(minute),
|
|
393
|
+
Number(second),
|
|
394
|
+
Number(ms),
|
|
395
|
+
);
|
|
396
|
+
const time = timestamp.getTime();
|
|
397
|
+
return Number.isFinite(time) ? time : null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function formatZcodeLogDate(date) {
|
|
401
|
+
const y = date.getFullYear();
|
|
402
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
403
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
404
|
+
return `${y}-${m}-${d}`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function recentZcodeLogPaths({ home, env, nowMs = Date.now() } = {}) {
|
|
408
|
+
const logsDir = path.join(resolveZcodeHome({ home, env }), "v2", "logs");
|
|
409
|
+
const today = new Date(nowMs);
|
|
410
|
+
const yesterday = new Date(nowMs - 24 * 60 * 60 * 1000);
|
|
411
|
+
return [today, yesterday].map((d) => path.join(logsDir, `${formatZcodeLogDate(d)}.log`));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function extractZcodeBalanceLogRecord(line) {
|
|
415
|
+
if (!line.includes("[usage-stats] billing/balance 请求完成")) return null;
|
|
416
|
+
const jsonStart = line.indexOf("{");
|
|
417
|
+
if (jsonStart < 0) return null;
|
|
418
|
+
try {
|
|
419
|
+
const entry = JSON.parse(line.slice(jsonStart));
|
|
420
|
+
const body = entry?.payload;
|
|
421
|
+
if (!entry?.success || entry?.code !== 0 || !body || body?.code !== 0) return null;
|
|
422
|
+
if (!Array.isArray(body?.data?.balances) || body.data.balances.length === 0) return null;
|
|
423
|
+
const timestampMs = parseZcodeLogTimestamp(line);
|
|
424
|
+
if (timestampMs == null) return null;
|
|
425
|
+
return {
|
|
426
|
+
body,
|
|
427
|
+
providerKey: typeof entry.providerId === "string" ? entry.providerId : null,
|
|
428
|
+
timestampMs,
|
|
429
|
+
log_timestamp: new Date(timestampMs).toISOString(),
|
|
430
|
+
};
|
|
431
|
+
} catch (_error) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function loadLatestZcodeBalanceFromLogs({ home, env = process.env, providerKeys = [], nowMs = Date.now() } = {}) {
|
|
437
|
+
if (env.TOKENTRACKER_ZCODE_DISABLE_LOG_FALLBACK === "1") return null;
|
|
438
|
+
const maxAgeMs = parsePositiveInteger(env.TOKENTRACKER_ZCODE_LOG_MAX_AGE_MS, DEFAULT_ZCODE_LOG_FALLBACK_MAX_AGE_MS);
|
|
439
|
+
const preferredProviders = new Set(providerKeys.filter(Boolean));
|
|
440
|
+
let preferred = null;
|
|
441
|
+
let fallback = null;
|
|
442
|
+
for (const logPath of recentZcodeLogPaths({ home, env, nowMs })) {
|
|
443
|
+
if (!fs.existsSync(logPath)) continue;
|
|
444
|
+
let text = "";
|
|
445
|
+
try {
|
|
446
|
+
text = fs.readFileSync(logPath, "utf8");
|
|
447
|
+
} catch (_error) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const lines = text.split(/\r?\n/);
|
|
451
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
452
|
+
const record = extractZcodeBalanceLogRecord(lines[i]);
|
|
453
|
+
if (!record) continue;
|
|
454
|
+
const ageMs = nowMs - record.timestampMs;
|
|
455
|
+
if (ageMs < 0 || ageMs > maxAgeMs) continue;
|
|
456
|
+
if (!fallback || record.timestampMs > fallback.timestampMs) fallback = record;
|
|
457
|
+
if (preferredProviders.has(record.providerKey) && (!preferred || record.timestampMs > preferred.timestampMs)) {
|
|
458
|
+
preferred = record;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return preferred || fallback;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function zcodeLogFallbackResult(logRecord, errors = []) {
|
|
466
|
+
if (!logRecord) return null;
|
|
467
|
+
return {
|
|
468
|
+
configured: true,
|
|
469
|
+
error: null,
|
|
470
|
+
source: "zcode-log",
|
|
471
|
+
provider_key: logRecord.providerKey,
|
|
472
|
+
log_timestamp: logRecord.log_timestamp,
|
|
473
|
+
provider_errors: errors,
|
|
474
|
+
...normalizeZcodeBalanceResponse(logRecord.body),
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now() } = {}) {
|
|
172
479
|
if (!isZcodeInstalled({ home, env })) {
|
|
173
480
|
return { configured: false };
|
|
174
481
|
}
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
482
|
+
const authCandidates = loadZcodeAuthCandidates({ home, env });
|
|
483
|
+
if (!authCandidates.length) {
|
|
484
|
+
const logFallback = loadLatestZcodeBalanceFromLogs({ home, env, nowMs });
|
|
485
|
+
if (logFallback) return zcodeLogFallbackResult(logFallback, []);
|
|
177
486
|
return { configured: false };
|
|
178
487
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
488
|
+
const errors = [];
|
|
489
|
+
let emptySuccess = null;
|
|
490
|
+
for (const auth of authCandidates) {
|
|
491
|
+
try {
|
|
492
|
+
const body = await fetchZcodeBilling(auth.apiKey, {
|
|
493
|
+
fetchImpl,
|
|
494
|
+
baseUrl: auth.billingBaseUrl,
|
|
495
|
+
env,
|
|
496
|
+
home,
|
|
497
|
+
});
|
|
498
|
+
const apiCode = typeof body?.code === "number" ? body.code : null;
|
|
499
|
+
if (apiCode !== null && apiCode !== 0) {
|
|
500
|
+
throw new Error(`ZCode billing API error: code=${apiCode} msg=${body?.msg || "unknown"}`);
|
|
501
|
+
}
|
|
502
|
+
const normalized = normalizeZcodeBalanceResponse(body);
|
|
503
|
+
const result = {
|
|
504
|
+
configured: true,
|
|
505
|
+
error: null,
|
|
506
|
+
provider_key: auth.providerKey,
|
|
507
|
+
...normalized,
|
|
508
|
+
};
|
|
509
|
+
if (Array.isArray(normalized.buckets) && normalized.buckets.length === 0 && authCandidates.length > 1) {
|
|
510
|
+
emptySuccess = emptySuccess || result;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
514
|
+
} catch (error) {
|
|
515
|
+
errors.push(`${auth.providerKey}: ${error?.message || "Unknown error"}`);
|
|
190
516
|
}
|
|
191
|
-
return {
|
|
192
|
-
configured: true,
|
|
193
|
-
error: null,
|
|
194
|
-
...normalizeZcodeBalanceResponse(body),
|
|
195
|
-
};
|
|
196
|
-
} catch (error) {
|
|
197
|
-
return {
|
|
198
|
-
configured: true,
|
|
199
|
-
error: error?.message || "Unknown error",
|
|
200
|
-
};
|
|
201
517
|
}
|
|
518
|
+
if (emptySuccess) return emptySuccess;
|
|
519
|
+
const logFallback = loadLatestZcodeBalanceFromLogs({
|
|
520
|
+
home,
|
|
521
|
+
env,
|
|
522
|
+
providerKeys: authCandidates.map((auth) => auth.providerKey),
|
|
523
|
+
nowMs,
|
|
524
|
+
});
|
|
525
|
+
if (logFallback) return zcodeLogFallbackResult(logFallback, errors);
|
|
526
|
+
return {
|
|
527
|
+
configured: true,
|
|
528
|
+
error: errors[0] || "Unknown error",
|
|
529
|
+
provider_errors: errors,
|
|
530
|
+
};
|
|
202
531
|
}
|
|
203
532
|
|
|
204
533
|
module.exports = {
|
|
205
534
|
resolveZcodeHome,
|
|
206
535
|
resolveZcodeBillingBaseUrl,
|
|
536
|
+
resolveZcodeAppVersion,
|
|
207
537
|
isZcodeInstalled,
|
|
538
|
+
loadZcodeProviderAvailability,
|
|
539
|
+
loadZcodeCredential,
|
|
540
|
+
loadLatestZcodeBalanceFromLogs,
|
|
541
|
+
resolveZcodeProviderBillingBaseUrl,
|
|
542
|
+
loadZcodeAuthCandidates,
|
|
208
543
|
loadZcodeApiKey,
|
|
209
544
|
deriveZcodePlanLabel,
|
|
210
545
|
normalizeZcodeBalanceResponse,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,b9 as a}from"./main-B0LYKUIY.js";import{u as p}from"./DialogTitle-Cvpx9Lfv.js";import{u as c}from"./useScrollLock-C4eBSnib.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,{store: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{c}from"./main-B0LYKUIY.js";const e=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],t=c("check",e);export{t as C};
|