diff --git a/packages/commands/src/commands/usage/shared.ts b/packages/commands/src/commands/usage/shared.ts index ec75b0c..2decba0 100644 --- a/packages/commands/src/commands/usage/shared.ts +++ b/packages/commands/src/commands/usage/shared.ts @@ -24,6 +24,14 @@ export function formatDate(ts: number): string { return `${year}-${month}-${day}`; } +export function formatDateTime(ts: number): string { + const date = new Date(ts); + const hour = String(date.getHours()).padStart(2, "0"); + const minute = String(date.getMinutes()).padStart(2, "0"); + const second = String(date.getSeconds()).padStart(2, "0"); + return `${formatDate(ts)} ${hour}:${minute}:${second}`; +} + export function requireWorkspaceId(settings: Settings, binName: string): string { if (settings.workspaceId) return settings.workspaceId; diff --git a/packages/commands/src/commands/usage/token-plan.ts b/packages/commands/src/commands/usage/token-plan.ts index 76c4876..d4bb8bf 100644 --- a/packages/commands/src/commands/usage/token-plan.ts +++ b/packages/commands/src/commands/usage/token-plan.ts @@ -1,5 +1,12 @@ -import { BailianError, ExitCode, defineCommand, unwrapResponse } from "bailian-cli-core"; -import { ansi, displayWidth, emitResult, type TextStyle } from "bailian-cli-runtime"; +import { defineCommand, detectOutputFormat, unwrapResponse } from "bailian-cli-core"; +import { + ansi, + displayWidth, + emitResult, + type AnsiStyles, + type TextStyle, +} from "bailian-cli-runtime"; +import { formatDateTime } from "./shared.ts"; const TOKEN_PLAN_USAGE_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage"; const BOX_WIDTH = 76; @@ -12,50 +19,36 @@ interface TokenPlanUsage { per1WeekResetTime?: number; } +interface QuotaWindow { + percentage?: number; + resetTime?: number; +} + +/** Accept only finite numbers; anything else counts as absent (possibly unlimited). */ +function readNumber(value: unknown): number | undefined { + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} + function readUsage(result: unknown): TokenPlanUsage { const response = unwrapResponse(result as Record); - const usage = { - per5HourPercentage: response.per5HourPercentage, - per5HourResetTime: response.per5HourResetTime, - per1WeekPercentage: response.per1WeekPercentage, - per1WeekResetTime: response.per1WeekResetTime, - }; + const usage: TokenPlanUsage = {}; - const quotas = [ - [usage.per5HourPercentage, usage.per5HourResetTime], - [usage.per1WeekPercentage, usage.per1WeekResetTime], - ]; - const hasValidQuotas = quotas.every( - ([percentage, resetTime]) => - (percentage === undefined && resetTime === undefined) || - (typeof percentage === "number" && - Number.isFinite(percentage) && - ((percentage === 0 && resetTime === undefined) || - (typeof resetTime === "number" && Number.isFinite(resetTime)))), - ); + const per5HourPercentage = readNumber(response.per5HourPercentage); + if (per5HourPercentage !== undefined) usage.per5HourPercentage = per5HourPercentage; + const per5HourResetTime = readNumber(response.per5HourResetTime); + if (per5HourResetTime !== undefined) usage.per5HourResetTime = per5HourResetTime; + const per1WeekPercentage = readNumber(response.per1WeekPercentage); + if (per1WeekPercentage !== undefined) usage.per1WeekPercentage = per1WeekPercentage; + const per1WeekResetTime = readNumber(response.per1WeekResetTime); + if (per1WeekResetTime !== undefined) usage.per1WeekResetTime = per1WeekResetTime; - if (!hasValidQuotas) { - throw new BailianError("Token Plan usage response has an unexpected format.", ExitCode.GENERAL); - } - - return usage as TokenPlanUsage; + return usage; } function formatPercentage(ratio: number): string { return `${(ratio * 100).toFixed(2)}%`; } -function formatDateTime(timestamp: number): string { - const date = new Date(timestamp); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - const hour = String(date.getHours()).padStart(2, "0"); - const minute = String(date.getMinutes()).padStart(2, "0"); - const second = String(date.getSeconds()).padStart(2, "0"); - return `${year}-${month}-${day} ${hour}:${minute}:${second}`; -} - function formatRemainingTime(resetTime: number, now: number): string { const remainingMs = Math.max(0, resetTime - now); const totalMinutes = Math.floor(remainingMs / 60_000); @@ -77,102 +70,74 @@ function progressBar(ratio: number): string { return `[${"█".repeat(filled)}${"░".repeat(PROGRESS_WIDTH - filled)}]`; } -function progressStyle( - percentage: number, - green: TextStyle, - yellow: TextStyle, - red: TextStyle, -): TextStyle { - if (percentage >= 0.9) return red; - if (percentage >= 0.75) return yellow; - return green; +function progressStyle(percentage: number, color: AnsiStyles): TextStyle { + if (percentage >= 0.9) return color.red; + if (percentage >= 0.75) return color.yellow; + return color.green; } function printView(usage: TokenPlanUsage, generatedAt: number): void { const color = ansi(process.stdout); - const writeLine = (content = "", visibleContent = content) => { - const padding = Math.max(0, BOX_WIDTH - displayWidth(` ${visibleContent}`)); - process.stdout.write(`│ ${content}${" ".repeat(padding)}│\n`); + const writeLine = (text = "", style?: TextStyle) => { + const padding = Math.max(0, BOX_WIDTH - displayWidth(` ${text}`)); + process.stdout.write(`│ ${style ? style(text) : text}${" ".repeat(padding)}│\n`); }; - const writeQuota = ( - label: string, - unlimitedMessage: string, - percentage: number | undefined, - resetTime: number | undefined, - ) => { - writeLine(color.bold(label), label); - if (percentage === undefined) { - writeLine(color.dim(unlimitedMessage), unlimitedMessage); + const writeQuota = (label: string, unlimitedMessage: string, window: QuotaWindow) => { + writeLine(label, color.bold); + if (window.percentage === undefined) { + writeLine(unlimitedMessage, color.dim); return; } - const percentageText = formatPercentage(percentage); - const bar = progressBar(percentage); - const style = progressStyle(percentage, color.green, color.yellow, color.red); - writeLine(`${percentageText} used ${style(bar)}`, `${percentageText} used ${bar}`); - if (resetTime === undefined) { - writeLine( - color.dim("Resets: not applicable (no usage yet)"), - "Resets: not applicable (no usage yet)", - ); + const percentageText = formatPercentage(window.percentage); + const bar = progressBar(window.percentage); + writeLine(`${percentageText} used ${bar}`, progressStyle(window.percentage, color)); + if (window.resetTime === undefined) { + writeLine("Resets: not applicable (no usage yet)", color.dim); return; } - const resetText = `Resets: ${formatDateTime(resetTime)} (in ${formatRemainingTime(resetTime, generatedAt)})`; - writeLine(color.dim(resetText), resetText); + const resetText = `Resets: ${formatDateTime(window.resetTime)} (in ${formatRemainingTime(window.resetTime, generatedAt)})`; + writeLine(resetText, color.dim); }; process.stdout.write(`┌${"─".repeat(BOX_WIDTH)}┐\n`); - writeLine(color.cyan("Token Plan Usage"), "Token Plan Usage"); - const generatedAtText = `Generated at: ${formatDateTime(generatedAt)} (local time)`; - writeLine(color.dim(generatedAtText), generatedAtText); + writeLine("Token Plan Usage", color.cyan); + writeLine(`Generated at: ${formatDateTime(generatedAt)} (local time)`, color.dim); process.stdout.write(`├${"─".repeat(BOX_WIDTH)}┤\n`); writeQuota( "5-hour quota", - "5小时限额当前可能无限制,请到百炼 Token Plan 控制台核实。", - usage.per5HourPercentage, - usage.per5HourResetTime, + "The 5-hour limit may be unlimited; verify in the Bailian Token Plan console.", + { percentage: usage.per5HourPercentage, resetTime: usage.per5HourResetTime }, ); process.stdout.write(`├${"─".repeat(BOX_WIDTH)}┤\n`); writeQuota( "1-week quota", - "1周限额当前可能无限制,请到百炼 Token Plan 控制台核实。", - usage.per1WeekPercentage, - usage.per1WeekResetTime, + "The 1-week limit may be unlimited; verify in the Bailian Token Plan console.", + { percentage: usage.per1WeekPercentage, resetTime: usage.per1WeekResetTime }, ); process.stdout.write(`└${"─".repeat(BOX_WIDTH)}┘\n`); } export default defineCommand({ - description: "Show Token Plan quota usage as core JSON or a human-readable view", + description: "Show Token Plan quota usage", auth: "console", - usageArgs: "<--json | --view> [flags]", - flags: { - json: { - type: "switch", - description: "Output only the four core usage fields as JSON", - }, - view: { - type: "switch", - description: "Render a compact human-readable quota view", - }, - }, - exampleArgs: ["--json", "--view"], - validate: (flags) => - flags.json === flags.view ? "Choose exactly one of --json or --view." : undefined, + usageArgs: "[flags]", + exampleArgs: ["", "--output json"], async run(ctx) { - const { flags, settings } = ctx; + const { settings } = ctx; + const format = detectOutputFormat(settings.output); if (settings.dryRun) { - emitResult({ api: TOKEN_PLAN_USAGE_API, data: {} }, "json"); + emitResult({ api: TOKEN_PLAN_USAGE_API, data: {} }, format); return; } const result = await ctx.client.console(TOKEN_PLAN_USAGE_API, {}); const usage = readUsage(result); - if (flags.json) { - emitResult(usage, "json"); + if (format === "json") { + emitResult(usage, format); return; } diff --git a/packages/commands/tests/e2e/usage-token-plan.e2e.test.ts b/packages/commands/tests/e2e/usage-token-plan.e2e.test.ts index 1acf052..1adc592 100644 --- a/packages/commands/tests/e2e/usage-token-plan.e2e.test.ts +++ b/packages/commands/tests/e2e/usage-token-plan.e2e.test.ts @@ -15,39 +15,28 @@ describe("e2e: usage token-plan", () => { "--help", ]); expect(exitCode, stderr).toBe(0); - expect(stderr).toMatch(/--json|--view|Token Plan/i); + expect(stderr).toMatch(/Token Plan|quota/i); }); - test("usage token-plan 未选择输出形式时退出为用法错误", async () => { + test("usage token-plan --help 包含 --output json 示例", async () => { const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [ "usage", "token-plan", - "--quiet", + "--help", ]); - expect(exitCode).toBe(2); - expect(stderr).toContain("Choose exactly one of --json or --view."); - }); - - test("usage token-plan 同时选择两种输出形式时退出为用法错误", async () => { - const { stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [ - "usage", - "token-plan", - "--json", - "--view", - "--quiet", - ]); - expect(exitCode).toBe(2); - expect(stderr).toContain("Choose exactly one of --json or --view."); + expect(exitCode, stderr).toBe(0); + expect(stderr).toContain("bl usage token-plan --output json"); }); }); describe.skipIf(!isConsoleE2EReady())("e2e: usage token-plan(Console)", () => { - test("usage token-plan --json --dry-run 输出网关请求计划", async () => { + test("usage token-plan --dry-run 输出网关请求计划", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(USAGE_ROUTES, [ "usage", "token-plan", - "--json", "--dry-run", + "--output", + "json", ]); expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ api?: string; data?: Record }>(stdout); @@ -55,8 +44,8 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage token-plan(Console)", () = expect(data.data).toEqual({}); }); - test("usage token-plan --json 返回可用的额度字段", async () => { - const result = await runCommandE2e(USAGE_ROUTES, ["usage", "token-plan", "--json"]); + test("usage token-plan --output json 返回可用的额度字段", async () => { + const result = await runCommandE2e(USAGE_ROUTES, ["usage", "token-plan", "--output", "json"]); if (isConsoleAuthFailure(result)) return; expect(result.exitCode, result.stderr).toBe(0); const data = parseStdoutJson<{ @@ -65,22 +54,19 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage token-plan(Console)", () = per1WeekPercentage?: number; per1WeekResetTime?: number; }>(result.stdout); - const quotas = [ - [data.per5HourPercentage, data.per5HourResetTime], - [data.per1WeekPercentage, data.per1WeekResetTime], + const fields = [ + data.per5HourPercentage, + data.per5HourResetTime, + data.per1WeekPercentage, + data.per1WeekResetTime, ]; - for (const [percentage, resetTime] of quotas) { - if (percentage === undefined) expect(resetTime).toBeUndefined(); - else if (percentage === 0) expect(resetTime).toBeUndefined(); - else { - expect(percentage).toBeTypeOf("number"); - expect(resetTime).toBeTypeOf("number"); - } + for (const field of fields) { + if (field !== undefined) expect(field).toBeTypeOf("number"); } }); - test("usage token-plan --view 渲染生成时间与两个额度窗口", async () => { - const result = await runCommandE2e(USAGE_ROUTES, ["usage", "token-plan", "--view"]); + test("usage token-plan 默认渲染生成时间与两个额度窗口", async () => { + const result = await runCommandE2e(USAGE_ROUTES, ["usage", "token-plan"]); if (isConsoleAuthFailure(result)) return; expect(result.exitCode, result.stderr).toBe(0); expect(result.stdout).toContain("Generated at:"); diff --git a/packages/commands/tests/token-plan-usage.test.ts b/packages/commands/tests/token-plan-usage.test.ts index 9a1596c..fe551f5 100644 --- a/packages/commands/tests/token-plan-usage.test.ts +++ b/packages/commands/tests/token-plan-usage.test.ts @@ -12,6 +12,23 @@ afterEach(() => { vi.restoreAllMocks(); }); +function captureStdout(): string[] { + const output: string[] = []; + vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { + output.push(String(chunk)); + return true; + }); + return output; +} + +async function runTokenPlan(response: Record, output?: string): Promise { + await tokenPlanUsage.run({ + client: { console: vi.fn().mockResolvedValue(response) }, + flags: {}, + settings: { dryRun: false, output }, + } as never); +} + function makeUsageResponse( per5HourPercentage?: number, per1WeekPercentage = per5HourPercentage, @@ -26,6 +43,10 @@ function makeUsageResponse( if (per1WeekPercentage !== 0) usage.per1WeekResetTime = 1_786_100_000_000; } + return wrapResponse(usage); +} + +function wrapResponse(usage: Record): Record { return { data: { DataV2: { @@ -45,49 +66,25 @@ describe("usage token-plan view", () => { ])("uses ANSI color %s for %s", async (percentage, colorCode) => { delete process.env.NO_COLOR; Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true }); - const output: string[] = []; - vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { - output.push(String(chunk)); - return true; - }); + const output = captureStdout(); - await tokenPlanUsage.run({ - client: { console: vi.fn().mockResolvedValue(makeUsageResponse(percentage)) }, - flags: { json: false, view: true }, - settings: { dryRun: false }, - } as never); + await runTokenPlan(makeUsageResponse(percentage)); - expect(output.join("")).toContain(`\u001B[${colorCode}m[`); + expect(output.join("")).toContain(`\u001B[${colorCode}m`); }); test("accepts missing reset times when the quota usage is zero", async () => { - const output: string[] = []; - vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { - output.push(String(chunk)); - return true; - }); + const output = captureStdout(); - await tokenPlanUsage.run({ - client: { console: vi.fn().mockResolvedValue(makeUsageResponse(0)) }, - flags: { json: false, view: true }, - settings: { dryRun: false }, - } as never); + await runTokenPlan(makeUsageResponse(0)); expect(output.join("")).toContain("Resets: not applicable (no usage yet)"); }); test("allows one unused quota window without masking another reset time", async () => { - const output: string[] = []; - vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { - output.push(String(chunk)); - return true; - }); + const output = captureStdout(); - await tokenPlanUsage.run({ - client: { console: vi.fn().mockResolvedValue(makeUsageResponse(0, 0.5)) }, - flags: { json: false, view: true }, - settings: { dryRun: false }, - } as never); + await runTokenPlan(makeUsageResponse(0, 0.5)); const renderedOutput = output.join(""); expect(renderedOutput).toContain("Resets: not applicable (no usage yet)"); @@ -95,55 +92,91 @@ describe("usage token-plan view", () => { }); test("renders missing quota windows as possibly unlimited", async () => { - const output: string[] = []; - vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { - output.push(String(chunk)); - return true; - }); + const output = captureStdout(); - await tokenPlanUsage.run({ - client: { console: vi.fn().mockResolvedValue(makeUsageResponse()) }, - flags: { json: false, view: true }, - settings: { dryRun: false }, - } as never); + await runTokenPlan(makeUsageResponse()); const renderedOutput = output.join(""); - expect(renderedOutput).toContain("5小时限额当前可能无限制,请到百炼 Token Plan 控制台核实。"); - expect(renderedOutput).toContain("1周限额当前可能无限制,请到百炼 Token Plan 控制台核实。"); + expect(renderedOutput).toContain( + "The 5-hour limit may be unlimited; verify in the Bailian Token Plan console.", + ); + expect(renderedOutput).toContain( + "The 1-week limit may be unlimited; verify in the Bailian Token Plan console.", + ); }); test("renders only the missing quota window as possibly unlimited", async () => { - const output: string[] = []; - vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { - output.push(String(chunk)); - return true; - }); + const output = captureStdout(); - await tokenPlanUsage.run({ - client: { console: vi.fn().mockResolvedValue(makeUsageResponse(undefined, 0.5)) }, - flags: { json: false, view: true }, - settings: { dryRun: false }, - } as never); + await runTokenPlan(makeUsageResponse(undefined, 0.5)); const renderedOutput = output.join(""); - expect(renderedOutput).toContain("5小时限额当前可能无限制,请到百炼 Token Plan 控制台核实。"); - expect(renderedOutput).not.toContain("1周限额当前可能无限制,请到百炼 Token Plan 控制台核实。"); + expect(renderedOutput).toContain( + "The 5-hour limit may be unlimited; verify in the Bailian Token Plan console.", + ); + expect(renderedOutput).not.toContain( + "The 1-week limit may be unlimited; verify in the Bailian Token Plan console.", + ); expect(renderedOutput).toMatch(/Resets: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/); }); - test("returns an empty JSON object when no quota fields are available", async () => { - const output: string[] = []; - vi.spyOn(process.stdout, "write").mockImplementation((chunk) => { - output.push(String(chunk)); - return true; - }); + test("renders a window with a missing percentage as possibly unlimited even when its reset time is present", async () => { + const output = captureStdout(); - await tokenPlanUsage.run({ - client: { console: vi.fn().mockResolvedValue(makeUsageResponse()) }, - flags: { json: true, view: false }, - settings: { dryRun: false }, - } as never); + await runTokenPlan(wrapResponse({ per5HourResetTime: 1_786_000_000_000 })); + + expect(output.join("")).toContain( + "The 5-hour limit may be unlimited; verify in the Bailian Token Plan console.", + ); + }); + + test("treats non-numeric quota fields as absent instead of failing", async () => { + const output = captureStdout(); + + await runTokenPlan( + wrapResponse({ per5HourPercentage: "not-a-number", per1WeekPercentage: Number.NaN }), + ); + + const renderedOutput = output.join(""); + expect(renderedOutput).toContain( + "The 5-hour limit may be unlimited; verify in the Bailian Token Plan console.", + ); + expect(renderedOutput).toContain( + "The 1-week limit may be unlimited; verify in the Bailian Token Plan console.", + ); + }); +}); + +describe("usage token-plan json", () => { + test("outputs the four core usage fields with --output json", async () => { + const output = captureStdout(); + + await runTokenPlan(makeUsageResponse(0.5, 0.25), "json"); + + expect(JSON.parse(output.join(""))).toEqual({ + per5HourPercentage: 0.5, + per5HourResetTime: 1_786_000_000_000, + per1WeekPercentage: 0.25, + per1WeekResetTime: 1_786_100_000_000, + }); + }); + + test("returns an empty JSON object when no quota fields are available", async () => { + const output = captureStdout(); + + await runTokenPlan(makeUsageResponse(), "json"); expect(output.join("").trim()).toBe("{}"); }); + + test("omits non-numeric quota fields from the JSON output", async () => { + const output = captureStdout(); + + await runTokenPlan( + wrapResponse({ per5HourPercentage: "not-a-number", per1WeekPercentage: 0 }), + "json", + ); + + expect(JSON.parse(output.join(""))).toEqual({ per1WeekPercentage: 0 }); + }); }); diff --git a/skills/bailian-cli/SKILL.md b/skills/bailian-cli/SKILL.md index 0d2ae3d..0d53217 100644 --- a/skills/bailian-cli/SKILL.md +++ b/skills/bailian-cli/SKILL.md @@ -71,6 +71,7 @@ Use this table only after the decision table in [`bailian-protocol`](../bailian- | Bailian pipeline workflow (a step in a bl flow) | `bl pipeline run` / `validate` | JSON/YAML workflow definitions | | Bailian rate limits / quota | `bl quota list` / `check` / `request` | Console auth; class 2 — ask which product first if unnamed | | Bailian free tier / usage stats | `bl usage free` / `stats` / `freetier` | Console auth; class 2 — ask which product first if unnamed | +| Bailian Token Plan quota usage | `bl usage token-plan` | Console auth; class 2 — ask which product first if unnamed | | Console API (advanced) | `bl console call` | Console auth | | Bailian workspace listing | `bl workspace list` | Console auth | | Image / video / speech / omni / vision | → skill `bailian-gen` | Fallback: `bl image\|video\|speech\|omni\|vision --help` | diff --git a/skills/bailian-cli/reference/index.md b/skills/bailian-cli/reference/index.md index 42068ce..2fa87ae 100644 --- a/skills/bailian-cli/reference/index.md +++ b/skills/bailian-cli/reference/index.md @@ -66,7 +66,7 @@ Use this index for the skill-scoped quick index and global flags. | `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) | | `bl usage stats` | Query model usage statistics | [usage.md](usage.md) | | `bl usage summary` | Show a unified usage summary: free-tier quota and recent usage overview | [usage.md](usage.md) | -| `bl usage token-plan` | Show Token Plan quota usage as core JSON or a human-readable view | [usage.md](usage.md) | +| `bl usage token-plan` | Show Token Plan quota usage | [usage.md](usage.md) | | `bl workspace init` | Initialize Bailian workspace and activate postpaid services | [workspace.md](workspace.md) | | `bl workspace list` | List all workspaces | [workspace.md](workspace.md) | diff --git a/skills/bailian-cli/reference/usage.md b/skills/bailian-cli/reference/usage.md index f391cf2..904c3a3 100644 --- a/skills/bailian-cli/reference/usage.md +++ b/skills/bailian-cli/reference/usage.md @@ -13,7 +13,7 @@ Index: [index.md](index.md) | `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | | `bl usage stats` | Query model usage statistics | | `bl usage summary` | Show a unified usage summary: free-tier quota and recent usage overview | -| `bl usage token-plan` | Show Token Plan quota usage as core JSON or a human-readable view | +| `bl usage token-plan` | Show Token Plan quota usage | ## Command details @@ -203,18 +203,16 @@ bl usage summary --output json ### `bl usage token-plan` -| Field | Value | -| --------------- | ----------------------------------------------------------------- | -| **Name** | `usage token-plan` | -| **Description** | Show Token Plan quota usage as core JSON or a human-readable view | -| **Usage** | `bl usage token-plan <--json \| --view> [flags]` | +| Field | Value | +| --------------- | ----------------------------- | +| **Name** | `usage token-plan` | +| **Description** | Show Token Plan quota usage | +| **Usage** | `bl usage token-plan [flags]` | #### Flags | Flag | Type | Required | Description | | ------------------------------ | ------ | -------- | -------------------------------------------------------- | -| `--json` | switch | no | Output only the four core usage fields as JSON | -| `--view` | switch | no | Render a compact human-readable quota view | | `--console-region ` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) | | `--console-site ` | string | no | Console site: domestic, international | | `--console-switch-agent ` | number | no | Switch agent UID for delegated access | @@ -223,9 +221,9 @@ bl usage summary --output json #### Examples ```bash -bl usage token-plan --json +bl usage token-plan ``` ```bash -bl usage token-plan --view +bl usage token-plan --output json ```