mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 241345d7f1 | |||
| 46b55b29ac | |||
| f953f428c5 | |||
| e03103364e | |||
| 64b1e72238 |
@@ -6,6 +6,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||||
|
||||
[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)
|
||||
|
||||
## [1.25.0] - 2026-09-14
|
||||
|
||||
### Added
|
||||
|
||||
- **Token Plan harness quota** — `bl token-plan harness-quota` shows Token Plan harness entitlement quota usage (Console auth), joining the harness list with issued entitlements to display used/total quota, usage ratio, and reset time; harnesses with a pending entitlement are listed as issuing.
|
||||
- Filter the harness list with `--type official_tool|infrastructure`; render as a quota box or `--output json`.
|
||||
|
||||
## [1.24.0] - 2026-09-11
|
||||
|
||||
### Added
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
|
||||
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
|
||||
|
||||
## [1.25.0] - 2026-09-14
|
||||
|
||||
### 新增
|
||||
|
||||
- **Token Plan harness 权益额度** —— `bl token-plan harness-quota` 查看 Token Plan harness 权益额度用量(Console 认证),联合 harness 列表与已发放权益,展示已用/总额度、使用比例和重置时间;待发放权益的 harness 以「发放中」状态列出。
|
||||
- 通过 `--type official_tool|infrastructure` 筛选 harness 列表;支持额度框输出或 `--output json`。
|
||||
|
||||
## [1.24.0] - 2026-09-11
|
||||
|
||||
### 新增
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli",
|
||||
"version": "1.24.0",
|
||||
"version": "1.25.0",
|
||||
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
|
||||
"keywords": [
|
||||
"agent",
|
||||
|
||||
@@ -130,6 +130,7 @@ import {
|
||||
tokenPlanCreateKey,
|
||||
tokenPlanAssignSeats,
|
||||
tokenPlanAddMember,
|
||||
tokenPlanHarnessQuota,
|
||||
workspaceInit,
|
||||
pluginInstall,
|
||||
pluginLink,
|
||||
@@ -366,6 +367,7 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"token-plan create-key": tokenPlanCreateKey,
|
||||
"token-plan assign-seats": tokenPlanAssignSeats,
|
||||
"token-plan add-member": tokenPlanAddMember,
|
||||
"token-plan harness-quota": tokenPlanHarnessQuota,
|
||||
"workspace init": workspaceInit,
|
||||
"plugin install": pluginInstall,
|
||||
"plugin link": pluginLink,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-commands",
|
||||
"version": "1.24.0",
|
||||
"version": "1.25.0",
|
||||
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { defineCommand, detectOutputFormat, unwrapResponse } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { printQuotaBox, readNumber, type QuotaSection } from "../usage/quota-box.ts";
|
||||
import { formatNumber } from "../shared/format.ts";
|
||||
import type { HarnessBenefitItem, TokenPlanEquityInfo } from "./types.ts";
|
||||
|
||||
const HARNESS_LIST_API = "zeldaEasy.broadscope-bailian.token-plan.detail";
|
||||
const EQUITY_INFO_API = "zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo";
|
||||
|
||||
/** One harness that carries an entitlement quota, issued or still pending. */
|
||||
interface HarnessQuotaRow {
|
||||
planCode: string;
|
||||
title: string;
|
||||
type: string;
|
||||
/** `issuing` means the harness carries a quota but no entitlement is issued yet. */
|
||||
status: "issued" | "issuing";
|
||||
quotaUnit: string;
|
||||
instanceId?: string;
|
||||
totalQuota?: number;
|
||||
availableQuota?: number;
|
||||
usedQuota?: number;
|
||||
/** Used ratio in percent with 0.1 precision, matching the console display. */
|
||||
usedPercent?: number;
|
||||
instanceStartTime?: number;
|
||||
instanceEndTime?: number;
|
||||
}
|
||||
|
||||
function readArray<T>(result: unknown, field: string): T[] {
|
||||
const response = unwrapResponse(result as Record<string, unknown>);
|
||||
const value = response[field];
|
||||
return Array.isArray(value) ? (value as T[]) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the harness list with the issued entitlements, keeping the server order.
|
||||
* A harness carrying a resource pack but no matching entitlement is still
|
||||
* listed as `issuing` — issuance lags the purchase by a few minutes.
|
||||
*/
|
||||
function buildRows(
|
||||
items: HarnessBenefitItem[],
|
||||
equityInfos: TokenPlanEquityInfo[],
|
||||
): HarnessQuotaRow[] {
|
||||
const rows: HarnessQuotaRow[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
// Only harnesses that carry a resource pack have an entitlement quota at all.
|
||||
if (item.hasResourcePack !== true) continue;
|
||||
|
||||
const planCodes = item.planCodes ?? [];
|
||||
const equity = equityInfos.find(
|
||||
(equityInfo) => equityInfo.equityType && planCodes.includes(equityInfo.equityType),
|
||||
);
|
||||
const planCode = equity?.equityType ?? planCodes[0] ?? "";
|
||||
const row: HarnessQuotaRow = {
|
||||
planCode,
|
||||
title: item.title ?? planCode,
|
||||
type: item.type ?? "",
|
||||
status: equity ? "issued" : "issuing",
|
||||
quotaUnit: item.quotaUnit ?? item.priceInfo?.unit ?? "",
|
||||
};
|
||||
|
||||
if (!equity) {
|
||||
rows.push(row);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (equity.instanceId) row.instanceId = equity.instanceId;
|
||||
const totalQuota = readNumber(equity.totalQuota);
|
||||
if (totalQuota !== undefined) row.totalQuota = totalQuota;
|
||||
const availableQuota = readNumber(equity.availableQuota);
|
||||
if (availableQuota !== undefined) row.availableQuota = availableQuota;
|
||||
if (totalQuota !== undefined && availableQuota !== undefined) {
|
||||
row.usedQuota = Math.max(totalQuota - availableQuota, 0);
|
||||
if (totalQuota > 0) {
|
||||
row.usedPercent = Math.round((row.usedQuota / totalQuota) * 1000) / 10;
|
||||
}
|
||||
}
|
||||
const instanceStartTime = readNumber(equity.instanceStartTime);
|
||||
if (instanceStartTime !== undefined) row.instanceStartTime = instanceStartTime;
|
||||
const instanceEndTime = readNumber(equity.instanceEndTime);
|
||||
if (instanceEndTime !== undefined) row.instanceEndTime = instanceEndTime;
|
||||
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function toSection(row: HarnessQuotaRow): QuotaSection {
|
||||
const section: QuotaSection = {
|
||||
label: `${row.title} (${row.planCode})`,
|
||||
emptyMessage:
|
||||
row.status === "issuing"
|
||||
? "Quota is being issued; issuance can take up to 5 minutes."
|
||||
: "No positive quota total reported; check the Bailian Token Plan console.",
|
||||
};
|
||||
if (row.status === "issuing") return section;
|
||||
|
||||
if (row.totalQuota !== undefined && row.usedQuota !== undefined) {
|
||||
const unitSuffix = row.quotaUnit ? ` ${row.quotaUnit}` : "";
|
||||
section.detail = `Used: ${formatNumber(row.usedQuota)} / ${formatNumber(row.totalQuota)}${unitSuffix}`;
|
||||
if (row.totalQuota > 0) section.percentage = row.usedQuota / row.totalQuota;
|
||||
}
|
||||
if (row.instanceEndTime !== undefined) section.resetTime = row.instanceEndTime;
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US": "Show Token Plan harness entitlement quota usage",
|
||||
"zh-CN": "查看 Token Plan harness 权益额度用量",
|
||||
},
|
||||
auth: "console",
|
||||
usageArgs: "[flags]",
|
||||
flags: {
|
||||
type: {
|
||||
type: "string",
|
||||
valueHint: "<type>",
|
||||
choices: ["official_tool", "infrastructure"] as const,
|
||||
description: {
|
||||
"en-US": "Filter harness list by type: official_tool, infrastructure",
|
||||
"zh-CN": "按类型筛选 harness 列表:official_tool、infrastructure",
|
||||
},
|
||||
},
|
||||
},
|
||||
exampleArgs: ["", "--type official_tool", "--output json"],
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
const benefitsData = flags.type ? { type: flags.type } : {};
|
||||
const equityData = { queryTokenPlanEquityInfoRequest: { requestId: randomUUID() } };
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
{
|
||||
requests: [
|
||||
{ api: HARNESS_LIST_API, data: benefitsData },
|
||||
{ api: EQUITY_INFO_API, data: equityData },
|
||||
],
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const [benefitsResult, equityResult] = await Promise.all([
|
||||
ctx.client.console(HARNESS_LIST_API, benefitsData),
|
||||
ctx.client.console(EQUITY_INFO_API, equityData),
|
||||
]);
|
||||
const rows = buildRows(
|
||||
readArray<HarnessBenefitItem>(benefitsResult, "items"),
|
||||
readArray<TokenPlanEquityInfo>(equityResult, "tokenPlanEquityInfos"),
|
||||
);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ generatedAt: Date.now(), items: rows }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
process.stdout.write("No harness with entitlement quota found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printQuotaBox("Token Plan Harness Quota", rows.map(toSection), Date.now());
|
||||
},
|
||||
});
|
||||
@@ -65,3 +65,25 @@ export interface AddOrganizationMemberResponse {
|
||||
SeatAssigned?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Console gateway payloads keep the server's camelCase keys, unlike the
|
||||
// PascalCase OpenAPI shapes above.
|
||||
|
||||
export interface HarnessBenefitItem {
|
||||
type?: string;
|
||||
hasResourcePack?: boolean;
|
||||
planCodes?: string[];
|
||||
title?: string;
|
||||
quotaUnit?: string;
|
||||
priceInfo?: { unit?: string } | null;
|
||||
}
|
||||
|
||||
export interface TokenPlanEquityInfo {
|
||||
instanceId?: string;
|
||||
templateCode?: string;
|
||||
equityType?: string;
|
||||
totalQuota?: number;
|
||||
availableQuota?: number;
|
||||
instanceStartTime?: number;
|
||||
instanceEndTime?: number;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ export { default as tokenPlanListSeats } from "./commands/token-plan/list-seats.
|
||||
export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts";
|
||||
export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts";
|
||||
export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts";
|
||||
export { default as tokenPlanHarnessQuota } from "./commands/token-plan/harness-quota.ts";
|
||||
export { default as managedAgentInit } from "./commands/managed-agent/init.ts";
|
||||
export { default as managedAgentValidate } from "./commands/managed-agent/validate.ts";
|
||||
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { makeE2eOutputDir, parseStdoutJson, runCommandHelp, runCommandE2e } from "./helpers.ts";
|
||||
import {
|
||||
isConsoleAuthFailure,
|
||||
isConsoleE2EReady,
|
||||
makeE2eOutputDir,
|
||||
parseStdoutJson,
|
||||
runCommandHelp,
|
||||
runCommandE2e,
|
||||
} from "./helpers.ts";
|
||||
import { TOKEN_PLAN_ROUTES } from "./topic-routes.ts";
|
||||
|
||||
describe("e2e: token-plan", () => {
|
||||
@@ -60,4 +67,92 @@ describe("e2e: token-plan", () => {
|
||||
expect(stderr).toMatch(/ALIBABA_CLOUD_ACCESS_KEY_ID/);
|
||||
expect(stderr).not.toMatch(/auth login --api-key/);
|
||||
});
|
||||
|
||||
test("token-plan harness-quota help 展示 --type 与 console 鉴权域 flags", async () => {
|
||||
const { stderr, exitCode } = await runCommandHelp(TOKEN_PLAN_ROUTES, [
|
||||
"token-plan",
|
||||
"harness-quota",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--type <official_tool\|infrastructure>/);
|
||||
expect(stderr).toMatch(/--console-region/);
|
||||
expect(stderr).not.toMatch(/--access-key-id/);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!isConsoleE2EReady())("e2e: token-plan harness-quota(Console)", () => {
|
||||
test("harness-quota --dry-run 输出两个网关请求计划", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(TOKEN_PLAN_ROUTES, [
|
||||
"token-plan",
|
||||
"harness-quota",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
requests?: Array<{ api?: string; data?: Record<string, unknown> }>;
|
||||
}>(stdout);
|
||||
expect(data.requests?.[0]?.api).toBe("zeldaEasy.broadscope-bailian.token-plan.detail");
|
||||
expect(data.requests?.[1]?.api).toBe(
|
||||
"zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo",
|
||||
);
|
||||
expect(data.requests?.[0]?.data).toEqual({});
|
||||
});
|
||||
|
||||
test("harness-quota --type 透传给 harness 列表接口", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(TOKEN_PLAN_ROUTES, [
|
||||
"token-plan",
|
||||
"harness-quota",
|
||||
"--type",
|
||||
"official_tool",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{ requests?: Array<{ data?: Record<string, unknown> }> }>(stdout);
|
||||
expect(data.requests?.[0]?.data).toEqual({ type: "official_tool" });
|
||||
});
|
||||
|
||||
test("harness-quota --output json 返回权益额度条目", async () => {
|
||||
const result = await runCommandE2e(TOKEN_PLAN_ROUTES, [
|
||||
"token-plan",
|
||||
"harness-quota",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
if (isConsoleAuthFailure(result)) return;
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
generatedAt?: number;
|
||||
items?: Array<{
|
||||
planCode?: string;
|
||||
status?: string;
|
||||
totalQuota?: number;
|
||||
availableQuota?: number;
|
||||
usedQuota?: number;
|
||||
usedPercent?: number;
|
||||
}>;
|
||||
}>(result.stdout);
|
||||
expect(Array.isArray(data.items)).toBe(true);
|
||||
for (const item of data.items ?? []) {
|
||||
expect(item.planCode).toBeTypeOf("string");
|
||||
expect(["issued", "issuing"]).toContain(item.status);
|
||||
const numbers = [item.totalQuota, item.availableQuota, item.usedQuota, item.usedPercent];
|
||||
for (const value of numbers) {
|
||||
if (value !== undefined) expect(value).toBeTypeOf("number");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("harness-quota 默认渲染额度框或空态文案", async () => {
|
||||
const result = await runCommandE2e(TOKEN_PLAN_ROUTES, ["token-plan", "harness-quota"]);
|
||||
if (isConsoleAuthFailure(result)) return;
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(result.stdout).toMatch(
|
||||
/Token Plan Harness Quota|No harness with entitlement quota found/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -177,6 +177,7 @@ export const TOKEN_PLAN_ROUTES: E2eRouteExports = {
|
||||
"token-plan create-key": "tokenPlanCreateKey",
|
||||
"token-plan assign-seats": "tokenPlanAssignSeats",
|
||||
"token-plan add-member": "tokenPlanAddMember",
|
||||
"token-plan harness-quota": "tokenPlanHarnessQuota",
|
||||
};
|
||||
|
||||
export const SKILL_ROUTES: E2eRouteExports = {
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
import { afterEach, describe, expect, test, vi } from "vite-plus/test";
|
||||
import harnessQuota from "../src/commands/token-plan/harness-quota.ts";
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
function captureStdout(): string[] {
|
||||
const output: string[] = [];
|
||||
vi.spyOn(process.stdout, "write").mockImplementation((chunk) => {
|
||||
output.push(String(chunk));
|
||||
return true;
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
/** Gateway envelope produced by `callConsoleGateway` (DataV2 wrapper included). */
|
||||
function wrapResponse(payload: Record<string, unknown>): Record<string, unknown> {
|
||||
return { data: { DataV2: { data: { data: payload } } } };
|
||||
}
|
||||
|
||||
const IMAGE_HARNESS = {
|
||||
type: "official_tool",
|
||||
hasResourcePack: true,
|
||||
title: "图像生成 MCP",
|
||||
quotaUnit: "张",
|
||||
planCodes: ["tokenplan_harnesstool_image_generation", "tokenplan_harnesstool_image_monthly"],
|
||||
};
|
||||
|
||||
const SEARCH_HARNESS = {
|
||||
type: "official_tool",
|
||||
hasResourcePack: true,
|
||||
title: "联网搜索 MCP",
|
||||
priceInfo: { unit: "次" },
|
||||
planCodes: ["tokenplan_harnesstool_web_search"],
|
||||
};
|
||||
|
||||
/** Harness without an entitlement quota — never shown, same as the console card. */
|
||||
const NO_QUOTA_HARNESS = {
|
||||
type: "infrastructure",
|
||||
hasResourcePack: false,
|
||||
title: "无额度 Harness",
|
||||
planCodes: ["tokenplan_harnesstool_no_quota"],
|
||||
};
|
||||
|
||||
async function runHarnessQuota(
|
||||
items: Record<string, unknown>[],
|
||||
equityInfos: Record<string, unknown>[],
|
||||
options: { output?: string; type?: string } = {},
|
||||
): Promise<Record<string, unknown>[]> {
|
||||
const calls: Record<string, unknown>[] = [];
|
||||
await harnessQuota.run({
|
||||
client: {
|
||||
console: vi.fn().mockImplementation((api: string, data: Record<string, unknown>) => {
|
||||
calls.push({ api, data });
|
||||
return Promise.resolve(
|
||||
api.endsWith("token-plan.detail")
|
||||
? wrapResponse({ items })
|
||||
: wrapResponse({ userId: "1256099523640572", tokenPlanEquityInfos: equityInfos }),
|
||||
);
|
||||
}),
|
||||
},
|
||||
flags: options.type ? { type: options.type } : {},
|
||||
settings: { dryRun: false, output: options.output },
|
||||
} as never);
|
||||
return calls;
|
||||
}
|
||||
|
||||
describe("token-plan harness-quota view", () => {
|
||||
test("renders one gauge per harness with issued quota", async () => {
|
||||
const output = captureStdout();
|
||||
|
||||
await runHarnessQuota(
|
||||
[IMAGE_HARNESS, NO_QUOTA_HARNESS],
|
||||
[
|
||||
{
|
||||
instanceId: "instance-1",
|
||||
equityType: "tokenplan_harnesstool_image_generation",
|
||||
totalQuota: 100,
|
||||
availableQuota: 60,
|
||||
instanceStartTime: 1_786_000_000_000,
|
||||
instanceEndTime: 1_788_000_000_000,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const rendered = output.join("");
|
||||
expect(rendered).toContain("Token Plan Harness Quota");
|
||||
expect(rendered).toContain("图像生成 MCP (tokenplan_harnesstool_image_generation)");
|
||||
expect(rendered).toContain("40% used");
|
||||
expect(rendered).toContain("Used: 40 / 100 张");
|
||||
expect(rendered).toMatch(/Resets: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/);
|
||||
// Harness without a resource pack has no entitlement quota at all.
|
||||
expect(rendered).not.toContain("无额度 Harness");
|
||||
});
|
||||
|
||||
test("marks a harness whose entitlement is not issued yet as issuing", async () => {
|
||||
const output = captureStdout();
|
||||
|
||||
await runHarnessQuota(
|
||||
[IMAGE_HARNESS, SEARCH_HARNESS],
|
||||
[
|
||||
{
|
||||
equityType: "tokenplan_harnesstool_image_generation",
|
||||
totalQuota: 100,
|
||||
availableQuota: 60,
|
||||
instanceEndTime: 1_788_000_000_000,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const rendered = output.join("");
|
||||
// Pending harness keeps its first plan code and shows no gauge or reset line.
|
||||
expect(rendered).toContain("联网搜索 MCP (tokenplan_harnesstool_web_search)");
|
||||
expect(rendered).toContain("Quota is being issued; issuance can take up to 5 minutes.");
|
||||
expect(rendered).not.toContain("Resets: not applicable");
|
||||
});
|
||||
|
||||
test("falls back to the price unit when quotaUnit is absent", async () => {
|
||||
const output = captureStdout();
|
||||
|
||||
await runHarnessQuota(
|
||||
[SEARCH_HARNESS],
|
||||
[
|
||||
{
|
||||
equityType: "tokenplan_harnesstool_web_search",
|
||||
totalQuota: 2000,
|
||||
availableQuota: 500,
|
||||
instanceEndTime: 1_788_000_000_000,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
expect(output.join("")).toContain("Used: 1,500 / 2,000 次");
|
||||
});
|
||||
|
||||
test("reports a non-positive quota total instead of a gauge", async () => {
|
||||
const output = captureStdout();
|
||||
|
||||
await runHarnessQuota(
|
||||
[IMAGE_HARNESS],
|
||||
[
|
||||
{
|
||||
equityType: "tokenplan_harnesstool_image_monthly",
|
||||
totalQuota: 0,
|
||||
availableQuota: 0,
|
||||
instanceEndTime: 1_788_000_000_000,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
expect(output.join("")).toContain(
|
||||
"No positive quota total reported; check the Bailian Token Plan console.",
|
||||
);
|
||||
});
|
||||
|
||||
test("prints the empty state when no harness carries an entitlement quota", async () => {
|
||||
const output = captureStdout();
|
||||
|
||||
await runHarnessQuota([NO_QUOTA_HARNESS], []);
|
||||
|
||||
expect(output.join("")).toBe("No harness with entitlement quota found.\n");
|
||||
});
|
||||
|
||||
test("passes --type through to the harness list API only", async () => {
|
||||
captureStdout();
|
||||
|
||||
const calls = await runHarnessQuota([], [], { type: "infrastructure" });
|
||||
|
||||
expect(calls[0]).toEqual({
|
||||
api: "zeldaEasy.broadscope-bailian.token-plan.detail",
|
||||
data: { type: "infrastructure" },
|
||||
});
|
||||
expect(calls[1]?.api).toBe("zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("token-plan harness-quota json", () => {
|
||||
test("emits the joined quota fields with --output json", async () => {
|
||||
const output = captureStdout();
|
||||
|
||||
await runHarnessQuota(
|
||||
[IMAGE_HARNESS, SEARCH_HARNESS],
|
||||
[
|
||||
{
|
||||
instanceId: "instance-1",
|
||||
equityType: "tokenplan_harnesstool_image_generation",
|
||||
totalQuota: 100,
|
||||
availableQuota: 60,
|
||||
instanceStartTime: 1_786_000_000_000,
|
||||
instanceEndTime: 1_788_000_000_000,
|
||||
},
|
||||
],
|
||||
{ output: "json" },
|
||||
);
|
||||
|
||||
const parsed = JSON.parse(output.join("")) as { items: Record<string, unknown>[] };
|
||||
expect(parsed.items).toEqual([
|
||||
{
|
||||
planCode: "tokenplan_harnesstool_image_generation",
|
||||
title: "图像生成 MCP",
|
||||
type: "official_tool",
|
||||
status: "issued",
|
||||
quotaUnit: "张",
|
||||
instanceId: "instance-1",
|
||||
totalQuota: 100,
|
||||
availableQuota: 60,
|
||||
usedQuota: 40,
|
||||
usedPercent: 40,
|
||||
instanceStartTime: 1_786_000_000_000,
|
||||
instanceEndTime: 1_788_000_000_000,
|
||||
},
|
||||
{
|
||||
planCode: "tokenplan_harnesstool_web_search",
|
||||
title: "联网搜索 MCP",
|
||||
type: "official_tool",
|
||||
status: "issuing",
|
||||
quotaUnit: "次",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("treats non-numeric quota fields as absent instead of failing", async () => {
|
||||
const output = captureStdout();
|
||||
|
||||
await runHarnessQuota(
|
||||
[IMAGE_HARNESS],
|
||||
[
|
||||
{
|
||||
equityType: "tokenplan_harnesstool_image_generation",
|
||||
totalQuota: "not-a-number",
|
||||
availableQuota: Number.NaN,
|
||||
},
|
||||
],
|
||||
{ output: "json" },
|
||||
);
|
||||
|
||||
const parsed = JSON.parse(output.join("")) as { items: Record<string, unknown>[] };
|
||||
expect(parsed.items[0]).toEqual({
|
||||
planCode: "tokenplan_harnesstool_image_generation",
|
||||
title: "图像生成 MCP",
|
||||
type: "official_tool",
|
||||
status: "issued",
|
||||
quotaUnit: "张",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-core",
|
||||
"version": "1.24.0",
|
||||
"version": "1.25.0",
|
||||
"description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "knowledge-studio-cli",
|
||||
"version": "1.24.0",
|
||||
"version": "1.25.0",
|
||||
"description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.",
|
||||
"keywords": [
|
||||
"alibaba-cloud",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-runtime",
|
||||
"version": "1.24.0",
|
||||
"version": "1.25.0",
|
||||
"description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-cli
|
||||
metadata:
|
||||
version: "1.24.0"
|
||||
version: "1.25.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
@@ -74,6 +74,7 @@ Use this table only after the decision table in [`bailian-protocol`](../bailian-
|
||||
| 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 |
|
||||
| Token Plan harness entitlement quota | `bl token-plan harness-quota` | Console auth; issued quota usage + still-issuing harnesses |
|
||||
| Bailian Coding Plan quota usage | `bl usage coding-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 |
|
||||
|
||||
@@ -95,6 +95,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `bl token-plan add-member` | AK/SK | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan assign-seats` | AK/SK | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan create-key` | AK/SK | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan harness-quota` | Console | Show Token Plan harness entitlement quota usage | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan list-seats` | AK/SK | List Token Plan subscription seat details | [token-plan.md](token-plan.md) |
|
||||
| `bl update` | No Auth | Update the CLI to the latest or a specified version | [update.md](update.md) |
|
||||
| `bl usage coding-plan` | Console | Show Coding Plan quota usage | [usage.md](usage.md) |
|
||||
@@ -127,7 +128,7 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `harness-quota`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
| `usage` | `coding-plan`, `free`, `freetier`, `stats`, `summary`, `token-plan` | [usage.md](usage.md) |
|
||||
| `workspace` | `init`, `list` | [workspace.md](workspace.md) |
|
||||
|
||||
@@ -7,12 +7,13 @@ Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Authentication | Description |
|
||||
| ---------------------------- | -------------- | ----------------------------------------- |
|
||||
| `bl token-plan add-member` | AK/SK | Add a member to a Token Plan organization |
|
||||
| `bl token-plan assign-seats` | AK/SK | Batch assign Token Plan seats to members |
|
||||
| `bl token-plan create-key` | AK/SK | Create a Token Plan API key for a seat |
|
||||
| `bl token-plan list-seats` | AK/SK | List Token Plan subscription seat details |
|
||||
| Command | Authentication | Description |
|
||||
| ----------------------------- | -------------- | ----------------------------------------------- |
|
||||
| `bl token-plan add-member` | AK/SK | Add a member to a Token Plan organization |
|
||||
| `bl token-plan assign-seats` | AK/SK | Batch assign Token Plan seats to members |
|
||||
| `bl token-plan create-key` | AK/SK | Create a Token Plan API key for a seat |
|
||||
| `bl token-plan harness-quota` | Console | Show Token Plan harness entitlement quota usage |
|
||||
| `bl token-plan list-seats` | AK/SK | List Token Plan subscription seat details |
|
||||
|
||||
## Command details
|
||||
|
||||
@@ -118,6 +119,39 @@ bl token-plan create-key --account-id acc_123 --workspace-id ws_456
|
||||
bl token-plan create-key --account-id acc_123 --workspace-id ws_456 --description 'Dev key'
|
||||
```
|
||||
|
||||
### `bl token-plan harness-quota`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ----------------------------------------------- |
|
||||
| **Name** | `token-plan harness-quota` |
|
||||
| **Description** | Show Token Plan harness entitlement quota usage |
|
||||
| **Authentication** | Console |
|
||||
| **Usage** | `bl token-plan harness-quota [flags]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------------------------- | ------ | -------- | ---------------------------------------------------------- |
|
||||
| `--type <official_tool\|infrastructure>` | string | no | Filter harness list by type: official_tool, infrastructure |
|
||||
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl token-plan harness-quota
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan harness-quota --type official_tool
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan harness-quota --output json
|
||||
```
|
||||
|
||||
### `bl token-plan list-seats`
|
||||
|
||||
| Field | Value |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-finetune
|
||||
metadata:
|
||||
version: "1.24.0"
|
||||
version: "1.25.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-gen
|
||||
metadata:
|
||||
version: "1.24.0"
|
||||
version: "1.25.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-managed-agent
|
||||
metadata:
|
||||
version: "1.24.0"
|
||||
version: "1.25.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-protocol
|
||||
metadata:
|
||||
version: "1.24.0"
|
||||
version: "1.25.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-sandbox
|
||||
metadata:
|
||||
version: "1.24.0"
|
||||
version: "1.25.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-web-search
|
||||
metadata:
|
||||
version: "1.24.0"
|
||||
version: "1.25.0"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
Reference in New Issue
Block a user