@v1nvn/zai 0.36.0 → 0.37.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.md +17 -17
- package/dist/index.js +150 -111
- package/dist/index.js.map +1 -1
- package/dist/usage.mjs +457 -418
- package/dist/usage.mjs.map +1 -1
- package/package.json +3 -3
package/dist/usage.mjs
CHANGED
|
@@ -21,8 +21,408 @@ function printUsageAndExit(program) {
|
|
|
21
21
|
console.error(program.helpInformation());
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
|
+
async function runMain(main) {
|
|
25
|
+
try {
|
|
26
|
+
await main();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(e.message);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
24
32
|
Number.MAX_SAFE_INTEGER;
|
|
25
33
|
//#endregion
|
|
34
|
+
//#region ../tokens/dist/assets/text-D_R3UWAk.js
|
|
35
|
+
function plain(text) {
|
|
36
|
+
return { text };
|
|
37
|
+
}
|
|
38
|
+
function dim(text) {
|
|
39
|
+
return {
|
|
40
|
+
text,
|
|
41
|
+
ink: "dim"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function bold(text) {
|
|
45
|
+
return {
|
|
46
|
+
text,
|
|
47
|
+
ink: "bold"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var MONTHS = [
|
|
51
|
+
"Jan",
|
|
52
|
+
"Feb",
|
|
53
|
+
"Mar",
|
|
54
|
+
"Apr",
|
|
55
|
+
"May",
|
|
56
|
+
"Jun",
|
|
57
|
+
"Jul",
|
|
58
|
+
"Aug",
|
|
59
|
+
"Sep",
|
|
60
|
+
"Oct",
|
|
61
|
+
"Nov",
|
|
62
|
+
"Dec"
|
|
63
|
+
];
|
|
64
|
+
var EIGHTHS = [
|
|
65
|
+
"",
|
|
66
|
+
"▏",
|
|
67
|
+
"▎",
|
|
68
|
+
"▍",
|
|
69
|
+
"▌",
|
|
70
|
+
"▋",
|
|
71
|
+
"▊",
|
|
72
|
+
"▉"
|
|
73
|
+
];
|
|
74
|
+
function rule() {
|
|
75
|
+
return "─".repeat(68);
|
|
76
|
+
}
|
|
77
|
+
function pad2(n) {
|
|
78
|
+
return String(n).padStart(2, "0");
|
|
79
|
+
}
|
|
80
|
+
function fmtTokens(n) {
|
|
81
|
+
if (n == null || Number.isNaN(n)) return "—";
|
|
82
|
+
if (n >= 1e9) return (n / 1e9).toFixed(1) + "B";
|
|
83
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
84
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
|
|
85
|
+
return String(n);
|
|
86
|
+
}
|
|
87
|
+
function fmtNum(n) {
|
|
88
|
+
return (n || 0).toLocaleString("en-US");
|
|
89
|
+
}
|
|
90
|
+
function padR(s, n) {
|
|
91
|
+
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
92
|
+
}
|
|
93
|
+
function padL(s, n) {
|
|
94
|
+
return s.length >= n ? s : " ".repeat(n - s.length) + s;
|
|
95
|
+
}
|
|
96
|
+
/** Fixed-width bar field (width cols): █ blocks + an eighth-fraction + trailing spaces. */
|
|
97
|
+
function barField(v, max, width) {
|
|
98
|
+
if (!v || v <= 0 || max <= 0) return " ".repeat(width);
|
|
99
|
+
const scaled = v / max * width;
|
|
100
|
+
let full = Math.floor(scaled);
|
|
101
|
+
let fi = Math.round((scaled - full) * 8);
|
|
102
|
+
if (fi === 8) {
|
|
103
|
+
full += 1;
|
|
104
|
+
fi = 0;
|
|
105
|
+
}
|
|
106
|
+
if (full === 0 && fi === 0) fi = 1;
|
|
107
|
+
let s = "█".repeat(Math.min(full, width));
|
|
108
|
+
if (full < width && fi > 0) s += EIGHTHS[fi] ?? "";
|
|
109
|
+
if (s.length < width) s += " ".repeat(width - s.length);
|
|
110
|
+
return s.slice(0, width);
|
|
111
|
+
}
|
|
112
|
+
/** Filled/empty meter: █ for used, ░ for remaining. */
|
|
113
|
+
function meter(pct, width) {
|
|
114
|
+
let filled = Math.round((pct || 0) / 100 * width);
|
|
115
|
+
filled = Math.max(0, Math.min(width, filled));
|
|
116
|
+
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
117
|
+
}
|
|
118
|
+
function renderLines(lines) {
|
|
119
|
+
return lines.map((line) => line.map((seg) => seg.text).join("")).join("\n");
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/format.ts
|
|
123
|
+
/**
|
|
124
|
+
* Usage-report line model: `reportLines` lays the report out once as ink-tagged
|
|
125
|
+
* segments (the pane draws those), and `render` joins them into the CLI's
|
|
126
|
+
* monospace terminal / hook-block `reason` bytes — no markdown; alignment comes
|
|
127
|
+
* from fixed-width columns and unicode block glyphs. Input is the parsed `data`
|
|
128
|
+
* of the three ZAI/ZHIPU monitor endpoints (model-usage, tool-usage,
|
|
129
|
+
* quota/limit — the last already passed through processQuotaLimit).
|
|
130
|
+
*/
|
|
131
|
+
var VBLOCKS = "▁▂▃▄▅▆▇";
|
|
132
|
+
function parseSlot(s) {
|
|
133
|
+
const m = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec(s ?? "");
|
|
134
|
+
if (!m) return null;
|
|
135
|
+
return {
|
|
136
|
+
h: +m[4],
|
|
137
|
+
day: `${MONTHS[+m[2] - 1]} ${m[3]}`,
|
|
138
|
+
time: `${m[4]}:${m[5]}`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Re-express a naive "YYYY-MM-DD HH:MM" string from one UTC offset to another.
|
|
143
|
+
* Offsets are minutes east of UTC (Beijing = 480, IST = 330). Pure — does not
|
|
144
|
+
* depend on the runtime timezone, so it is deterministic under test.
|
|
145
|
+
*/
|
|
146
|
+
function shiftSlot(s, fromOffsetMin, toOffsetMin) {
|
|
147
|
+
const m = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec(s);
|
|
148
|
+
if (!m) return null;
|
|
149
|
+
const utcMs = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]) - fromOffsetMin * 6e4;
|
|
150
|
+
const dt = new Date(utcMs + toOffsetMin * 6e4);
|
|
151
|
+
return {
|
|
152
|
+
h: dt.getUTCHours(),
|
|
153
|
+
day: `${MONTHS[dt.getUTCMonth()]} ${pad2(dt.getUTCDate())}`,
|
|
154
|
+
time: `${pad2(dt.getUTCHours())}:${pad2(dt.getUTCMinutes())}`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function friendlyTool(code) {
|
|
158
|
+
switch ((code ?? "").toLowerCase()) {
|
|
159
|
+
case "search-prime": return "web search";
|
|
160
|
+
case "web-reader": return "web read";
|
|
161
|
+
default: return code || "other";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function idleRuns(tok) {
|
|
165
|
+
const runs = [];
|
|
166
|
+
let i = 0;
|
|
167
|
+
while (i < tok.length) if ((tok[i] || 0) === 0) {
|
|
168
|
+
let j = i;
|
|
169
|
+
while (j < tok.length && (tok[j] || 0) === 0) j++;
|
|
170
|
+
runs.push({
|
|
171
|
+
i,
|
|
172
|
+
j: j - 1,
|
|
173
|
+
len: j - i
|
|
174
|
+
});
|
|
175
|
+
i = j;
|
|
176
|
+
} else i++;
|
|
177
|
+
return runs;
|
|
178
|
+
}
|
|
179
|
+
function idleLabel(run, x, slotFn) {
|
|
180
|
+
const a = slotFn(x[run.i]);
|
|
181
|
+
const b = slotFn(x[run.j]);
|
|
182
|
+
if (!a || !b) return `idle (${run.len}h)`;
|
|
183
|
+
return `idle ${`${a.day} ${a.time}`} → ${a.day === b.day ? b.time : `${b.day} ${b.time}`} (${run.len}h)`;
|
|
184
|
+
}
|
|
185
|
+
function isPeakBucket(s) {
|
|
186
|
+
const m = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):/.exec(s ?? "");
|
|
187
|
+
if (!m) return false;
|
|
188
|
+
const wd = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])).getUTCDay();
|
|
189
|
+
return wd >= 1 && wd <= 5 && +m[4] >= 14 && +m[4] <= 17;
|
|
190
|
+
}
|
|
191
|
+
/** Local-time start/end of the 14:00–18:00 Beijing peak window (e.g. 11:30 / 15:30). */
|
|
192
|
+
function localPeakWindow(apiOffsetMin, toOffsetMin) {
|
|
193
|
+
const a = shiftSlot("2000-01-03 14:00", apiOffsetMin, toOffsetMin);
|
|
194
|
+
const b = shiftSlot("2000-01-03 18:00", apiOffsetMin, toOffsetMin);
|
|
195
|
+
if (!a || !b) return null;
|
|
196
|
+
return {
|
|
197
|
+
start: a.time,
|
|
198
|
+
end: b.time
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Vertical bar chart of hourly tokens. Returns an array of plain-text lines
|
|
203
|
+
* (without the section header), or null if there are fewer than 2 buckets.
|
|
204
|
+
*
|
|
205
|
+
* Bars are scaled to `maxTok` across `ROWS` rows; the topmost filled cell of a
|
|
206
|
+
* bar uses a fractional lower-block glyph (▁▂▃▄▅▆▇) for sub-row height. The
|
|
207
|
+
* y-axis labels only the peak (top) and 0 (baseline); the header window and the
|
|
208
|
+
* caller's peak annotation carry the exact values.
|
|
209
|
+
*/
|
|
210
|
+
function hourlyVerticalChart({ x, tok, nh, maxTok, peakIdx, slotFn }) {
|
|
211
|
+
const ROWS = 6;
|
|
212
|
+
if (nh < 2 || maxTok <= 0) return null;
|
|
213
|
+
const lines = [];
|
|
214
|
+
const maxLab = fmtTokens(maxTok);
|
|
215
|
+
const L = maxLab.length;
|
|
216
|
+
const AXIS = 1 + L + 1;
|
|
217
|
+
const P0 = AXIS + 2;
|
|
218
|
+
const PW = 68 - P0;
|
|
219
|
+
const slot = Math.max(1, Math.floor(PW / nh));
|
|
220
|
+
const barW = slot >= 2 ? slot - 1 : 1;
|
|
221
|
+
function barLeft(i) {
|
|
222
|
+
return P0 + i * slot;
|
|
223
|
+
}
|
|
224
|
+
function barCenter(i) {
|
|
225
|
+
return barLeft(i) + Math.floor(barW / 2);
|
|
226
|
+
}
|
|
227
|
+
const usedCols = nh * slot;
|
|
228
|
+
function blank() {
|
|
229
|
+
return Array(68).fill(" ");
|
|
230
|
+
}
|
|
231
|
+
function glyph(i, r) {
|
|
232
|
+
const v = tok[i] || 0;
|
|
233
|
+
if (v <= 0) return " ";
|
|
234
|
+
const fill = v / maxTok * ROWS - r;
|
|
235
|
+
if (fill >= 1) return "█";
|
|
236
|
+
if (fill > 0) {
|
|
237
|
+
let e = Math.round(fill * 8);
|
|
238
|
+
if (e < 1) e = 1;
|
|
239
|
+
if (e > 7) e = 7;
|
|
240
|
+
return VBLOCKS[e - 1];
|
|
241
|
+
}
|
|
242
|
+
return " ";
|
|
243
|
+
}
|
|
244
|
+
for (let r = 5; r >= 0; r--) {
|
|
245
|
+
const line = blank();
|
|
246
|
+
line[AXIS] = r === 5 ? "┤" : "│";
|
|
247
|
+
if (r === 5) for (let k = 0; k < L; k++) line[AXIS - 1 - L + k] = maxLab[k];
|
|
248
|
+
for (let i = 0; i < nh; i++) {
|
|
249
|
+
const g = glyph(i, r);
|
|
250
|
+
if (g !== " ") for (let c = 0; c < barW; c++) line[barLeft(i) + c] = g;
|
|
251
|
+
}
|
|
252
|
+
lines.push(line.join("").trimEnd());
|
|
253
|
+
}
|
|
254
|
+
function slotOf(i) {
|
|
255
|
+
return slotFn(x[i]);
|
|
256
|
+
}
|
|
257
|
+
function isTick(i) {
|
|
258
|
+
if (i === 0 || i === nh - 1) return true;
|
|
259
|
+
const s = slotOf(i);
|
|
260
|
+
return !!s && s.h % 6 === 0;
|
|
261
|
+
}
|
|
262
|
+
const base = blank();
|
|
263
|
+
base[AXIS - 2] = "0";
|
|
264
|
+
base[AXIS] = "└";
|
|
265
|
+
for (let c = AXIS + 1; c < P0 + usedCols; c++) base[c] = "─";
|
|
266
|
+
for (let i = 0; i < nh; i++) if (isTick(i)) base[barCenter(i)] = "┬";
|
|
267
|
+
lines.push(base.join("").trimEnd());
|
|
268
|
+
const hr = blank();
|
|
269
|
+
let last = -10;
|
|
270
|
+
for (let i = 0; i < nh; i++) {
|
|
271
|
+
if (!isTick(i)) continue;
|
|
272
|
+
const col = barCenter(i);
|
|
273
|
+
if (col - last < 3) continue;
|
|
274
|
+
const s = slotOf(i);
|
|
275
|
+
const txt = s ? pad2(s.h) : " ";
|
|
276
|
+
hr[col] = txt[0];
|
|
277
|
+
hr[col + 1] = txt[1];
|
|
278
|
+
last = col;
|
|
279
|
+
}
|
|
280
|
+
lines.push(hr.join("").trimEnd());
|
|
281
|
+
const day = blank();
|
|
282
|
+
let lastDay = null;
|
|
283
|
+
last = -10;
|
|
284
|
+
for (let i = 0; i < nh; i++) {
|
|
285
|
+
if (!isTick(i)) continue;
|
|
286
|
+
const s = slotOf(i);
|
|
287
|
+
if (!s || s.day === lastDay) continue;
|
|
288
|
+
const col = barCenter(i);
|
|
289
|
+
if (col - last >= s.day.length && col + s.day.length <= 68) {
|
|
290
|
+
for (let k = 0; k < s.day.length; k++) day[col + k] = s.day[k];
|
|
291
|
+
lastDay = s.day;
|
|
292
|
+
last = col + s.day.length;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (day.some((c) => c !== " ")) lines.push(day.join("").trimEnd());
|
|
296
|
+
const mark = blank();
|
|
297
|
+
for (let i = 0; i < nh; i++) if (isPeakBucket(x[i])) mark[barCenter(i)] = "↑";
|
|
298
|
+
if (peakIdx >= 0) mark[barCenter(peakIdx)] = "◂";
|
|
299
|
+
if (mark.some((c) => c !== " ")) lines.push(mark.join("").trimEnd());
|
|
300
|
+
return lines;
|
|
301
|
+
}
|
|
302
|
+
function reportLines({ platform, model, tool, quota, apiOffsetMin = 480, localOffsetMin, now = /* @__PURE__ */ new Date() }) {
|
|
303
|
+
const out = [];
|
|
304
|
+
const toOffset = localOffsetMin != null ? localOffsetMin : -(/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
305
|
+
function convertSlot(s) {
|
|
306
|
+
return shiftSlot(s, apiOffsetMin, toOffset) ?? parseSlot(s);
|
|
307
|
+
}
|
|
308
|
+
const x = model.x_time ?? [];
|
|
309
|
+
const tok = model.tokensUsage ?? [];
|
|
310
|
+
const calls = model.modelCallCount ?? [];
|
|
311
|
+
const nh = x.length;
|
|
312
|
+
const tu = model.totalUsage ?? {};
|
|
313
|
+
const total = tu.totalTokensUsage != null ? tu.totalTokensUsage : tok.reduce((a, b) => a + (b || 0), 0);
|
|
314
|
+
const totalCalls = tu.totalModelCallCount != null ? tu.totalModelCallCount : calls.reduce((a, b) => a + (b || 0), 0);
|
|
315
|
+
let peakIdx = -1;
|
|
316
|
+
let peakTok = 0;
|
|
317
|
+
for (let i = 0; i < tok.length; i++) if (tok[i] > peakTok) {
|
|
318
|
+
peakTok = tok[i];
|
|
319
|
+
peakIdx = i;
|
|
320
|
+
}
|
|
321
|
+
const peakCalls = peakIdx >= 0 ? calls[peakIdx] || 0 : 0;
|
|
322
|
+
const pctPeak = total > 0 ? peakTok / total * 100 : 0;
|
|
323
|
+
const peakSlot = peakIdx >= 0 ? convertSlot(x[peakIdx]) : null;
|
|
324
|
+
const level = quota.level ? quota.level.charAt(0).toUpperCase() + quota.level.slice(1) : "";
|
|
325
|
+
const left = ` GLM Coding Plan${level ? " · " + level : ""}`;
|
|
326
|
+
const firstSlot = convertSlot(x[0]);
|
|
327
|
+
const lastSlot = convertSlot(x[nh - 1]);
|
|
328
|
+
const win = firstSlot && lastSlot ? `${firstSlot.day} ${firstSlot.time} → ${lastSlot.day} ${lastSlot.time} · ${nh}h` : platform;
|
|
329
|
+
out.push([plain(rule())]);
|
|
330
|
+
out.push([bold(left), dim(padL(win, 68 - left.length))]);
|
|
331
|
+
out.push([plain(rule())]);
|
|
332
|
+
const head = plain(` ${fmtTokens(total)} tokens across ${fmtNum(totalCalls)} model calls`);
|
|
333
|
+
out.push([]);
|
|
334
|
+
if (peakIdx >= 0 && peakSlot) {
|
|
335
|
+
out.push([head, plain(" — ")]);
|
|
336
|
+
out.push([bold(`${Math.round(pctPeak)}%`), plain(" of it in a single hour")]);
|
|
337
|
+
out.push([plain(` (${peakSlot.day} ${peakSlot.time}, ${fmtTokens(peakTok)} tokens / ${fmtNum(peakCalls)} calls).`)]);
|
|
338
|
+
} else out.push([head, plain(".")]);
|
|
339
|
+
const activeHours = tok.filter((t) => t > 0).length;
|
|
340
|
+
const longest = idleRuns(tok).filter((r) => r.len >= 2).sort((a, b) => b.len - a.len).at(0);
|
|
341
|
+
const tt = tool.totalUsage ?? {};
|
|
342
|
+
const searchN = tt.totalNetworkSearchCount || 0;
|
|
343
|
+
const readN = tt.totalWebReadMcpCount || 0;
|
|
344
|
+
const zreadN = tt.totalZreadMcpCount || 0;
|
|
345
|
+
const toolTotal = tt.totalSearchMcpCount != null ? tt.totalSearchMcpCount : searchN + readN + zreadN;
|
|
346
|
+
let peakWinActive = 0;
|
|
347
|
+
let peakWinTokens = 0;
|
|
348
|
+
for (let k = 0; k < nh; k++) if (isPeakBucket(x[k]) && tok[k] > 0) {
|
|
349
|
+
peakWinActive++;
|
|
350
|
+
peakWinTokens += tok[k];
|
|
351
|
+
}
|
|
352
|
+
const peakWinPct = total > 0 ? peakWinTokens / total * 100 : 0;
|
|
353
|
+
const peakWin = localPeakWindow(apiOffsetMin, toOffset);
|
|
354
|
+
out.push([]);
|
|
355
|
+
out.push([plain(` Peak ${padR(peakSlot ? peakSlot.day + " " + peakSlot.time : "—", 15)}${padL(fmtTokens(peakTok), 7)} tokens · ${padL(fmtNum(peakCalls), 5)} calls`)]);
|
|
356
|
+
out.push([plain(` Active ${padR(`${activeHours} / ${nh} hours`, 15)}${longest ? idleLabel(longest, x, convertSlot) : "no idle gaps"}`)]);
|
|
357
|
+
out.push([plain(` Tools ${padR(`${toolTotal} calls`, 15)}${searchN} searches · ${readN} reads${zreadN ? ` · ${zreadN} zread` : ""}`)]);
|
|
358
|
+
if (peakWin) out.push([
|
|
359
|
+
plain(` Peak hrs Mon–Fri ${peakWin.start}–${peakWin.end} · GLM-5.2 3× · ${peakWinActive}h active · ${fmtTokens(peakWinTokens)} (`),
|
|
360
|
+
bold(`${Math.round(peakWinPct)}%`),
|
|
361
|
+
plain(")")
|
|
362
|
+
]);
|
|
363
|
+
out.push([]);
|
|
364
|
+
out.push([dim(" Hourly tokens · ↑ peak hour " + "─".repeat(Math.max(0, 39)))]);
|
|
365
|
+
const chartLines = hourlyVerticalChart({
|
|
366
|
+
x,
|
|
367
|
+
tok,
|
|
368
|
+
nh,
|
|
369
|
+
maxTok: peakTok || 1,
|
|
370
|
+
peakIdx,
|
|
371
|
+
slotFn: convertSlot
|
|
372
|
+
});
|
|
373
|
+
if (chartLines) {
|
|
374
|
+
out.push(...chartLines.map((l) => [plain(l)]));
|
|
375
|
+
if (peakIdx >= 0 && peakSlot) out.push([plain(` ◂ peak ${peakSlot.day} ${peakSlot.time} ${fmtTokens(peakTok)} tokens · ${fmtNum(peakCalls)} calls`)]);
|
|
376
|
+
} else out.push([plain(" (not enough hourly data to chart)")]);
|
|
377
|
+
out.push([]);
|
|
378
|
+
out.push([dim(" Model mix " + "─".repeat(Math.max(0, 57)))]);
|
|
379
|
+
const mixSrc = model.modelSummaryList ?? model.modelDataList ?? [];
|
|
380
|
+
const denom = mixSrc.reduce((a, m) => a + (m.totalTokens ?? 0), 0) || 1;
|
|
381
|
+
const mixSorted = [...mixSrc].sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
|
|
382
|
+
for (const m of mixSorted) {
|
|
383
|
+
const pct = (m.totalTokens ?? 0) / denom * 100;
|
|
384
|
+
out.push([
|
|
385
|
+
plain(` ${padR(m.modelName || "?", 11)}${padL(fmtTokens(m.totalTokens), 8)} `),
|
|
386
|
+
bold(padL(pct.toFixed(1) + "%", 6)),
|
|
387
|
+
plain(` ${barField(pct, 100, 20)}`)
|
|
388
|
+
]);
|
|
389
|
+
}
|
|
390
|
+
out.push([]);
|
|
391
|
+
out.push([dim(" Limits " + "─".repeat(Math.max(0, 60)))]);
|
|
392
|
+
const bj = new Date(now.getTime() + apiOffsetMin * 6e4);
|
|
393
|
+
const bjDay = bj.getUTCDay();
|
|
394
|
+
const elapsedMin = bjDay >= 1 && bjDay <= 5 ? Math.max(0, Math.min(240, (bj.getUTCHours() - 14) * 60 + bj.getUTCMinutes())) : 0;
|
|
395
|
+
if (peakWin) out.push([dim(` ${padR("Peak", 16)}`), plain(`${padL(peakWin.start, 5)} ${meter(Math.round(elapsedMin / 240 * 100), 22)} ${peakWin.end}`)]);
|
|
396
|
+
const limits = quota.limits ?? [];
|
|
397
|
+
const mcp = limits.find((l) => /mcp/i.test(l.type ?? ""));
|
|
398
|
+
const tok5 = limits.find((l) => /token/i.test(l.type ?? ""));
|
|
399
|
+
if (tok5) {
|
|
400
|
+
const reset = new Date(tok5.nextResetTime ?? "").toLocaleTimeString("en-IN");
|
|
401
|
+
out.push([
|
|
402
|
+
dim(` ${padR("Tokens · 5h", 16)}`),
|
|
403
|
+
bold(padL(`${tok5.percentage || 0}%`, 5)),
|
|
404
|
+
plain(` ${meter(tok5.percentage, 22)} ${reset}`)
|
|
405
|
+
]);
|
|
406
|
+
}
|
|
407
|
+
if (tok5 && mcp) out.push([]);
|
|
408
|
+
if (mcp) {
|
|
409
|
+
const reset = new Date(mcp.nextResetTime ?? "").toLocaleString("en-IN");
|
|
410
|
+
out.push([
|
|
411
|
+
dim(` ${padR("MCP · this month", 16)}`),
|
|
412
|
+
bold(padL(`${mcp.percentage || 0}%`, 5)),
|
|
413
|
+
plain(` ${meter(mcp.percentage, 22)} ${reset}`)
|
|
414
|
+
]);
|
|
415
|
+
const parts = (mcp.usageDetails ?? []).map((d) => `${friendlyTool(d.modelCode)} ${fmtNum(d.usage)}`).join(" ");
|
|
416
|
+
if (parts) out.push([plain(` ${fmtNum(mcp.currentUsage)}M / ${fmtNum(mcp.totol)}M · ${parts}`)]);
|
|
417
|
+
}
|
|
418
|
+
out.push([]);
|
|
419
|
+
out.push([plain(rule())]);
|
|
420
|
+
return out;
|
|
421
|
+
}
|
|
422
|
+
function render(input) {
|
|
423
|
+
return renderLines(reportLines(input));
|
|
424
|
+
}
|
|
425
|
+
//#endregion
|
|
26
426
|
//#region ../../node_modules/commander/lib/error.js
|
|
27
427
|
/**
|
|
28
428
|
* CommanderError class
|
|
@@ -2982,429 +3382,70 @@ new Command();
|
|
|
2982
3382
|
var DEFAULT_BASE_URL = "https://api.z.ai";
|
|
2983
3383
|
var GLM_HOSTS = /* @__PURE__ */ new Set([
|
|
2984
3384
|
"api.z.ai",
|
|
2985
|
-
"dev.bigmodel.cn",
|
|
2986
|
-
"open.bigmodel.cn"
|
|
2987
|
-
]);
|
|
2988
|
-
function fromEnv(value) {
|
|
2989
|
-
return value ? value : void 0;
|
|
2990
|
-
}
|
|
2991
|
-
function originOf(value) {
|
|
2992
|
-
try {
|
|
2993
|
-
const url = new URL(value);
|
|
2994
|
-
return url.protocol === "https:" || url.protocol === "http:" ? url.origin : void 0;
|
|
2995
|
-
} catch {
|
|
2996
|
-
return;
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
function isGlm(origin) {
|
|
3000
|
-
return GLM_HOSTS.has(new URL(origin).hostname);
|
|
3001
|
-
}
|
|
3002
|
-
function resolveBaseUrl(flag, env) {
|
|
3003
|
-
if (flag !== void 0) {
|
|
3004
|
-
const origin = originOf(flag);
|
|
3005
|
-
if (origin === void 0) throw new Error(`invalid base URL: ${flag}`);
|
|
3006
|
-
return {
|
|
3007
|
-
glm: isGlm(origin),
|
|
3008
|
-
origin
|
|
3009
|
-
};
|
|
3010
|
-
}
|
|
3011
|
-
const inherited = fromEnv(env.ANTHROPIC_BASE_URL);
|
|
3012
|
-
const origin = inherited === void 0 ? void 0 : originOf(inherited);
|
|
3013
|
-
if (origin !== void 0 && isGlm(origin)) return {
|
|
3014
|
-
glm: true,
|
|
3015
|
-
origin
|
|
3016
|
-
};
|
|
3017
|
-
return {
|
|
3018
|
-
glm: false,
|
|
3019
|
-
origin: DEFAULT_BASE_URL
|
|
3020
|
-
};
|
|
3021
|
-
}
|
|
3022
|
-
function buildProgram() {
|
|
3023
|
-
return new Command().name("zai-usage").addOption(new Option("--auth-token <token>", "API key").env("ZAI_AUTH_TOKEN")).addOption(new Option("--base-url <url>", "base URL").env("ZAI_BASE_URL"));
|
|
3024
|
-
}
|
|
3025
|
-
function parseArgs(args) {
|
|
3026
|
-
const program = parseQuietly(buildProgram(), args);
|
|
3027
|
-
if (program === void 0) return;
|
|
3028
|
-
const { authToken, baseUrl } = program.opts();
|
|
3029
|
-
return {
|
|
3030
|
-
authToken: authToken || void 0,
|
|
3031
|
-
baseUrl: baseUrl || void 0
|
|
3032
|
-
};
|
|
3033
|
-
}
|
|
3034
|
-
function resolveConfig(env, parsed) {
|
|
3035
|
-
const base = resolveBaseUrl(parsed.baseUrl, env);
|
|
3036
|
-
const token = parsed.authToken ?? (base.glm ? fromEnv(env.ANTHROPIC_AUTH_TOKEN) : void 0);
|
|
3037
|
-
if (token === void 0) throw new Error(env.ANTHROPIC_AUTH_TOKEN ? "ANTHROPIC_AUTH_TOKEN is set, but the resolved base URL does not name a GLM host, so it is not a GLM Coding Plan token — set ZAI_AUTH_TOKEN (or --auth-token)" : "no API key: set ZAI_AUTH_TOKEN (or --auth-token)");
|
|
3038
|
-
return {
|
|
3039
|
-
token,
|
|
3040
|
-
url: base.origin
|
|
3041
|
-
};
|
|
3042
|
-
}
|
|
3043
|
-
//#endregion
|
|
3044
|
-
//#region ../tokens/dist/text.js
|
|
3045
|
-
/**
|
|
3046
|
-
* Plain-text rendering primitives for the fixed-width reports. Output targets
|
|
3047
|
-
* a monospace terminal — the pane — so everything here is fixed-width:
|
|
3048
|
-
* padding, block-glyph bars, and compact number formatting.
|
|
3049
|
-
*/
|
|
3050
|
-
var MONTHS = [
|
|
3051
|
-
"Jan",
|
|
3052
|
-
"Feb",
|
|
3053
|
-
"Mar",
|
|
3054
|
-
"Apr",
|
|
3055
|
-
"May",
|
|
3056
|
-
"Jun",
|
|
3057
|
-
"Jul",
|
|
3058
|
-
"Aug",
|
|
3059
|
-
"Sep",
|
|
3060
|
-
"Oct",
|
|
3061
|
-
"Nov",
|
|
3062
|
-
"Dec"
|
|
3063
|
-
];
|
|
3064
|
-
var EIGHTHS = [
|
|
3065
|
-
"",
|
|
3066
|
-
"▏",
|
|
3067
|
-
"▎",
|
|
3068
|
-
"▍",
|
|
3069
|
-
"▌",
|
|
3070
|
-
"▋",
|
|
3071
|
-
"▊",
|
|
3072
|
-
"▉"
|
|
3073
|
-
];
|
|
3074
|
-
function rule() {
|
|
3075
|
-
return "─".repeat(68);
|
|
3076
|
-
}
|
|
3077
|
-
function pad2(n) {
|
|
3078
|
-
return String(n).padStart(2, "0");
|
|
3079
|
-
}
|
|
3080
|
-
function fmtTokens(n) {
|
|
3081
|
-
if (n == null || Number.isNaN(n)) return "—";
|
|
3082
|
-
if (n >= 1e9) return (n / 1e9).toFixed(1) + "B";
|
|
3083
|
-
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
3084
|
-
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
|
|
3085
|
-
return String(n);
|
|
3086
|
-
}
|
|
3087
|
-
function fmtNum(n) {
|
|
3088
|
-
return (n || 0).toLocaleString("en-US");
|
|
3089
|
-
}
|
|
3090
|
-
function padR(s, n) {
|
|
3091
|
-
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
3092
|
-
}
|
|
3093
|
-
function padL(s, n) {
|
|
3094
|
-
return s.length >= n ? s : " ".repeat(n - s.length) + s;
|
|
3095
|
-
}
|
|
3096
|
-
/** Fixed-width bar field (width cols): █ blocks + an eighth-fraction + trailing spaces. */
|
|
3097
|
-
function barField(v, max, width) {
|
|
3098
|
-
if (!v || v <= 0 || max <= 0) return " ".repeat(width);
|
|
3099
|
-
const scaled = v / max * width;
|
|
3100
|
-
let full = Math.floor(scaled);
|
|
3101
|
-
let fi = Math.round((scaled - full) * 8);
|
|
3102
|
-
if (fi === 8) {
|
|
3103
|
-
full += 1;
|
|
3104
|
-
fi = 0;
|
|
3105
|
-
}
|
|
3106
|
-
if (full === 0 && fi === 0) fi = 1;
|
|
3107
|
-
let s = "█".repeat(Math.min(full, width));
|
|
3108
|
-
if (full < width && fi > 0) s += EIGHTHS[fi] ?? "";
|
|
3109
|
-
if (s.length < width) s += " ".repeat(width - s.length);
|
|
3110
|
-
return s.slice(0, width);
|
|
3111
|
-
}
|
|
3112
|
-
/** Filled/empty meter: █ for used, ░ for remaining. */
|
|
3113
|
-
function meter(pct, width) {
|
|
3114
|
-
let filled = Math.round((pct || 0) / 100 * width);
|
|
3115
|
-
filled = Math.max(0, Math.min(width, filled));
|
|
3116
|
-
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
3117
|
-
}
|
|
3118
|
-
//#endregion
|
|
3119
|
-
//#region src/format.ts
|
|
3120
|
-
/**
|
|
3121
|
-
* Plain-text usage report renderer.
|
|
3122
|
-
*
|
|
3123
|
-
* Rendered for a monospace terminal / hook-block `reason`, so it must NOT rely on
|
|
3124
|
-
* markdown (bold, tables, fenced code). Alignment comes from fixed-width columns
|
|
3125
|
-
* and unicode block glyphs. Input is the parsed `data` of the three ZAI/ZHIPU
|
|
3126
|
-
* monitor endpoints (model-usage, tool-usage, quota/limit — the last already
|
|
3127
|
-
* passed through processQuotaLimit).
|
|
3128
|
-
*/
|
|
3129
|
-
var VBLOCKS = "▁▂▃▄▅▆▇";
|
|
3130
|
-
function parseSlot(s) {
|
|
3131
|
-
const m = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec(s ?? "");
|
|
3132
|
-
if (!m) return null;
|
|
3133
|
-
return {
|
|
3134
|
-
h: +m[4],
|
|
3135
|
-
day: `${MONTHS[+m[2] - 1]} ${m[3]}`,
|
|
3136
|
-
time: `${m[4]}:${m[5]}`
|
|
3137
|
-
};
|
|
3138
|
-
}
|
|
3139
|
-
/**
|
|
3140
|
-
* Re-express a naive "YYYY-MM-DD HH:MM" string from one UTC offset to another.
|
|
3141
|
-
* Offsets are minutes east of UTC (Beijing = 480, IST = 330). Pure — does not
|
|
3142
|
-
* depend on the runtime timezone, so it is deterministic under test.
|
|
3143
|
-
*/
|
|
3144
|
-
function shiftSlot(s, fromOffsetMin, toOffsetMin) {
|
|
3145
|
-
const m = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec(s);
|
|
3146
|
-
if (!m) return null;
|
|
3147
|
-
const utcMs = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]) - fromOffsetMin * 6e4;
|
|
3148
|
-
const dt = new Date(utcMs + toOffsetMin * 6e4);
|
|
3149
|
-
return {
|
|
3150
|
-
h: dt.getUTCHours(),
|
|
3151
|
-
day: `${MONTHS[dt.getUTCMonth()]} ${pad2(dt.getUTCDate())}`,
|
|
3152
|
-
time: `${pad2(dt.getUTCHours())}:${pad2(dt.getUTCMinutes())}`
|
|
3153
|
-
};
|
|
3385
|
+
"dev.bigmodel.cn",
|
|
3386
|
+
"open.bigmodel.cn"
|
|
3387
|
+
]);
|
|
3388
|
+
function fromEnv(value) {
|
|
3389
|
+
return value ? value : void 0;
|
|
3154
3390
|
}
|
|
3155
|
-
function
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3391
|
+
function originOf(value) {
|
|
3392
|
+
try {
|
|
3393
|
+
const url = new URL(value);
|
|
3394
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.origin : void 0;
|
|
3395
|
+
} catch {
|
|
3396
|
+
return;
|
|
3160
3397
|
}
|
|
3161
3398
|
}
|
|
3162
|
-
function
|
|
3163
|
-
|
|
3164
|
-
let i = 0;
|
|
3165
|
-
while (i < tok.length) if ((tok[i] || 0) === 0) {
|
|
3166
|
-
let j = i;
|
|
3167
|
-
while (j < tok.length && (tok[j] || 0) === 0) j++;
|
|
3168
|
-
runs.push({
|
|
3169
|
-
i,
|
|
3170
|
-
j: j - 1,
|
|
3171
|
-
len: j - i
|
|
3172
|
-
});
|
|
3173
|
-
i = j;
|
|
3174
|
-
} else i++;
|
|
3175
|
-
return runs;
|
|
3399
|
+
function isGlm(origin) {
|
|
3400
|
+
return GLM_HOSTS.has(new URL(origin).hostname);
|
|
3176
3401
|
}
|
|
3177
|
-
function
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3402
|
+
function resolveBaseUrl(flag, env) {
|
|
3403
|
+
if (flag !== void 0) {
|
|
3404
|
+
const origin = originOf(flag);
|
|
3405
|
+
if (origin === void 0) throw new Error(`invalid base URL: ${flag}`);
|
|
3406
|
+
return {
|
|
3407
|
+
glm: isGlm(origin),
|
|
3408
|
+
origin
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
const inherited = fromEnv(env.ANTHROPIC_BASE_URL);
|
|
3412
|
+
const origin = inherited === void 0 ? void 0 : originOf(inherited);
|
|
3413
|
+
if (origin !== void 0 && isGlm(origin)) return {
|
|
3414
|
+
glm: true,
|
|
3415
|
+
origin
|
|
3416
|
+
};
|
|
3417
|
+
return {
|
|
3418
|
+
glm: false,
|
|
3419
|
+
origin: DEFAULT_BASE_URL
|
|
3420
|
+
};
|
|
3182
3421
|
}
|
|
3183
|
-
function
|
|
3184
|
-
|
|
3185
|
-
if (!m) return false;
|
|
3186
|
-
const wd = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])).getUTCDay();
|
|
3187
|
-
return wd >= 1 && wd <= 5 && +m[4] >= 14 && +m[4] <= 17;
|
|
3422
|
+
function buildProgram() {
|
|
3423
|
+
return new Command().name("zai-usage").addOption(new Option("--auth-token <token>", "API key").env("ZAI_AUTH_TOKEN")).addOption(new Option("--base-url <url>", "base URL").env("ZAI_BASE_URL")).addOption(new Option("--json", "print the report lines as JSON for the zai mod"));
|
|
3188
3424
|
}
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
const
|
|
3193
|
-
if (!a || !b) return null;
|
|
3425
|
+
function parseArgs(args) {
|
|
3426
|
+
const program = parseQuietly(buildProgram(), args);
|
|
3427
|
+
if (program === void 0) return;
|
|
3428
|
+
const { authToken, baseUrl, json } = program.opts();
|
|
3194
3429
|
return {
|
|
3195
|
-
|
|
3196
|
-
|
|
3430
|
+
authToken: authToken || void 0,
|
|
3431
|
+
baseUrl: baseUrl || void 0,
|
|
3432
|
+
json: json === true
|
|
3197
3433
|
};
|
|
3198
3434
|
}
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
*/
|
|
3208
|
-
function hourlyVerticalChart({ x, tok, nh, maxTok, peakIdx, slotFn }) {
|
|
3209
|
-
const ROWS = 6;
|
|
3210
|
-
if (nh < 2 || maxTok <= 0) return null;
|
|
3211
|
-
const lines = [];
|
|
3212
|
-
const maxLab = fmtTokens(maxTok);
|
|
3213
|
-
const L = maxLab.length;
|
|
3214
|
-
const AXIS = 1 + L + 1;
|
|
3215
|
-
const P0 = AXIS + 2;
|
|
3216
|
-
const PW = 68 - P0;
|
|
3217
|
-
const slot = Math.max(1, Math.floor(PW / nh));
|
|
3218
|
-
const barW = slot >= 2 ? slot - 1 : 1;
|
|
3219
|
-
function barLeft(i) {
|
|
3220
|
-
return P0 + i * slot;
|
|
3221
|
-
}
|
|
3222
|
-
function barCenter(i) {
|
|
3223
|
-
return barLeft(i) + Math.floor(barW / 2);
|
|
3224
|
-
}
|
|
3225
|
-
const usedCols = nh * slot;
|
|
3226
|
-
function blank() {
|
|
3227
|
-
return Array(68).fill(" ");
|
|
3228
|
-
}
|
|
3229
|
-
function glyph(i, r) {
|
|
3230
|
-
const v = tok[i] || 0;
|
|
3231
|
-
if (v <= 0) return " ";
|
|
3232
|
-
const fill = v / maxTok * ROWS - r;
|
|
3233
|
-
if (fill >= 1) return "█";
|
|
3234
|
-
if (fill > 0) {
|
|
3235
|
-
let e = Math.round(fill * 8);
|
|
3236
|
-
if (e < 1) e = 1;
|
|
3237
|
-
if (e > 7) e = 7;
|
|
3238
|
-
return VBLOCKS[e - 1];
|
|
3239
|
-
}
|
|
3240
|
-
return " ";
|
|
3241
|
-
}
|
|
3242
|
-
for (let r = 5; r >= 0; r--) {
|
|
3243
|
-
const line = blank();
|
|
3244
|
-
line[AXIS] = r === 5 ? "┤" : "│";
|
|
3245
|
-
if (r === 5) for (let k = 0; k < L; k++) line[AXIS - 1 - L + k] = maxLab[k];
|
|
3246
|
-
for (let i = 0; i < nh; i++) {
|
|
3247
|
-
const g = glyph(i, r);
|
|
3248
|
-
if (g !== " ") for (let c = 0; c < barW; c++) line[barLeft(i) + c] = g;
|
|
3249
|
-
}
|
|
3250
|
-
lines.push(line.join("").trimEnd());
|
|
3251
|
-
}
|
|
3252
|
-
function slotOf(i) {
|
|
3253
|
-
return slotFn(x[i]);
|
|
3254
|
-
}
|
|
3255
|
-
function isTick(i) {
|
|
3256
|
-
if (i === 0 || i === nh - 1) return true;
|
|
3257
|
-
const s = slotOf(i);
|
|
3258
|
-
return !!s && s.h % 6 === 0;
|
|
3259
|
-
}
|
|
3260
|
-
const base = blank();
|
|
3261
|
-
base[AXIS - 2] = "0";
|
|
3262
|
-
base[AXIS] = "└";
|
|
3263
|
-
for (let c = AXIS + 1; c < P0 + usedCols; c++) base[c] = "─";
|
|
3264
|
-
for (let i = 0; i < nh; i++) if (isTick(i)) base[barCenter(i)] = "┬";
|
|
3265
|
-
lines.push(base.join("").trimEnd());
|
|
3266
|
-
const hr = blank();
|
|
3267
|
-
let last = -10;
|
|
3268
|
-
for (let i = 0; i < nh; i++) {
|
|
3269
|
-
if (!isTick(i)) continue;
|
|
3270
|
-
const col = barCenter(i);
|
|
3271
|
-
if (col - last < 3) continue;
|
|
3272
|
-
const s = slotOf(i);
|
|
3273
|
-
const txt = s ? pad2(s.h) : " ";
|
|
3274
|
-
hr[col] = txt[0];
|
|
3275
|
-
hr[col + 1] = txt[1];
|
|
3276
|
-
last = col;
|
|
3277
|
-
}
|
|
3278
|
-
lines.push(hr.join("").trimEnd());
|
|
3279
|
-
const day = blank();
|
|
3280
|
-
let lastDay = null;
|
|
3281
|
-
last = -10;
|
|
3282
|
-
for (let i = 0; i < nh; i++) {
|
|
3283
|
-
if (!isTick(i)) continue;
|
|
3284
|
-
const s = slotOf(i);
|
|
3285
|
-
if (!s || s.day === lastDay) continue;
|
|
3286
|
-
const col = barCenter(i);
|
|
3287
|
-
if (col - last >= s.day.length && col + s.day.length <= 68) {
|
|
3288
|
-
for (let k = 0; k < s.day.length; k++) day[col + k] = s.day[k];
|
|
3289
|
-
lastDay = s.day;
|
|
3290
|
-
last = col + s.day.length;
|
|
3291
|
-
}
|
|
3292
|
-
}
|
|
3293
|
-
if (day.some((c) => c !== " ")) lines.push(day.join("").trimEnd());
|
|
3294
|
-
const mark = blank();
|
|
3295
|
-
for (let i = 0; i < nh; i++) if (isPeakBucket(x[i])) mark[barCenter(i)] = "↑";
|
|
3296
|
-
if (peakIdx >= 0) mark[barCenter(peakIdx)] = "◂";
|
|
3297
|
-
if (mark.some((c) => c !== " ")) lines.push(mark.join("").trimEnd());
|
|
3298
|
-
return lines;
|
|
3299
|
-
}
|
|
3300
|
-
function render({ platform, model, tool, quota, apiOffsetMin = 480, localOffsetMin, now = /* @__PURE__ */ new Date() }) {
|
|
3301
|
-
const out = [];
|
|
3302
|
-
const toOffset = localOffsetMin != null ? localOffsetMin : -(/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
3303
|
-
function convertSlot(s) {
|
|
3304
|
-
return shiftSlot(s, apiOffsetMin, toOffset) ?? parseSlot(s);
|
|
3305
|
-
}
|
|
3306
|
-
const x = model.x_time ?? [];
|
|
3307
|
-
const tok = model.tokensUsage ?? [];
|
|
3308
|
-
const calls = model.modelCallCount ?? [];
|
|
3309
|
-
const nh = x.length;
|
|
3310
|
-
const tu = model.totalUsage ?? {};
|
|
3311
|
-
const total = tu.totalTokensUsage != null ? tu.totalTokensUsage : tok.reduce((a, b) => a + (b || 0), 0);
|
|
3312
|
-
const totalCalls = tu.totalModelCallCount != null ? tu.totalModelCallCount : calls.reduce((a, b) => a + (b || 0), 0);
|
|
3313
|
-
let peakIdx = -1;
|
|
3314
|
-
let peakTok = 0;
|
|
3315
|
-
for (let i = 0; i < tok.length; i++) if (tok[i] > peakTok) {
|
|
3316
|
-
peakTok = tok[i];
|
|
3317
|
-
peakIdx = i;
|
|
3318
|
-
}
|
|
3319
|
-
const peakCalls = peakIdx >= 0 ? calls[peakIdx] || 0 : 0;
|
|
3320
|
-
const pctPeak = total > 0 ? peakTok / total * 100 : 0;
|
|
3321
|
-
const peakSlot = peakIdx >= 0 ? convertSlot(x[peakIdx]) : null;
|
|
3322
|
-
const level = quota.level ? quota.level.charAt(0).toUpperCase() + quota.level.slice(1) : "";
|
|
3323
|
-
const left = ` GLM Coding Plan${level ? " · " + level : ""}`;
|
|
3324
|
-
const firstSlot = convertSlot(x[0]);
|
|
3325
|
-
const lastSlot = convertSlot(x[nh - 1]);
|
|
3326
|
-
const win = firstSlot && lastSlot ? `${firstSlot.day} ${firstSlot.time} → ${lastSlot.day} ${lastSlot.time} · ${nh}h` : platform;
|
|
3327
|
-
out.push(rule());
|
|
3328
|
-
out.push(left + padL(win, 68 - left.length));
|
|
3329
|
-
out.push(rule());
|
|
3330
|
-
let lead = ` ${fmtTokens(total)} tokens across ${fmtNum(totalCalls)} model calls`;
|
|
3331
|
-
if (peakIdx >= 0 && peakSlot) lead += ` — ${Math.round(pctPeak)}% of it in a single hour (${peakSlot.day} ${peakSlot.time}, ${fmtTokens(peakTok)} tokens / ${fmtNum(peakCalls)} calls)`;
|
|
3332
|
-
lead += ".";
|
|
3333
|
-
out.push("");
|
|
3334
|
-
out.push(lead);
|
|
3335
|
-
const activeHours = tok.filter((t) => t > 0).length;
|
|
3336
|
-
const longest = idleRuns(tok).filter((r) => r.len >= 2).sort((a, b) => b.len - a.len).at(0);
|
|
3337
|
-
const tt = tool.totalUsage ?? {};
|
|
3338
|
-
const searchN = tt.totalNetworkSearchCount || 0;
|
|
3339
|
-
const readN = tt.totalWebReadMcpCount || 0;
|
|
3340
|
-
const zreadN = tt.totalZreadMcpCount || 0;
|
|
3341
|
-
const toolTotal = tt.totalSearchMcpCount != null ? tt.totalSearchMcpCount : searchN + readN + zreadN;
|
|
3342
|
-
let peakWinActive = 0;
|
|
3343
|
-
let peakWinTokens = 0;
|
|
3344
|
-
for (let k = 0; k < nh; k++) if (isPeakBucket(x[k]) && tok[k] > 0) {
|
|
3345
|
-
peakWinActive++;
|
|
3346
|
-
peakWinTokens += tok[k];
|
|
3347
|
-
}
|
|
3348
|
-
const peakWinPct = total > 0 ? peakWinTokens / total * 100 : 0;
|
|
3349
|
-
const peakWin = localPeakWindow(apiOffsetMin, toOffset);
|
|
3350
|
-
out.push("");
|
|
3351
|
-
out.push(` Peak ${padR(peakSlot ? peakSlot.day + " " + peakSlot.time : "—", 15)}${padL(fmtTokens(peakTok), 7)} tokens · ${padL(fmtNum(peakCalls), 5)} calls`);
|
|
3352
|
-
out.push(` Active ${padR(`${activeHours} / ${nh} hours`, 15)}${longest ? idleLabel(longest, x, convertSlot) : "no idle gaps"}`);
|
|
3353
|
-
out.push(` Tools ${padR(`${toolTotal} calls`, 15)}${searchN} searches · ${readN} reads${zreadN ? ` · ${zreadN} zread` : ""}`);
|
|
3354
|
-
if (peakWin) out.push(` Peak hrs Mon–Fri ${peakWin.start}–${peakWin.end} · GLM-5.2 3× · ${peakWinActive}h active · ${fmtTokens(peakWinTokens)} (${Math.round(peakWinPct)}%)`);
|
|
3355
|
-
out.push("");
|
|
3356
|
-
out.push(" Hourly tokens · ↑ peak hour " + "─".repeat(Math.max(0, 39)));
|
|
3357
|
-
const chartLines = hourlyVerticalChart({
|
|
3358
|
-
x,
|
|
3359
|
-
tok,
|
|
3360
|
-
nh,
|
|
3361
|
-
maxTok: peakTok || 1,
|
|
3362
|
-
peakIdx,
|
|
3363
|
-
slotFn: convertSlot
|
|
3364
|
-
});
|
|
3365
|
-
if (chartLines) {
|
|
3366
|
-
out.push(...chartLines);
|
|
3367
|
-
if (peakIdx >= 0 && peakSlot) out.push(` ◂ peak ${peakSlot.day} ${peakSlot.time} ${fmtTokens(peakTok)} tokens · ${fmtNum(peakCalls)} calls`);
|
|
3368
|
-
} else out.push(" (not enough hourly data to chart)");
|
|
3369
|
-
out.push("");
|
|
3370
|
-
out.push(" Model mix " + "─".repeat(Math.max(0, 57)));
|
|
3371
|
-
const mixSrc = model.modelSummaryList ?? model.modelDataList ?? [];
|
|
3372
|
-
const denom = mixSrc.reduce((a, m) => a + (m.totalTokens ?? 0), 0) || 1;
|
|
3373
|
-
const mixSorted = [...mixSrc].sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
|
|
3374
|
-
for (const m of mixSorted) {
|
|
3375
|
-
const pct = (m.totalTokens ?? 0) / denom * 100;
|
|
3376
|
-
out.push(` ${padR(m.modelName || "?", 11)}${padL(fmtTokens(m.totalTokens), 8)} ${padL(pct.toFixed(1) + "%", 6)} ${barField(pct, 100, 20)}`);
|
|
3377
|
-
}
|
|
3378
|
-
out.push("");
|
|
3379
|
-
out.push(" Limits " + "─".repeat(Math.max(0, 60)));
|
|
3380
|
-
const bj = new Date(now.getTime() + apiOffsetMin * 6e4);
|
|
3381
|
-
const bjDay = bj.getUTCDay();
|
|
3382
|
-
const elapsedMin = bjDay >= 1 && bjDay <= 5 ? Math.max(0, Math.min(240, (bj.getUTCHours() - 14) * 60 + bj.getUTCMinutes())) : 0;
|
|
3383
|
-
if (peakWin) out.push(` ${padR("Peak", 16)}${padL(peakWin.start, 5)} ${meter(Math.round(elapsedMin / 240 * 100), 22)} ${peakWin.end}`);
|
|
3384
|
-
const limits = quota.limits ?? [];
|
|
3385
|
-
const mcp = limits.find((l) => /mcp/i.test(l.type ?? ""));
|
|
3386
|
-
const tok5 = limits.find((l) => /token/i.test(l.type ?? ""));
|
|
3387
|
-
if (tok5) {
|
|
3388
|
-
const reset = new Date(tok5.nextResetTime ?? "").toLocaleTimeString("en-IN");
|
|
3389
|
-
out.push(` ${padR("Tokens · 5h", 16)}${padL(`${tok5.percentage || 0}%`, 5)} ${meter(tok5.percentage, 22)} ${reset}`);
|
|
3390
|
-
}
|
|
3391
|
-
if (tok5 && mcp) out.push("");
|
|
3392
|
-
if (mcp) {
|
|
3393
|
-
const reset = new Date(mcp.nextResetTime ?? "").toLocaleString("en-IN");
|
|
3394
|
-
out.push(` ${padR("MCP · this month", 16)}${padL(`${mcp.percentage || 0}%`, 5)} ${meter(mcp.percentage, 22)} ${reset}`);
|
|
3395
|
-
const parts = (mcp.usageDetails ?? []).map((d) => `${friendlyTool(d.modelCode)} ${fmtNum(d.usage)}`).join(" ");
|
|
3396
|
-
if (parts) out.push(` ${fmtNum(mcp.currentUsage)}M / ${fmtNum(mcp.totol)}M · ${parts}`);
|
|
3397
|
-
}
|
|
3398
|
-
out.push("");
|
|
3399
|
-
out.push(rule());
|
|
3400
|
-
return out.join("\n");
|
|
3435
|
+
function resolveConfig(env, parsed) {
|
|
3436
|
+
const base = resolveBaseUrl(parsed.baseUrl, env);
|
|
3437
|
+
const token = parsed.authToken ?? (base.glm ? fromEnv(env.ANTHROPIC_AUTH_TOKEN) : void 0);
|
|
3438
|
+
if (token === void 0) throw new Error(env.ANTHROPIC_AUTH_TOKEN ? "ANTHROPIC_AUTH_TOKEN is set, but the resolved base URL does not name a GLM host, so it is not a GLM Coding Plan token — set ZAI_AUTH_TOKEN (or --auth-token)" : "no API key: set ZAI_AUTH_TOKEN (or --auth-token)");
|
|
3439
|
+
return {
|
|
3440
|
+
token,
|
|
3441
|
+
url: base.origin
|
|
3442
|
+
};
|
|
3401
3443
|
}
|
|
3402
3444
|
//#endregion
|
|
3403
3445
|
//#region src/usage.ts
|
|
3404
3446
|
/**
|
|
3405
|
-
* Usage query
|
|
3406
|
-
*
|
|
3407
|
-
* renders the plain-text report.
|
|
3447
|
+
* Usage query: the three GLM monitor endpoints of the resolved base URL,
|
|
3448
|
+
* assembled into the report input.
|
|
3408
3449
|
*/
|
|
3409
3450
|
function queryParams(now = /* @__PURE__ */ new Date()) {
|
|
3410
3451
|
const startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, now.getHours(), 0, 0, 0);
|
|
@@ -3460,12 +3501,12 @@ async function fetchReport(config) {
|
|
|
3460
3501
|
fetchJson(`${config.url}/api/monitor/usage/tool-usage`, "Tool usage", config.token, query),
|
|
3461
3502
|
fetchJson(`${config.url}/api/monitor/usage/quota/limit`, "Quota limit", config.token)
|
|
3462
3503
|
]);
|
|
3463
|
-
return
|
|
3504
|
+
return {
|
|
3464
3505
|
platform,
|
|
3465
3506
|
model,
|
|
3466
3507
|
tool,
|
|
3467
3508
|
quota: processQuotaLimit(quotaRaw)
|
|
3468
|
-
}
|
|
3509
|
+
};
|
|
3469
3510
|
} catch (e) {
|
|
3470
3511
|
throw new Error(`Request failed: ${e.message}`, { cause: e });
|
|
3471
3512
|
}
|
|
@@ -3473,12 +3514,10 @@ async function fetchReport(config) {
|
|
|
3473
3514
|
//#endregion
|
|
3474
3515
|
//#region src/index.ts
|
|
3475
3516
|
var parsed = parseArgs(process.argv.slice(2)) ?? printUsageAndExit(buildProgram());
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
process.exit(1);
|
|
3481
|
-
}
|
|
3517
|
+
await runMain(async () => {
|
|
3518
|
+
const input = await fetchReport(resolveConfig(process.env, parsed));
|
|
3519
|
+
console.log(parsed.json ? JSON.stringify({ lines: reportLines(input) }) : render(input));
|
|
3520
|
+
});
|
|
3482
3521
|
//#endregion
|
|
3483
3522
|
export {};
|
|
3484
3523
|
|