|
| 1 | +import { randomUUID } from "node:crypto"; |
| 2 | +import { defineCommand, detectOutputFormat, unwrapResponse } from "bailian-cli-core"; |
| 3 | +import { emitResult } from "bailian-cli-runtime"; |
| 4 | +import { printQuotaBox, readNumber, type QuotaSection } from "../usage/quota-box.ts"; |
| 5 | +import { formatNumber } from "../shared/format.ts"; |
| 6 | +import type { HarnessBenefitItem, TokenPlanEquityInfo } from "./types.ts"; |
| 7 | + |
| 8 | +const HARNESS_LIST_API = "zeldaEasy.broadscope-bailian.token-plan.detail"; |
| 9 | +const EQUITY_INFO_API = "zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo"; |
| 10 | + |
| 11 | +/** One harness that carries an entitlement quota, issued or still pending. */ |
| 12 | +interface HarnessQuotaRow { |
| 13 | + planCode: string; |
| 14 | + title: string; |
| 15 | + type: string; |
| 16 | + /** `issuing` means the harness carries a quota but no entitlement is issued yet. */ |
| 17 | + status: "issued" | "issuing"; |
| 18 | + quotaUnit: string; |
| 19 | + instanceId?: string; |
| 20 | + totalQuota?: number; |
| 21 | + availableQuota?: number; |
| 22 | + usedQuota?: number; |
| 23 | + /** Used ratio in percent with 0.1 precision, matching the console display. */ |
| 24 | + usedPercent?: number; |
| 25 | + instanceStartTime?: number; |
| 26 | + instanceEndTime?: number; |
| 27 | +} |
| 28 | + |
| 29 | +function readArray<T>(result: unknown, field: string): T[] { |
| 30 | + const response = unwrapResponse(result as Record<string, unknown>); |
| 31 | + const value = response[field]; |
| 32 | + return Array.isArray(value) ? (value as T[]) : []; |
| 33 | +} |
| 34 | + |
| 35 | +/** |
| 36 | + * Join the harness list with the issued entitlements, keeping the server order. |
| 37 | + * A harness carrying a resource pack but no matching entitlement is still |
| 38 | + * listed as `issuing` — issuance lags the purchase by a few minutes. |
| 39 | + */ |
| 40 | +function buildRows( |
| 41 | + items: HarnessBenefitItem[], |
| 42 | + equityInfos: TokenPlanEquityInfo[], |
| 43 | +): HarnessQuotaRow[] { |
| 44 | + const rows: HarnessQuotaRow[] = []; |
| 45 | + |
| 46 | + for (const item of items) { |
| 47 | + // Only harnesses that carry a resource pack have an entitlement quota at all. |
| 48 | + if (item.hasResourcePack !== true) continue; |
| 49 | + |
| 50 | + const planCodes = item.planCodes ?? []; |
| 51 | + const equity = equityInfos.find( |
| 52 | + (equityInfo) => equityInfo.equityType && planCodes.includes(equityInfo.equityType), |
| 53 | + ); |
| 54 | + const planCode = equity?.equityType ?? planCodes[0] ?? ""; |
| 55 | + const row: HarnessQuotaRow = { |
| 56 | + planCode, |
| 57 | + title: item.title ?? planCode, |
| 58 | + type: item.type ?? "", |
| 59 | + status: equity ? "issued" : "issuing", |
| 60 | + quotaUnit: item.quotaUnit ?? item.priceInfo?.unit ?? "", |
| 61 | + }; |
| 62 | + |
| 63 | + if (!equity) { |
| 64 | + rows.push(row); |
| 65 | + continue; |
| 66 | + } |
| 67 | + |
| 68 | + if (equity.instanceId) row.instanceId = equity.instanceId; |
| 69 | + const totalQuota = readNumber(equity.totalQuota); |
| 70 | + if (totalQuota !== undefined) row.totalQuota = totalQuota; |
| 71 | + const availableQuota = readNumber(equity.availableQuota); |
| 72 | + if (availableQuota !== undefined) row.availableQuota = availableQuota; |
| 73 | + if (totalQuota !== undefined && availableQuota !== undefined) { |
| 74 | + row.usedQuota = Math.max(totalQuota - availableQuota, 0); |
| 75 | + if (totalQuota > 0) { |
| 76 | + row.usedPercent = Math.round((row.usedQuota / totalQuota) * 1000) / 10; |
| 77 | + } |
| 78 | + } |
| 79 | + const instanceStartTime = readNumber(equity.instanceStartTime); |
| 80 | + if (instanceStartTime !== undefined) row.instanceStartTime = instanceStartTime; |
| 81 | + const instanceEndTime = readNumber(equity.instanceEndTime); |
| 82 | + if (instanceEndTime !== undefined) row.instanceEndTime = instanceEndTime; |
| 83 | + |
| 84 | + rows.push(row); |
| 85 | + } |
| 86 | + |
| 87 | + return rows; |
| 88 | +} |
| 89 | + |
| 90 | +function toSection(row: HarnessQuotaRow): QuotaSection { |
| 91 | + const section: QuotaSection = { |
| 92 | + label: `${row.title} (${row.planCode})`, |
| 93 | + emptyMessage: |
| 94 | + row.status === "issuing" |
| 95 | + ? "Quota is being issued; issuance can take up to 5 minutes." |
| 96 | + : "No positive quota total reported; check the Bailian Token Plan console.", |
| 97 | + }; |
| 98 | + if (row.status === "issuing") return section; |
| 99 | + |
| 100 | + if (row.totalQuota !== undefined && row.usedQuota !== undefined) { |
| 101 | + const unitSuffix = row.quotaUnit ? ` ${row.quotaUnit}` : ""; |
| 102 | + section.detail = `Used: ${formatNumber(row.usedQuota)} / ${formatNumber(row.totalQuota)}${unitSuffix}`; |
| 103 | + if (row.totalQuota > 0) section.percentage = row.usedQuota / row.totalQuota; |
| 104 | + } |
| 105 | + if (row.instanceEndTime !== undefined) section.resetTime = row.instanceEndTime; |
| 106 | + |
| 107 | + return section; |
| 108 | +} |
| 109 | + |
| 110 | +export default defineCommand({ |
| 111 | + description: { |
| 112 | + "en-US": "Show Token Plan harness entitlement quota usage", |
| 113 | + "zh-CN": "查看 Token Plan harness 权益额度用量", |
| 114 | + }, |
| 115 | + auth: "console", |
| 116 | + usageArgs: "[flags]", |
| 117 | + flags: { |
| 118 | + type: { |
| 119 | + type: "string", |
| 120 | + valueHint: "<type>", |
| 121 | + choices: ["official_tool", "infrastructure"] as const, |
| 122 | + description: { |
| 123 | + "en-US": "Filter harness list by type: official_tool, infrastructure", |
| 124 | + "zh-CN": "按类型筛选 harness 列表:official_tool、infrastructure", |
| 125 | + }, |
| 126 | + }, |
| 127 | + }, |
| 128 | + exampleArgs: ["", "--type official_tool", "--output json"], |
| 129 | + async run(ctx) { |
| 130 | + const { settings, flags } = ctx; |
| 131 | + const format = detectOutputFormat(settings.output); |
| 132 | + const benefitsData = flags.type ? { type: flags.type } : {}; |
| 133 | + const equityData = { queryTokenPlanEquityInfoRequest: { requestId: randomUUID() } }; |
| 134 | + |
| 135 | + if (settings.dryRun) { |
| 136 | + emitResult( |
| 137 | + { |
| 138 | + requests: [ |
| 139 | + { api: HARNESS_LIST_API, data: benefitsData }, |
| 140 | + { api: EQUITY_INFO_API, data: equityData }, |
| 141 | + ], |
| 142 | + }, |
| 143 | + format, |
| 144 | + ); |
| 145 | + return; |
| 146 | + } |
| 147 | + |
| 148 | + const [benefitsResult, equityResult] = await Promise.all([ |
| 149 | + ctx.client.console(HARNESS_LIST_API, benefitsData), |
| 150 | + ctx.client.console(EQUITY_INFO_API, equityData), |
| 151 | + ]); |
| 152 | + const rows = buildRows( |
| 153 | + readArray<HarnessBenefitItem>(benefitsResult, "items"), |
| 154 | + readArray<TokenPlanEquityInfo>(equityResult, "tokenPlanEquityInfos"), |
| 155 | + ); |
| 156 | + |
| 157 | + if (format === "json") { |
| 158 | + emitResult({ generatedAt: Date.now(), items: rows }, format); |
| 159 | + return; |
| 160 | + } |
| 161 | + |
| 162 | + if (rows.length === 0) { |
| 163 | + process.stdout.write("No harness with entitlement quota found.\n"); |
| 164 | + return; |
| 165 | + } |
| 166 | + |
| 167 | + printQuotaBox("Token Plan Harness Quota", rows.map(toSection), Date.now()); |
| 168 | + }, |
| 169 | +}); |
0 commit comments