tokentracker-cli 0.57.1 → 0.59.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.ja.md +6 -5
- package/README.ko.md +6 -5
- package/README.md +7 -6
- package/README.zh-CN.md +6 -5
- package/dashboard/dist/assets/{ActivityHeatmap-BvbV3_J7.js → ActivityHeatmap-DrF_K5ys.js} +1 -1
- package/dashboard/dist/assets/{Card-DJZ6ntik.js → Card-B13p4G_t.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-BCWtoeRf.js +19 -0
- package/dashboard/dist/assets/{DevicePage-DW6XIKeU.js → DevicePage-Be88eLBY.js} +1 -1
- package/dashboard/dist/assets/{DialogTitle-BsrlMQVP.js → DialogTitle-BZmopQRt.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-XjqQkcHf.js → FadeIn-BEym_KpF.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DcwDZDfU.js → HeaderGithubStar-Ckhzj9HH.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DwWHgam0.js → IpCheckPage-CJb8fFzg.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BqtWjGwE.js → LandingPage-CjTv2ISs.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-BeBtwC-_.js → LeaderboardAvatar-Cf0GnSTi.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-0TwqZOxu.js → LeaderboardPage-rIE2Ygw4.js} +3 -3
- package/dashboard/dist/assets/LeaderboardProfileModal-CT876wK9.js +72 -0
- package/dashboard/dist/assets/{LeaderboardProfilePage-865y5FBU.js → LeaderboardProfilePage-EyJsH0Yf.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-DNg_cqiC.js +2 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-CTKip6yZ.js → LocalOnlyNotice-Dz8c-3ZD.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DhfoXUgm.js → LoginPage-iekpEtgT.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-3YotW-4M.js → PopoverPopup-BhT2-G0O.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-CqEEo2VL.js → ResetPasswordPage-B69CFuJ-.js} +1 -1
- package/dashboard/dist/assets/{Select-BjnA__CB.js → Select-SwlPWXxZ.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-CoQ0e4TL.js → SelectItemText-DpUKUC8A.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-ushnnb65.js → SettingsPage-_spYM3Kx.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-CW4mBNzt.js → SkillsPage-CfTvatpr.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BiwEsW3y.js → WidgetsPage-Cs0kJgks.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-vu2gEGcG.js → WrappedPage-9EChVGEl.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-CvsRxSzl.js → agent-logos-LiqKeIhs.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-DwYMKA3i.js → arrow-up-right-B7ItQsbP.js} +1 -1
- package/dashboard/dist/assets/{download-BpLBXlyg.js → download-ZBU9wTAZ.js} +1 -1
- package/dashboard/dist/assets/{info-D7gs3LAC.js → info-CExabEBS.js} +1 -1
- package/dashboard/dist/assets/{main-D2iJg7iA.js → main-CgiMNMS_.js} +15 -11
- package/dashboard/dist/assets/{main-Bb0Bwbp7.css → main-xFJe2FDa.css} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-bSTssszE.js +1 -0
- package/dashboard/dist/assets/{use-native-settings-C-JvsUqt.js → use-native-settings-C3pXNNqf.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DfKKdPYm.js → use-usage-limits-8f1hUzNE.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-CAzf_Pmi.js → useCurrency-CrMUx8Pg.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-DNuJyLo9.js → useScrollLock-BaJCW9Ak.js} +1 -1
- package/dashboard/dist/brand-logos/zcode.svg +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/commands/init.js +9 -2
- package/src/commands/status.js +11 -0
- package/src/commands/sync.js +45 -0
- package/src/lib/pricing/curated-overrides.json +6 -2
- package/src/lib/pricing/matcher.js +17 -2
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +40 -0
- package/src/lib/usage-limits.js +6 -1
- package/src/lib/zcode-limits.js +213 -0
- package/dashboard/dist/assets/DashboardPage-Bu3v5Fm2.js +0 -19
- package/dashboard/dist/assets/LeaderboardProfileModal-3HwkYdbM.js +0 -72
- package/dashboard/dist/assets/LimitsPage-CfGYwdDz.js +0 -2
- package/dashboard/dist/assets/use-limits-display-prefs-BDCL1zsb.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -2550,6 +2550,45 @@ function readMimoDbMessages(dbPath, sqliteOptions = {}) {
|
|
|
2550
2550
|
return all.filter((m) => isMimoNativeMessage(m.data));
|
|
2551
2551
|
}
|
|
2552
2552
|
|
|
2553
|
+
// ZCode is Z.ai's (Zhipu) coding agent — another OpenCode-fork that stores
|
|
2554
|
+
// assistant messages in the identical `message` table schema
|
|
2555
|
+
// (~/.zcode/cli/db/db.sqlite). Its own agent runs GLM models through Z.ai /
|
|
2556
|
+
// BigModel endpoints (providerID "builtin:zai-start-plan",
|
|
2557
|
+
// "builtin:bigmodel-coding-plan", …), but ZCode also lets users add CUSTOM
|
|
2558
|
+
// providers (a built-in feature beyond the Z.ai plan subscription) that point at
|
|
2559
|
+
// ANY model — MiMo, Sakana Fugu, any OpenAI-compatible proxy. A user-defined
|
|
2560
|
+
// provider is assigned a random UUID as its providerID (observed on a real box:
|
|
2561
|
+
// model "mimo-v2.5-pro" under providerID "265956bf-…"), so an allowlist of known
|
|
2562
|
+
// vendor keywords can NEVER match it and silently drops every custom-provider
|
|
2563
|
+
// turn (issue #216). Those turns live ONLY in this DB, so we must keep them or
|
|
2564
|
+
// they go uncounted entirely. The exception is the bundled
|
|
2565
|
+
// claude-code / codex / gemini-cli sub-agents ZCode can orchestrate: those carry
|
|
2566
|
+
// providerID "anthropic"/"openai"/"google" and write to ~/.claude / ~/.codex /
|
|
2567
|
+
// ~/.gemini, so the standalone Claude/Codex/Gemini parsers already count them —
|
|
2568
|
+
// DROP those here to avoid double-counting. Hence a blocklist (exclude the three
|
|
2569
|
+
// direct vendors), not an allowlist that would silently miss every third-party
|
|
2570
|
+
// model. Key off providerID, NEVER the model id — a GLM/Claude model the user
|
|
2571
|
+
// ran *inside* Claude Code is source=claude, so matching the model name would
|
|
2572
|
+
// re-count it. Mirrors the Mimo discipline.
|
|
2573
|
+
function isZcodeNativeMessage(data) {
|
|
2574
|
+
if (!data) return false;
|
|
2575
|
+
const provider = String(data.providerID || "").toLowerCase();
|
|
2576
|
+
if (!provider) return false;
|
|
2577
|
+
return !(
|
|
2578
|
+
provider.includes("anthropic") ||
|
|
2579
|
+
provider.includes("openai") ||
|
|
2580
|
+
provider.includes("google")
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// Read only genuine ZCode assistant messages (its own GLM models via Z.ai /
|
|
2585
|
+
// BigModel), dropping any bundled sub-agent turns. See isZcodeNativeMessage.
|
|
2586
|
+
function readZcodeDbMessages(dbPath, sqliteOptions = {}) {
|
|
2587
|
+
if (!dbPath || !fssync.existsSync(dbPath)) return [];
|
|
2588
|
+
const all = readOpencodeDbMessages(dbPath, sqliteOptions);
|
|
2589
|
+
return all.filter((m) => isZcodeNativeMessage(m.data));
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2553
2592
|
async function parseOpencodeDbIncremental({
|
|
2554
2593
|
dbMessages,
|
|
2555
2594
|
cursors,
|
|
@@ -9064,6 +9103,7 @@ module.exports = {
|
|
|
9064
9103
|
listOpencodeMessageFiles,
|
|
9065
9104
|
readOpencodeDbMessages,
|
|
9066
9105
|
readMimoDbMessages,
|
|
9106
|
+
readZcodeDbMessages,
|
|
9067
9107
|
resolveKiroDbPath,
|
|
9068
9108
|
resolveKiroJsonlPath,
|
|
9069
9109
|
resolveHermesPath,
|
package/src/lib/usage-limits.js
CHANGED
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
fetchCursorUsageSummary,
|
|
25
25
|
} = require("./cursor-config");
|
|
26
26
|
const { fetchGrokLimits } = require("./grok-limits");
|
|
27
|
+
const { fetchZcodeLimits } = require("./zcode-limits");
|
|
27
28
|
const { readSqliteJsonRows } = require("./sqlite-reader");
|
|
28
29
|
|
|
29
30
|
// 2-minute in-memory cache
|
|
@@ -2289,7 +2290,7 @@ async function fetchUsageLimitsUncached({
|
|
|
2289
2290
|
const freshClaudeCache = claudeToken ? readFreshClaudeLimitsCache({ home, nowMs }) : null;
|
|
2290
2291
|
|
|
2291
2292
|
const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
|
|
2292
|
-
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok] = await Promise.all([
|
|
2293
|
+
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode] = await Promise.all([
|
|
2293
2294
|
claudeToken && !freshClaudeCache && !claudeRetryAtMs
|
|
2294
2295
|
? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
|
|
2295
2296
|
(value) => ({ status: "fulfilled", value }),
|
|
@@ -2318,6 +2319,8 @@ async function fetchUsageLimitsUncached({
|
|
|
2318
2319
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
2319
2320
|
withProviderTimeout(fetchGrokLimits({ home, env, fetchImpl: providerFetch }), "Grok Build", providerTimeoutMs)
|
|
2320
2321
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
2322
|
+
withProviderTimeout(fetchZcodeLimits({ home, env, fetchImpl: providerFetch }), "ZCode", providerTimeoutMs)
|
|
2323
|
+
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
2321
2324
|
]);
|
|
2322
2325
|
|
|
2323
2326
|
let claude;
|
|
@@ -2399,6 +2402,7 @@ async function fetchUsageLimitsUncached({
|
|
|
2399
2402
|
antigravity: withPlanLabel(antigravity, antigravity.account_plan, "Antigravity"),
|
|
2400
2403
|
copilot: withPlanLabel(copilot, copilot.plan_name, "Copilot"),
|
|
2401
2404
|
grok: withPlanLabel(grok, null, "Grok"),
|
|
2405
|
+
zcode: withPlanLabel(zcode, zcode.plan_label, "ZCode"),
|
|
2402
2406
|
};
|
|
2403
2407
|
|
|
2404
2408
|
cache = { data, fetchedAt: nowMs };
|
|
@@ -2430,4 +2434,5 @@ module.exports = {
|
|
|
2430
2434
|
decryptCopilotAuthDbToken,
|
|
2431
2435
|
describeCopilotOtelStatus,
|
|
2432
2436
|
fetchGrokLimits,
|
|
2437
|
+
fetchZcodeLimits,
|
|
2433
2438
|
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const os = require("node:os");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BILLING_BASE_URL = "https://zcode.z.ai/api/v1/zcode-plan";
|
|
6
|
+
|
|
7
|
+
function resolveZcodeHome({ home, env = process.env } = {}) {
|
|
8
|
+
if (typeof env.TOKENTRACKER_ZCODE_HOME === "string" && env.TOKENTRACKER_ZCODE_HOME.trim()) {
|
|
9
|
+
return path.resolve(env.TOKENTRACKER_ZCODE_HOME.trim());
|
|
10
|
+
}
|
|
11
|
+
if (typeof env.ZCODE_HOME === "string" && env.ZCODE_HOME.trim()) {
|
|
12
|
+
return path.resolve(env.ZCODE_HOME.trim());
|
|
13
|
+
}
|
|
14
|
+
return path.join(home || os.homedir(), ".zcode");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveZcodeBillingBaseUrl(env = process.env) {
|
|
18
|
+
const explicit =
|
|
19
|
+
typeof env.TOKENTRACKER_ZCODE_BILLING_BASE_URL === "string"
|
|
20
|
+
? env.TOKENTRACKER_ZCODE_BILLING_BASE_URL.trim()
|
|
21
|
+
: "";
|
|
22
|
+
if (explicit) return explicit.replace(/\/$/, "");
|
|
23
|
+
return DEFAULT_BILLING_BASE_URL;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isZcodeInstalled({ home, env } = {}) {
|
|
27
|
+
const zcodeHome = resolveZcodeHome({ home, env });
|
|
28
|
+
const configPath = path.join(zcodeHome, "v2", "config.json");
|
|
29
|
+
if (fs.existsSync(configPath)) return true;
|
|
30
|
+
const dbPath = path.join(zcodeHome, "cli", "db", "db.sqlite");
|
|
31
|
+
return fs.existsSync(dbPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadZcodeApiKey({ home, env } = {}) {
|
|
35
|
+
const zcodeHome = resolveZcodeHome({ home, env });
|
|
36
|
+
const configPath = path.join(zcodeHome, "v2", "config.json");
|
|
37
|
+
if (!fs.existsSync(configPath)) return null;
|
|
38
|
+
try {
|
|
39
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
40
|
+
if (!config || typeof config !== "object") return null;
|
|
41
|
+
const providers = config.provider || {};
|
|
42
|
+
// Try the active start-plan first, then coding-plan variants
|
|
43
|
+
const candidates = [
|
|
44
|
+
"builtin:bigmodel-start-plan",
|
|
45
|
+
"builtin:zai-start-plan",
|
|
46
|
+
"builtin:bigmodel-coding-plan",
|
|
47
|
+
"builtin:zai-coding-plan",
|
|
48
|
+
];
|
|
49
|
+
for (const key of candidates) {
|
|
50
|
+
const provider = providers[key];
|
|
51
|
+
if (!provider || typeof provider !== "object") continue;
|
|
52
|
+
if (provider.enabled === false) continue;
|
|
53
|
+
const apiKey = typeof provider?.options?.apiKey === "string" ? provider.options.apiKey.trim() : "";
|
|
54
|
+
if (apiKey) return { apiKey, providerKey: key, baseUrl: provider?.options?.baseURL || null };
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
} catch (_error) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function zcodeValNumber(value) {
|
|
63
|
+
if (value == null) return null;
|
|
64
|
+
const n = Number(value);
|
|
65
|
+
return Number.isFinite(n) ? n : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function zcodeTsToIso(value) {
|
|
69
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
70
|
+
return new Date(value * 1000).toISOString();
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clampPercent(value) {
|
|
76
|
+
if (value === null || value === undefined || value === "") return null;
|
|
77
|
+
const n = Number(value);
|
|
78
|
+
if (!Number.isFinite(n)) return null;
|
|
79
|
+
if (n <= 0) return 0;
|
|
80
|
+
if (n >= 100) return 100;
|
|
81
|
+
return n;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildWindow({ usedPercent, resetAt }) {
|
|
85
|
+
const pct = clampPercent(usedPercent);
|
|
86
|
+
if (pct === null) return null;
|
|
87
|
+
return {
|
|
88
|
+
used_percent: pct,
|
|
89
|
+
reset_at: typeof resetAt === "string" && resetAt ? resetAt : null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Z.ai coding-plan ids look like "zcode-v3-start-plan-0615". The raw id reads
|
|
94
|
+
// terribly as a plan label, so extract just the human tier ("Start"/"Lite"/
|
|
95
|
+
// "Pro"/"Max"); fall back to null (→ bare "ZCode") when no known tier matches.
|
|
96
|
+
function deriveZcodePlanLabel(planId) {
|
|
97
|
+
if (typeof planId !== "string" || !planId) return null;
|
|
98
|
+
const m = planId.toLowerCase().match(/\b(lite|start|pro|max|team|enterprise)\b/);
|
|
99
|
+
if (!m) return null;
|
|
100
|
+
return m[1].charAt(0).toUpperCase() + m[1].slice(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeZcodeBalanceResponse(body) {
|
|
104
|
+
const data = body?.data;
|
|
105
|
+
if (!data || typeof data !== "object") {
|
|
106
|
+
throw new Error("Could not parse ZCode balance: missing data");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const balances = Array.isArray(data.balances) ? data.balances : [];
|
|
110
|
+
if (!balances.length) {
|
|
111
|
+
throw new Error("Could not parse ZCode balance: no balance buckets");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const serverTime = zcodeValNumber(data.server_time);
|
|
115
|
+
const buckets = balances.map((b) => {
|
|
116
|
+
const total = zcodeValNumber(b.total_units);
|
|
117
|
+
const used = zcodeValNumber(b.used_units);
|
|
118
|
+
const remaining = zcodeValNumber(b.remaining_units);
|
|
119
|
+
const periodEnd = zcodeValNumber(b.period_end) || zcodeValNumber(b.expires_at);
|
|
120
|
+
const resetAt = zcodeTsToIso(periodEnd);
|
|
121
|
+
const usedPercent =
|
|
122
|
+
total != null && total > 0 && used != null ? (used / total) * 100 : null;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
show_name: typeof b.show_name === "string" ? b.show_name : "",
|
|
126
|
+
entitlement_id: typeof b.entitlement_id === "string" ? b.entitlement_id : "",
|
|
127
|
+
total_units: total,
|
|
128
|
+
used_units: used,
|
|
129
|
+
remaining_units: remaining,
|
|
130
|
+
window: buildWindow({ usedPercent, resetAt }),
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Primary window: highest-priority bucket (GLM-5.2 typically)
|
|
135
|
+
// Secondary window: second bucket (GLM-5-Turbo typically)
|
|
136
|
+
const sorted = buckets.slice().sort((a, b) => {
|
|
137
|
+
const aTotal = a.total_units || 0;
|
|
138
|
+
const bTotal = b.total_units || 0;
|
|
139
|
+
return bTotal - aTotal;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const planId = typeof balances[0]?.plan_id === "string" ? balances[0].plan_id : null;
|
|
143
|
+
return {
|
|
144
|
+
server_time: serverTime,
|
|
145
|
+
plan_id: planId,
|
|
146
|
+
plan_label: deriveZcodePlanLabel(planId),
|
|
147
|
+
buckets: sorted,
|
|
148
|
+
primary_window: sorted[0]?.window || null,
|
|
149
|
+
secondary_window: sorted[1]?.window || null,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function fetchZcodeBilling(apiKey, { fetchImpl = fetch, baseUrl, env } = {}) {
|
|
154
|
+
const root = (baseUrl || resolveZcodeBillingBaseUrl(env)).replace(/\/$/, "");
|
|
155
|
+
const res = await fetchImpl(`${root}/billing/balance`, {
|
|
156
|
+
method: "GET",
|
|
157
|
+
headers: {
|
|
158
|
+
Authorization: `Bearer ${apiKey}`,
|
|
159
|
+
Accept: "application/json",
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
if (res.status === 401 || res.status === 403) {
|
|
163
|
+
throw new Error("Not authenticated with ZCode. Run `zcode` in Terminal to log in.");
|
|
164
|
+
}
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
throw new Error(`ZCode billing API returned ${res.status}`);
|
|
167
|
+
}
|
|
168
|
+
return res.json();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function fetchZcodeLimits({ home, env, fetchImpl = fetch } = {}) {
|
|
172
|
+
if (!isZcodeInstalled({ home, env })) {
|
|
173
|
+
return { configured: false };
|
|
174
|
+
}
|
|
175
|
+
const auth = loadZcodeApiKey({ home, env });
|
|
176
|
+
if (!auth) {
|
|
177
|
+
return { configured: false };
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const body = await fetchZcodeBilling(auth.apiKey, {
|
|
181
|
+
fetchImpl,
|
|
182
|
+
// Coding-plan baseURL is ".../zcode-plan/anthropic"; strip the trailing
|
|
183
|
+
// "/anthropic" (with or without a trailing slash) to reach the billing root.
|
|
184
|
+
baseUrl: auth.baseUrl ? auth.baseUrl.replace(/\/anthropic\/?$/, "") : undefined,
|
|
185
|
+
env,
|
|
186
|
+
});
|
|
187
|
+
const apiCode = typeof body?.code === "number" ? body.code : null;
|
|
188
|
+
if (apiCode !== null && apiCode !== 0) {
|
|
189
|
+
throw new Error(`ZCode billing API error: code=${apiCode} msg=${body?.msg || "unknown"}`);
|
|
190
|
+
}
|
|
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
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
resolveZcodeHome,
|
|
206
|
+
resolveZcodeBillingBaseUrl,
|
|
207
|
+
isZcodeInstalled,
|
|
208
|
+
loadZcodeApiKey,
|
|
209
|
+
deriveZcodePlanLabel,
|
|
210
|
+
normalizeZcodeBalanceResponse,
|
|
211
|
+
fetchZcodeBilling,
|
|
212
|
+
fetchZcodeLimits,
|
|
213
|
+
};
|