mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
feat: opt json output & reduce Chinese table name
This commit is contained in:
@@ -64,9 +64,9 @@ function formatRatio(usage: number, limit: number): string {
|
||||
function getStatus(usage: number, limit: number): string {
|
||||
if (limit <= 0) return "-";
|
||||
const pct = (usage / limit) * 100;
|
||||
if (pct >= 100) return "已限流";
|
||||
if (pct >= 80) return "接近限流";
|
||||
return "正常";
|
||||
if (pct >= 100) return "Throttled";
|
||||
if (pct >= 80) return "Near Limit";
|
||||
return "Normal";
|
||||
}
|
||||
|
||||
function getNestedRecord(
|
||||
@@ -193,7 +193,6 @@ function printTable(rows: CheckRow[], noColor: boolean): void {
|
||||
const yellow = noColor ? (t: string) => t : (t: string) => `\x1b[33m${t}\x1b[0m`;
|
||||
const red = noColor ? (t: string) => t : (t: string) => `\x1b[31m${t}\x1b[0m`;
|
||||
|
||||
const headersCn = ["模型", "RPM 用量/限额", "TPM 用量/限额", "状态"];
|
||||
const headersEn = ["Model", "RPM Usage/Limit", "TPM Usage/Limit", "Status"];
|
||||
|
||||
const tableRows = rows.map((r) => {
|
||||
@@ -215,36 +214,30 @@ function printTable(rows: CheckRow[], noColor: boolean): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const widths = headersCn.map((label, col) =>
|
||||
Math.max(
|
||||
displayWidth(label),
|
||||
displayWidth(headersEn[col]),
|
||||
...tableRows.map((r) => displayWidth(r.cells[col])),
|
||||
),
|
||||
const widths = headersEn.map((label, col) =>
|
||||
Math.max(displayWidth(label), ...tableRows.map((r) => displayWidth(r.cells[col]))),
|
||||
);
|
||||
|
||||
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
|
||||
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
|
||||
|
||||
process.stdout.write(cnLine + "\n");
|
||||
process.stdout.write(enLine + "\n");
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
|
||||
const statusCol = 3;
|
||||
for (const r of tableRows) {
|
||||
const cells = r.cells.map((cell, col) => {
|
||||
if (col === statusCol) {
|
||||
if (cell === "已限流") return red(padEnd(cell, widths[col]));
|
||||
if (cell === "接近限流") return yellow(padEnd(cell, widths[col]));
|
||||
if (cell === "正常") return green(padEnd(cell, widths[col]));
|
||||
if (cell === "Throttled") return red(padEnd(cell, widths[col]));
|
||||
if (cell === "Near Limit") return yellow(padEnd(cell, widths[col]));
|
||||
if (cell === "Normal") return green(padEnd(cell, widths[col]));
|
||||
}
|
||||
return padEnd(cell, widths[col]);
|
||||
});
|
||||
process.stdout.write(cells.join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\n共 ${rows.length} 个模型 (Total: ${rows.length})`) + "\n");
|
||||
process.stdout.write(dim(`\nTotal: ${rows.length} models`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
|
||||
@@ -65,7 +65,6 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu
|
||||
const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`;
|
||||
const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`;
|
||||
|
||||
const headersCn = ["模型", "Token 账号限流", "申请时间"];
|
||||
const headersEn = ["Model", "Token Limit", "Applied At"];
|
||||
|
||||
const rows = records.map((r) => [
|
||||
@@ -74,27 +73,21 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu
|
||||
formatDateTime(r.gmtCreate),
|
||||
]);
|
||||
|
||||
const widths = headersCn.map((label, col) =>
|
||||
Math.max(
|
||||
displayWidth(label),
|
||||
displayWidth(headersEn[col]),
|
||||
...rows.map((row) => displayWidth(row[col])),
|
||||
),
|
||||
const widths = headersEn.map((label, col) =>
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
|
||||
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
|
||||
|
||||
process.stdout.write(cnLine + "\n");
|
||||
process.stdout.write(enLine + "\n");
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
|
||||
for (const row of rows) {
|
||||
process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\n共 ${total} 条记录 (Total: ${total})`) + "\n");
|
||||
process.stdout.write(dim(`\nTotal: ${total} records`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -161,11 +154,6 @@ export default defineCommand({
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(result, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const resp = extractResponseData(result as Record<string, unknown>);
|
||||
let records = (resp.records as LimitApplicationItem[]) ?? [];
|
||||
const total = (resp.items as number) ?? records.length;
|
||||
@@ -174,6 +162,16 @@ export default defineCommand({
|
||||
records = records.filter((r) => r.deployedModel === modelFilter);
|
||||
}
|
||||
|
||||
if (format === "json") {
|
||||
const items = records.map((r) => ({
|
||||
model: r.deployedModel,
|
||||
tokenLimit: r.usageLimit,
|
||||
appliedAt: formatDateTime(r.gmtCreate),
|
||||
}));
|
||||
emitResult({ records: items, total: modelFilter ? records.length : total }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
if (records.length === 0) {
|
||||
process.stdout.write("No quota change history found.\n");
|
||||
return;
|
||||
|
||||
@@ -108,7 +108,6 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void {
|
||||
const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`;
|
||||
const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`;
|
||||
|
||||
const headersCn = ["模型", "RPM", "TPM", "可设上限 TPM"];
|
||||
const headersEn = ["Model", "Req/min", "Token/min", "Max TPM"];
|
||||
|
||||
const rows = models.map((m) => {
|
||||
@@ -135,27 +134,21 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const widths = headersCn.map((label, col) =>
|
||||
Math.max(
|
||||
displayWidth(label),
|
||||
displayWidth(headersEn[col]),
|
||||
...rows.map((row) => displayWidth(row[col])),
|
||||
),
|
||||
const widths = headersEn.map((label, col) =>
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
|
||||
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((w) => dim("─".repeat(w))).join("──");
|
||||
|
||||
process.stdout.write(cnLine + "\n");
|
||||
process.stdout.write(enLine + "\n");
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
|
||||
for (const row of rows) {
|
||||
process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\n共 ${models.length} 个模型 (Total: ${models.length})`) + "\n");
|
||||
process.stdout.write(dim(`\nTotal: ${models.length} models`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -221,7 +214,25 @@ export default defineCommand({
|
||||
}
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(models, format);
|
||||
const items = models.map((m) => {
|
||||
const qpm = m.qpmInfo;
|
||||
const modelDefault = qpm?.["model-default"];
|
||||
const userSpec = qpm?.["user-spec"];
|
||||
|
||||
const defaultRPM = calculateRPM(modelDefault);
|
||||
const defaultTPM = calculateTPM(modelDefault);
|
||||
const currentRPM = calculateRPM(userSpec, modelDefault?.count_limit_period) || defaultRPM;
|
||||
const currentTPM = calculateTPM(userSpec, modelDefault?.usage_limit_period) || defaultTPM;
|
||||
const maxTPM = defaultTPM * 2;
|
||||
|
||||
return {
|
||||
model: m.model,
|
||||
rpm: currentRPM > 0 ? currentRPM : null,
|
||||
tpm: currentTPM > 0 ? currentTPM : null,
|
||||
maxTPM: maxTPM > 0 ? maxTPM : null,
|
||||
};
|
||||
});
|
||||
emitResult(items, format);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ function printTable(
|
||||
typeMap: Map<string, string>,
|
||||
noColor: boolean,
|
||||
): void {
|
||||
const headersCn = ["模型", "类型", "剩余/总量", "使用率", "过期时间", "用完即停"];
|
||||
const headersEn = ["Model", "Type", "Remaining/Total", "Usage", "Expires", "Auto-Stop"];
|
||||
|
||||
const rows = quotas.map((quota) => {
|
||||
@@ -102,12 +101,8 @@ function printTable(
|
||||
];
|
||||
});
|
||||
|
||||
const widths = headersCn.map((label, col) =>
|
||||
Math.max(
|
||||
displayWidth(label),
|
||||
displayWidth(headersEn[col]),
|
||||
...rows.map((row) => displayWidth(row[col])),
|
||||
),
|
||||
const widths = headersEn.map((label, col) =>
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
@@ -115,12 +110,10 @@ function printTable(
|
||||
const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`;
|
||||
const yellow = noColor ? (text: string) => text : (text: string) => `\x1b[33m${text}\x1b[0m`;
|
||||
|
||||
const autoStopCol = headersCn.length - 1;
|
||||
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
|
||||
const autoStopCol = headersEn.length - 1;
|
||||
const enLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
|
||||
|
||||
process.stdout.write(cnLine + "\n");
|
||||
process.stdout.write(enLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
|
||||
@@ -296,11 +289,6 @@ export default defineCommand({
|
||||
}),
|
||||
]);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(quotaResult, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const allQuotas = extractQuotas(quotaResult);
|
||||
let quotas = modelFlag
|
||||
? allQuotas
|
||||
@@ -321,14 +309,44 @@ export default defineCommand({
|
||||
quotas.sort((a, b) => (a.quotaValidityPeriod ?? 0) - (b.quotaValidityPeriod ?? 0));
|
||||
}
|
||||
|
||||
const stopStatuses = extractFreeTierOnlyStatuses(stopResult);
|
||||
const stopMap = new Map(stopStatuses.map((status) => [status.model, status.freeTierOnly]));
|
||||
|
||||
if (format === "json") {
|
||||
const items = quotas.map((quota) => {
|
||||
const hasQuota = quota.quotaInitTotal != null && quota.quotaTotal != null;
|
||||
const used = hasQuota ? quota.quotaInitTotal - quota.quotaTotal : 0;
|
||||
const stopStatus = stopMap.get(quota.model);
|
||||
const autoStop =
|
||||
quota.quotaStatus === "UNKNOWN"
|
||||
? "unsupported"
|
||||
: stopStatus === true
|
||||
? true
|
||||
: stopStatus === false
|
||||
? false
|
||||
: null;
|
||||
return {
|
||||
model: quota.model,
|
||||
type: typeMap.get(quota.model) || null,
|
||||
remaining: hasQuota ? quota.quotaTotal : null,
|
||||
total: hasQuota ? quota.quotaInitTotal : null,
|
||||
usagePercent:
|
||||
hasQuota && quota.quotaInitTotal > 0
|
||||
? Math.round((used / quota.quotaInitTotal) * 1000) / 10
|
||||
: null,
|
||||
expires: quota.quotaValidityPeriod ? formatDate(quota.quotaValidityPeriod) : null,
|
||||
autoStop,
|
||||
};
|
||||
});
|
||||
emitResult(items, format);
|
||||
return;
|
||||
}
|
||||
|
||||
if (quotas.length === 0) {
|
||||
process.stdout.write("No free-tier quota found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const stopStatuses = extractFreeTierOnlyStatuses(stopResult);
|
||||
const stopMap = new Map(stopStatuses.map((status) => [status.model, status.freeTierOnly]));
|
||||
|
||||
printTable(quotas, stopMap, typeMap, config.noColor);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -155,33 +155,32 @@ function resolveUsageMap(item: ModelStatisticItem): Record<string, number> {
|
||||
}
|
||||
|
||||
interface UsageLabel {
|
||||
cn: string;
|
||||
en: string;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
const USAGE_KEY_LABELS: Record<string, UsageLabel> = {
|
||||
total_token: { cn: "总 Token", en: "Total Tokens", unit: "tokens" },
|
||||
input_token: { cn: "输入 Token", en: "Input Tokens", unit: "tokens" },
|
||||
output_token: { cn: "输出 Token", en: "Output Tokens", unit: "tokens" },
|
||||
input_token_cache: { cn: "缓存 Token", en: "Cached Tokens", unit: "tokens" },
|
||||
input_token_cache_read: { cn: "缓存读取", en: "Cache Read", unit: "tokens" },
|
||||
input_token_cache_creation: { cn: "缓存创建", en: "Cache Creation", unit: "tokens" },
|
||||
thinking_input_token: { cn: "思考输入", en: "Thinking Input", unit: "tokens" },
|
||||
thinking_output_token: { cn: "思考输出", en: "Thinking Output", unit: "tokens" },
|
||||
text_input_token: { cn: "文本输入", en: "Text Input", unit: "tokens" },
|
||||
purein_text_output_token: { cn: "文本输出", en: "Text Output", unit: "tokens" },
|
||||
embedding_token: { cn: "向量", en: "Embedding", unit: "tokens" },
|
||||
image_number: { cn: "图片数", en: "Images", unit: "张" },
|
||||
video_duration: { cn: "视频时长", en: "Video Duration", unit: "秒" },
|
||||
content_duration: { cn: "音频时长", en: "Audio Duration", unit: "秒" },
|
||||
tts_text_number: { cn: "语音合成", en: "TTS Chars", unit: "字符" },
|
||||
total_token_avg: { cn: "平均 Token/次", en: "Avg Tokens/Req" },
|
||||
total_token: { en: "Total Tokens", unit: "tokens" },
|
||||
input_token: { en: "Input Tokens", unit: "tokens" },
|
||||
output_token: { en: "Output Tokens", unit: "tokens" },
|
||||
input_token_cache: { en: "Cached Tokens", unit: "tokens" },
|
||||
input_token_cache_read: { en: "Cache Read", unit: "tokens" },
|
||||
input_token_cache_creation: { en: "Cache Creation", unit: "tokens" },
|
||||
thinking_input_token: { en: "Thinking Input", unit: "tokens" },
|
||||
thinking_output_token: { en: "Thinking Output", unit: "tokens" },
|
||||
text_input_token: { en: "Text Input", unit: "tokens" },
|
||||
purein_text_output_token: { en: "Text Output", unit: "tokens" },
|
||||
embedding_token: { en: "Embedding", unit: "tokens" },
|
||||
image_number: { en: "Images", unit: "images" },
|
||||
video_duration: { en: "Video Duration", unit: "seconds" },
|
||||
content_duration: { en: "Audio Duration", unit: "seconds" },
|
||||
tts_text_number: { en: "TTS Chars", unit: "chars" },
|
||||
total_token_avg: { en: "Avg Tokens/Req" },
|
||||
};
|
||||
|
||||
function formatLabel(label: UsageLabel): string {
|
||||
const unitSuffix = label.unit ? ` [${label.unit}]` : "";
|
||||
return `${label.cn} (${label.en})${unitSuffix}`;
|
||||
return `${label.en}${unitSuffix}`;
|
||||
}
|
||||
|
||||
function printOverview(
|
||||
@@ -195,12 +194,12 @@ function printOverview(
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
|
||||
process.stdout.write(
|
||||
`${dim("时间范围 Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} 天)`)}\n\n`,
|
||||
`${dim("Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`,
|
||||
);
|
||||
|
||||
const rows: [string, string][] = [
|
||||
["调用模型数 (Models Called)", formatNumber(stat.modelCount ?? 0)],
|
||||
["调用成功次数 (Successful Calls)", formatNumber(stat.callSuccessCount ?? 0)],
|
||||
["Models Called", formatNumber(stat.modelCount ?? 0)],
|
||||
["Successful Calls", formatNumber(stat.callSuccessCount ?? 0)],
|
||||
];
|
||||
|
||||
for (const usage of stat.usages ?? []) {
|
||||
@@ -226,7 +225,7 @@ function printModelTable(
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
|
||||
process.stdout.write(
|
||||
`${dim("时间范围 Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} 天)`)}\n\n`,
|
||||
`${dim("Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`,
|
||||
);
|
||||
|
||||
if (items.length === 0) {
|
||||
@@ -257,15 +256,6 @@ function printModelTable(
|
||||
return (idxA === -1 ? 999 : idxA) - (idxB === -1 ? 999 : idxB);
|
||||
});
|
||||
|
||||
const headersCn = [
|
||||
"模型",
|
||||
"调用次数",
|
||||
...orderedKeys.map((key) => {
|
||||
const label = USAGE_KEY_LABELS[key];
|
||||
if (!label) return key;
|
||||
return label.unit ? `${label.cn} [${label.unit}]` : label.cn;
|
||||
}),
|
||||
];
|
||||
const headersEn = [
|
||||
"Model",
|
||||
"Calls",
|
||||
@@ -280,20 +270,14 @@ function printModelTable(
|
||||
}),
|
||||
]);
|
||||
|
||||
const widths = headersCn.map((label, col) =>
|
||||
Math.max(
|
||||
displayWidth(label),
|
||||
displayWidth(headersEn[col]),
|
||||
...rows.map((row) => displayWidth(row[col])),
|
||||
),
|
||||
const widths = headersEn.map((label, col) =>
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
|
||||
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
|
||||
|
||||
process.stdout.write(cnLine + "\n");
|
||||
process.stdout.write(enLine + "\n");
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
|
||||
for (const row of rows) {
|
||||
@@ -301,7 +285,7 @@ function printModelTable(
|
||||
process.stdout.write(cells.join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(dim(`\n共 ${items.length} 个模型 (Total: ${items.length})`) + "\n");
|
||||
process.stdout.write(dim(`\nTotal: ${items.length} models`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -391,16 +375,31 @@ export default defineCommand({
|
||||
);
|
||||
|
||||
const allItems: ModelStatisticItem[] = [];
|
||||
const jsonResults: unknown[] = [];
|
||||
for (const result of results) {
|
||||
if (!result) continue;
|
||||
jsonResults.push(result);
|
||||
const listData = extractListData(result);
|
||||
allItems.push(...listData.list);
|
||||
}
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(jsonResults.length === 1 ? jsonResults[0] : jsonResults, format);
|
||||
const items = allItems.map((item) => {
|
||||
const usage = resolveUsageMap(item);
|
||||
const clean: Record<string, unknown> = {
|
||||
model: item.model,
|
||||
successfulCalls: item.callSuccessCount ?? 0,
|
||||
};
|
||||
for (const [key, val] of Object.entries(usage)) {
|
||||
clean[key] = val;
|
||||
}
|
||||
return clean;
|
||||
});
|
||||
emitResult(
|
||||
{
|
||||
period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag },
|
||||
items,
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -425,12 +424,37 @@ export default defineCommand({
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const stat = extractOverviewData(result);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(result, format);
|
||||
if (!stat) {
|
||||
emitResult(
|
||||
{
|
||||
period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag },
|
||||
modelsCalled: 0,
|
||||
successfulCalls: 0,
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
emitResult(
|
||||
{
|
||||
period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag },
|
||||
modelsCalled: stat.modelCount ?? 0,
|
||||
successfulCalls: stat.callSuccessCount ?? 0,
|
||||
usages: (stat.usages ?? []).map((u) => ({
|
||||
key: u.key,
|
||||
value: u.value,
|
||||
unit: u.unit,
|
||||
label: USAGE_KEY_LABELS[u.key]?.en ?? u.key,
|
||||
})),
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const stat = extractOverviewData(result);
|
||||
if (!stat) {
|
||||
process.stdout.write("No usage data found.\n");
|
||||
return;
|
||||
|
||||
@@ -46,8 +46,7 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
|
||||
const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`;
|
||||
const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`;
|
||||
|
||||
const headersCn = ["空间名称", "Workspace ID", "默认空间"];
|
||||
const headersEn = ["Name", "", "Default"];
|
||||
const headersEn = ["Name", "Workspace ID", "Default"];
|
||||
|
||||
const rows = workspaces.map((ws) => [
|
||||
ws.agentName,
|
||||
@@ -55,20 +54,14 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
|
||||
ws.defaultAgent ? "Yes" : "-",
|
||||
]);
|
||||
|
||||
const widths = headersCn.map((label, col) =>
|
||||
Math.max(
|
||||
displayWidth(label),
|
||||
displayWidth(headersEn[col]),
|
||||
...rows.map((row) => displayWidth(row[col])),
|
||||
),
|
||||
const widths = headersEn.map((label, col) =>
|
||||
Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))),
|
||||
);
|
||||
|
||||
const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" ");
|
||||
const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" ");
|
||||
const separator = widths.map((width) => dim("─".repeat(width))).join("──");
|
||||
|
||||
process.stdout.write(cnLine + "\n");
|
||||
process.stdout.write(enLine + "\n");
|
||||
process.stdout.write(headerLine + "\n");
|
||||
process.stdout.write(separator + "\n");
|
||||
|
||||
for (const row of rows) {
|
||||
@@ -79,9 +72,7 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void {
|
||||
process.stdout.write(cells.join(" ") + "\n");
|
||||
}
|
||||
|
||||
process.stdout.write(
|
||||
dim(`\n共 ${workspaces.length} 个空间 (Total: ${workspaces.length})`) + "\n",
|
||||
);
|
||||
process.stdout.write(dim(`\nTotal: ${workspaces.length} workspaces`) + "\n");
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
@@ -117,21 +108,30 @@ export default defineCommand({
|
||||
region,
|
||||
});
|
||||
|
||||
if (format === "json") {
|
||||
emitResult(result, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const resp = extractResponseData(result as Record<string, unknown>);
|
||||
const dataArr = resp.data as Record<string, unknown>[] | undefined;
|
||||
if (!Array.isArray(dataArr) || dataArr.length === 0) {
|
||||
process.stdout.write("No workspace found.\n");
|
||||
if (format === "json") {
|
||||
emitResult([], format);
|
||||
} else {
|
||||
process.stdout.write("No workspace found.\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let workspaces = dataArr as unknown as WorkspaceInfo[];
|
||||
if (limit > 0) workspaces = workspaces.slice(0, limit);
|
||||
|
||||
if (format === "json") {
|
||||
const items = workspaces.map((ws) => ({
|
||||
workspaceId: ws.workspaceId,
|
||||
name: ws.agentName,
|
||||
default: ws.defaultAgent,
|
||||
}));
|
||||
emitResult(items, format);
|
||||
return;
|
||||
}
|
||||
|
||||
printTable(workspaces, config.noColor);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
expect(data.data?.input?.supports).toBeUndefined();
|
||||
});
|
||||
|
||||
test("quota list 文本输出包含双行表头", async () => {
|
||||
test("quota list 文本输出包含单行英⽂表头", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"quota",
|
||||
"list",
|
||||
@@ -105,11 +105,9 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("模型");
|
||||
expect(stdout).toContain("Model");
|
||||
expect(stdout).toContain("RPM");
|
||||
expect(stdout).toContain("TPM");
|
||||
expect(stdout).toContain("可设上限 TPM");
|
||||
expect(stdout).toContain("Req/min");
|
||||
expect(stdout).toContain("Token/min");
|
||||
expect(stdout).toContain("Max TPM");
|
||||
});
|
||||
|
||||
@@ -125,7 +123,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3.6-plus");
|
||||
expect(stdout).toMatch(/共 1 个模型/);
|
||||
expect(stdout).toMatch(/Total: 1 models/);
|
||||
});
|
||||
|
||||
test("quota list --model 不存在的模型报错", async () => {
|
||||
@@ -141,14 +139,19 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
expect(stderr).toContain("no matching models found");
|
||||
});
|
||||
|
||||
test("quota list JSON 输出包含 qpmInfo", async () => {
|
||||
test("quota list JSON 输出包含 model/rpm/tpm/maxTPM", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli(["quota", "list", "--output", "json"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<Array<{ model?: string; qpmInfo?: unknown }>>(stdout);
|
||||
const data =
|
||||
parseStdoutJson<
|
||||
Array<{ model?: string; rpm?: number | null; tpm?: number | null; maxTPM?: number | null }>
|
||||
>(stdout);
|
||||
expect(Array.isArray(data)).toBe(true);
|
||||
expect(data.length).toBeGreaterThan(0);
|
||||
expect(data[0].model).toBeTypeOf("string");
|
||||
expect(data[0].qpmInfo).toBeDefined();
|
||||
expect(data[0].rpm).toBeTypeOf("number");
|
||||
expect(data[0].tpm).toBeTypeOf("number");
|
||||
expect(data[0].maxTPM).toBeTypeOf("number");
|
||||
});
|
||||
|
||||
test("quota request --dry-run 输出请求参数", async () => {
|
||||
@@ -235,7 +238,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
expect(data.apis).toContain("zeldaEasy.bailian-telemetry.monitor.getMonitorData");
|
||||
});
|
||||
|
||||
test("quota check 文本输出包含双行表头", async () => {
|
||||
test("quota check 文本输出包含单行英⽂表头", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"quota",
|
||||
"check",
|
||||
@@ -244,12 +247,10 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
"--no-color",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("模型");
|
||||
expect(stdout).toContain("Model");
|
||||
expect(stdout).toContain("RPM 用量/限额");
|
||||
expect(stdout).toContain("RPM Usage/Limit");
|
||||
expect(stdout).toContain("TPM 用量/限额");
|
||||
expect(stdout).toContain("状态");
|
||||
expect(stdout).toContain("TPM Usage/Limit");
|
||||
expect(stdout).toContain("Status");
|
||||
});
|
||||
|
||||
test("quota check --model 指定单模型", async () => {
|
||||
@@ -264,7 +265,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3.6-plus");
|
||||
expect(stdout).toMatch(/共 1 个模型/);
|
||||
expect(stdout).toMatch(/Total: 1 models/);
|
||||
});
|
||||
|
||||
test("quota check --model 逗号分隔多模型", async () => {
|
||||
@@ -280,7 +281,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stdout).toContain("qwen3.6-plus");
|
||||
expect(stdout).toContain("qwen-plus");
|
||||
expect(stdout).toMatch(/共 2 个模型/);
|
||||
expect(stdout).toMatch(/Total: 2 models/);
|
||||
});
|
||||
|
||||
test("quota check JSON 输出包含用量和限额字段", async () => {
|
||||
@@ -311,7 +312,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
expect(data[0].tpmLimit).toBeTypeOf("number");
|
||||
});
|
||||
|
||||
test("quota check 状态列显示正常/接近限流/已限流之一", async () => {
|
||||
test("quota check 状态列显示 Normal/Near Limit/Throttled 之一", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"quota",
|
||||
"check",
|
||||
@@ -323,7 +324,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const hasStatus =
|
||||
stdout.includes("正常") || stdout.includes("接近限流") || stdout.includes("已限流");
|
||||
stdout.includes("Normal") || stdout.includes("Near Limit") || stdout.includes("Throttled");
|
||||
expect(hasStatus).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -133,12 +133,21 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
code?: string;
|
||||
successResponse?: boolean;
|
||||
}>(stdout);
|
||||
expect(data.code).toBe("200");
|
||||
expect(data.successResponse).toBe(true);
|
||||
const data = parseStdoutJson<
|
||||
Array<{
|
||||
model?: string;
|
||||
type?: string | null;
|
||||
remaining?: number | null;
|
||||
total?: number | null;
|
||||
usagePercent?: number | null;
|
||||
expires?: string | null;
|
||||
autoStop?: boolean | string | null;
|
||||
}>
|
||||
>(stdout);
|
||||
expect(Array.isArray(data)).toBe(true);
|
||||
expect(data.length).toBeGreaterThan(0);
|
||||
expect(data[0].model).toBe("qwen3-max");
|
||||
expect(data[0].type).toBeTypeOf("string");
|
||||
});
|
||||
|
||||
test("usage free --model 单模型文本输出包含表头", async () => {
|
||||
@@ -276,7 +285,9 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{ code?: string }>(stdout);
|
||||
expect(data.code).toBe("200");
|
||||
const data = parseStdoutJson<Array<{ model?: string }>>(stdout);
|
||||
expect(Array.isArray(data)).toBe(true);
|
||||
expect(data.length).toBeGreaterThan(0);
|
||||
expect(data[0].model).toBe("qwen3-max");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -169,11 +169,17 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => {
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
code?: string;
|
||||
successResponse?: boolean;
|
||||
period?: { start?: string; end?: string; days?: number };
|
||||
modelsCalled?: number;
|
||||
successfulCalls?: number;
|
||||
usages?: Array<{ key?: string; value?: number }>;
|
||||
}>(stdout);
|
||||
expect(data.code).toBe("200");
|
||||
expect(data.successResponse).toBe(true);
|
||||
expect(data.period).toBeDefined();
|
||||
expect(data.period?.start).toBeTypeOf("string");
|
||||
expect(data.period?.end).toBeTypeOf("string");
|
||||
expect(data.period?.days).toBeTypeOf("number");
|
||||
expect(data.modelsCalled).toBeTypeOf("number");
|
||||
expect(data.successfulCalls).toBeTypeOf("number");
|
||||
});
|
||||
|
||||
test("usage stats 概览文本输出包含中英文表头", async () => {
|
||||
|
||||
Reference in New Issue
Block a user