Merge branch 'main' into feat/update-intent-model

This commit is contained in:
gujieye
2026-06-17 15:03:51 +08:00
committed by GitHub
51 changed files with 825 additions and 404 deletions
+12
View File
@@ -93,6 +93,18 @@ CLI 只为「自己能权威解释的错误」发出语义化信号,服务端的
不要扮演服务端错误的翻译官——我们没有最新的错误码体系认知,二次包装只会撒谎(详见 `docs/agents/error-hint-change.md` 中的反面 case)。
### 4. Console Gateway 命令必须声明 console 全局 flags
如果新命令使用了 `callConsoleGateway`,必须在 `options` 中添加以下三个全局 flag 的说明,以便 `--help` 中展示:
```ts
{ flag: "--console-region <region>", description: "Console region" },
{ flag: "--console-site <site>", description: "Console site: domestic, international" },
{ flag: "--console-switch-agent <uid>", description: "Switch agent UID", type: "number" },
```
这些 flag 已在 `GLOBAL_OPTIONS`(`packages/core/src/types/command.ts`)中注册,由 `loadConfig` 写入 `config.consoleRegion` / `config.consoleSite` / `config.consoleSwitchAgent`,`callConsoleGateway` 自动读取——命令无需手动提取或传递。
## 完成改动后的快速验证
```sh
+1 -1
View File
@@ -102,7 +102,7 @@ bl auth status --output json
bl text chat --message "ping" --non-interactive --output json
```
若失败:根据 stderr / JSON 中的 `hint` 或 `message` 排查(网络、Key 无效、region 等)。全局 region:`--region cn|us|intl`,默认 `cn`。
若失败:根据 stderr / JSON 中的 `hint` 或 `message` 排查(网络、Key 无效、`base_url` 等)。DashScope 端点:使用 `--base-url` / `bl config set --key base_url` / `DASHSCOPE_BASE_URL`,默认中国大陆 `https://dashscope.aliyuncs.com`。
---
+1 -1
View File
@@ -172,7 +172,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
bl config show
# Set defaults
bl config set --key region --value us
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
bl config set --key default_text_model --value qwen-turbo
bl config set --key timeout --value 600
+1 -1
View File
@@ -167,7 +167,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
bl config show
# 设置默认值
bl config set --key region --value us
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
bl config set --key default_text_model --value qwen-turbo
bl config set --key timeout --value 600
+1 -1
View File
@@ -172,7 +172,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
bl config show
# Set defaults
bl config set --key region --value us
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
bl config set --key default_text_model --value qwen-turbo
bl config set --key timeout --value 600
+1 -1
View File
@@ -167,7 +167,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
bl config show
# 设置默认值
bl config set --key region --value us
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
bl config set --key default_text_model --value qwen-turbo
bl config set --key timeout --value 600
+9 -8
View File
@@ -30,9 +30,15 @@ export default defineCommand({
description: "Results per page (default: 30)",
type: "number",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -45,7 +51,6 @@ export default defineCommand({
const name = (flags.name as string) || "";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
const credential = await resolveConsoleGatewayCredential(config);
@@ -62,17 +67,13 @@ export default defineCommand({
};
if (config.dryRun) {
emitResult(
{ api: APP_LIST_API, data, region, token: credential.token.slice(0, 8) + "..." },
format,
);
emitResult({ api: APP_LIST_API, data, token: credential.token.slice(0, 8) + "..." }, format);
return;
}
const result = (await callConsoleGateway(config, credential.token, {
api: APP_LIST_API,
data,
region,
})) as any;
const list: unknown[] = result?.data?.DataV2?.data?.data?.list ?? [];
+193 -13
View File
@@ -5,18 +5,24 @@ import http from "node:http";
import {
BailianError,
ExitCode,
chatEndpoint,
getConfigPath,
readConfigFile,
requestJson,
writeConfigFile,
type Config,
} from "bailian-cli-core";
const CONSOLE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
const MAX_AUTH_CALLBACK_BODY = 65536;
const DEFAULT_CONSOLE_ORIGIN = "https://bailian.console.aliyun.com";
const CONSOLE_ORIGINS: Record<string, string> = {
domestic: "https://bailian.console.aliyun.com",
international: "https://modelstudio.console.alibabacloud.com",
};
export function resolveConsoleOrigin(): string {
return process.env.BAILIAN_CONSOLE_ORIGIN || DEFAULT_CONSOLE_ORIGIN;
export function resolveConsoleOrigin(site?: string): string {
return (site && CONSOLE_ORIGINS[site]) || CONSOLE_ORIGINS.domestic!;
}
function readBodyBounded(req: http.IncomingMessage): Promise<string> {
@@ -210,9 +216,76 @@ function parseApiKeyFromRawBody(raw: string, contentType: string): string | null
return null;
}
type CallbackExtras = Pick<
CallbackCredentials,
"baseUrl" | "consoleSite" | "consoleRegion" | "consoleSwitchAgent" | "workspaceId"
>;
function stringField(o: Record<string, unknown>, ...keys: string[]): string | null {
for (const k of keys) {
const v = o[k];
if (typeof v === "string" && v.trim()) return v.trim();
}
return null;
}
function parseExtrasFromRawBody(raw: string, contentType: string): CallbackExtras {
const empty: CallbackExtras = {
baseUrl: null,
consoleSite: null,
consoleRegion: null,
consoleSwitchAgent: null,
workspaceId: null,
};
if (!raw.trim()) return empty;
let obj: Record<string, unknown> | null = null;
const ct = contentType.toLowerCase();
if (ct.includes("application/json") || ct.includes("text/json")) {
try {
const parsed = JSON.parse(raw.trim());
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) obj = parsed;
} catch {
/* */
}
}
if (!obj && ct.includes("application/x-www-form-urlencoded")) {
try {
const params = new URLSearchParams(raw.trim());
obj = Object.fromEntries(params);
} catch {
/* */
}
}
if (!obj) {
try {
const parsed = JSON.parse(raw.trim());
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) obj = parsed;
} catch {
/* */
}
}
if (!obj) return empty;
return {
baseUrl: stringField(obj, "base_url", "baseUrl"),
consoleSite: stringField(obj, "console_site", "consoleSite"),
consoleRegion: stringField(obj, "console_region", "consoleRegion"),
consoleSwitchAgent: stringField(obj, "console_switch_agent", "consoleSwitchAgent"),
workspaceId: stringField(obj, "workspace_id", "workspaceId"),
};
}
interface CallbackCredentials {
accessToken: string | null;
apiKey: string | null;
baseUrl: string | null;
consoleSite: string | null;
consoleRegion: string | null;
consoleSwitchAgent: string | null;
workspaceId: string | null;
}
async function extractCredentialsFromRequest(
@@ -222,12 +295,30 @@ async function extractCredentialsFromRequest(
const accessTokenFromQuery =
u.searchParams.get("access_token") ?? u.searchParams.get("accessToken");
const apiKeyFromQuery = u.searchParams.get("api_key") ?? u.searchParams.get("apiKey");
const baseUrlFromQuery = u.searchParams.get("base_url") ?? u.searchParams.get("baseUrl");
const consoleSiteFromQuery =
u.searchParams.get("console_site") ?? u.searchParams.get("consoleSite");
const consoleRegionFromQuery =
u.searchParams.get("console_region") ?? u.searchParams.get("consoleRegion");
const consoleSwitchAgentFromQuery =
u.searchParams.get("console_switch_agent") ?? u.searchParams.get("consoleSwitchAgent");
const workspaceIdFromQuery =
u.searchParams.get("workspace_id") ?? u.searchParams.get("workspaceId");
const extras = {
baseUrl: baseUrlFromQuery?.trim() || null,
consoleSite: consoleSiteFromQuery?.trim() || null,
consoleRegion: consoleRegionFromQuery?.trim() || null,
consoleSwitchAgent: consoleSwitchAgentFromQuery?.trim() || null,
workspaceId: workspaceIdFromQuery?.trim() || null,
};
const m = req.method ?? "GET";
if (m !== "POST" && m !== "PUT" && m !== "PATCH") {
return {
accessToken: accessTokenFromQuery?.trim() || null,
apiKey: apiKeyFromQuery?.trim() || null,
...extras,
};
}
@@ -239,12 +330,24 @@ async function extractCredentialsFromRequest(
return {
accessToken: accessTokenFromQuery?.trim() || null,
apiKey: apiKeyFromQuery?.trim() || null,
...extras,
};
}
const accessToken = accessTokenFromQuery?.trim() || parseAccessTokenFromRawBody(raw, contentType);
const apiKey = apiKeyFromQuery?.trim() || parseApiKeyFromRawBody(raw, contentType);
return { accessToken, apiKey };
const bodyExtras = parseExtrasFromRawBody(raw, contentType);
return {
accessToken,
apiKey,
baseUrl: extras.baseUrl || bodyExtras.baseUrl,
consoleSite: extras.consoleSite || bodyExtras.consoleSite,
consoleRegion: extras.consoleRegion || bodyExtras.consoleRegion,
consoleSwitchAgent: extras.consoleSwitchAgent || bodyExtras.consoleSwitchAgent,
workspaceId: extras.workspaceId || bodyExtras.workspaceId,
};
}
function listenServerOnFreeLocalPort(server: http.Server): Promise<number> {
@@ -276,9 +379,69 @@ function openInBrowser(url: string): Promise<void> {
});
}
const RETRY_DELAY_BASE_MS = 500;
function canRetry(err: unknown): boolean {
if (err instanceof BailianError) {
if (err.exitCode === ExitCode.NETWORK || err.exitCode === ExitCode.TIMEOUT) return true;
const status = err.api?.httpStatus;
return status === 401 || (status !== undefined && status >= 500);
}
if (err instanceof Error) {
return (
err.name === "AbortError" ||
err.name === "TimeoutError" ||
err.message.includes("timed out") ||
err.message === "fetch failed"
);
}
return false;
}
export async function validateAndPersistApiKey(
config: Config,
key: string,
baseUrl: string,
): Promise<void> {
process.stderr.write("Testing key... ");
const testConfig = { ...config, apiKey: key, baseUrl };
const requestOpts = {
url: chatEndpoint(testConfig.baseUrl),
method: "POST",
timeout: Math.min(config.timeout, 30),
body: {
model: "qwen3.7-max",
messages: [{ role: "user", content: "hi" }],
max_tokens: 1,
},
};
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await requestJson<unknown>(testConfig, requestOpts);
break;
} catch (err) {
if (attempt >= 3 || !canRetry(err)) {
process.stderr.write("Failed\n");
throw new BailianError("API key validation failed", ExitCode.AUTH, "Invalid API key.", {
cause: err,
});
}
const delayMs = RETRY_DELAY_BASE_MS * 2 ** (attempt - 1);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
process.stderr.write("Valid\n");
const existing = readConfigFile() as Record<string, unknown>;
existing.api_key = key;
await writeConfigFile(existing);
}
export async function runConsoleLogin(
consoleOrigin: string,
opts?: { needApiKey?: boolean; onApiKey?: (key: string) => Promise<void> },
config: Config,
opts?: { needApiKey?: boolean },
): Promise<void> {
const state = randomBytes(16).toString("hex");
let callbackError: unknown;
@@ -301,18 +464,35 @@ export async function runConsoleLogin(
return;
}
const { accessToken, apiKey } = await extractCredentialsFromRequest(req);
const {
accessToken,
apiKey,
baseUrl,
consoleSite,
consoleRegion,
consoleSwitchAgent,
workspaceId,
} = await extractCredentialsFromRequest(req);
if (accessToken || apiKey) {
const hasConfig =
accessToken || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent || workspaceId;
if (hasConfig || apiKey) {
try {
if (accessToken) {
if (hasConfig) {
const existing = readConfigFile() as Record<string, unknown>;
existing.access_token = accessToken;
if (accessToken) existing.access_token = accessToken;
if (baseUrl) existing.base_url = baseUrl;
if (consoleSite) existing.console_site = consoleSite;
if (consoleRegion) existing.console_region = consoleRegion;
if (consoleSwitchAgent) existing.console_switch_agent = Number(consoleSwitchAgent);
if (workspaceId) existing.workspace_id = workspaceId;
await writeConfigFile(existing);
process.stderr.write(`access_token saved to ${getConfigPath()}\n`);
process.stderr.write(`Config saved to ${getConfigPath()}\n`);
}
if (apiKey && opts?.onApiKey) {
await opts.onApiKey(apiKey);
if (apiKey) {
const testBaseUrl = baseUrl || config.baseUrl;
await validateAndPersistApiKey(config, apiKey, testBaseUrl);
}
} catch (err: unknown) {
callbackError = err;
@@ -329,7 +509,7 @@ export async function runConsoleLogin(
});
res.end("OK\n");
if (accessToken || apiKey) {
if (hasConfig || apiKey) {
server.close();
}
} catch {
+21 -71
View File
@@ -1,13 +1,8 @@
import {
BailianError,
ExitCode,
chatEndpoint,
defineCommand,
getConfigPath,
isInteractive,
maskToken,
readConfigFile,
requestJson,
writeConfigFile,
type Config,
type GlobalFlags,
@@ -16,67 +11,11 @@ import { printQuickStart } from "../../output/banner.ts";
import { emitBare } from "../../output/output.ts";
import { promptConfirm } from "../../output/prompt.ts";
import { printCurrentCommandHelp } from "../../utils/command-help.ts";
import { resolveConsoleOrigin, runConsoleLogin } from "./login-console.ts";
const RETRY_DELAY_BASE_MS = 500;
function canRetry(err: unknown): boolean {
if (err instanceof BailianError) {
if (err.exitCode === ExitCode.NETWORK || err.exitCode === ExitCode.TIMEOUT) {
return true;
}
const status = err.api?.httpStatus;
return status === 401 || (status !== undefined && status >= 500);
}
if (err instanceof Error) {
return (
err.name === "AbortError" ||
err.name === "TimeoutError" ||
err.message.includes("timed out") ||
err.message === "fetch failed"
);
}
return false;
}
async function validateKeyAndPersist(config: Config, key: string): Promise<void> {
process.stderr.write("Testing key... ");
const testConfig = { ...config, apiKey: key };
const requestOpts = {
url: chatEndpoint(testConfig.baseUrl),
method: "POST",
timeout: Math.min(config.timeout, 30),
body: {
model: "qwen3.7-max",
messages: [{ role: "user", content: "hi" }],
max_tokens: 1,
},
};
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await requestJson<unknown>(testConfig, requestOpts);
break;
} catch (err) {
if (attempt >= 3 || !canRetry(err)) {
process.stderr.write("\n");
throw new BailianError("API key validation failed", ExitCode.AUTH, "Invalid API key.", {
cause: err,
});
}
// retry delay: 500ms, 1000ms, 2000ms
const delayMs = RETRY_DELAY_BASE_MS * 2 ** (attempt - 1);
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
process.stderr.write("Valid\n");
const existing = readConfigFile() as Record<string, unknown>;
existing.api_key = key;
await writeConfigFile(existing);
process.stderr.write(`Saved to ${getConfigPath()}\n`);
}
import {
resolveConsoleOrigin,
runConsoleLogin,
validateAndPersistApiKey,
} from "./login-console.ts";
export default defineCommand({
name: "auth login",
@@ -85,10 +24,14 @@ export default defineCommand({
usage: "bl auth login --api-key <key> | bl auth login --console",
options: [
{ flag: "--api-key <key>", description: "DashScope API key to store" },
{
flag: "--base-url <url>",
description: "DashScope API base URL (used with --api-key for validation)",
},
{
flag: "--console",
description: "Sign in via browser; opens the console login URL in your default browser",
type: "boolean",
description:
"Sign in via browser; use --console-site to choose domestic (default) or international",
},
],
examples: ["bl auth login --api-key sk-xxxxx", "bl auth login --console"],
@@ -101,9 +44,8 @@ export default defineCommand({
return;
}
const hasApiKey = !!(config.apiKey || config.fileApiKey);
await runConsoleLogin(resolveConsoleOrigin(), {
await runConsoleLogin(resolveConsoleOrigin(config.consoleSite || "domestic"), config, {
needApiKey: !hasApiKey,
onApiKey: (key) => validateKeyAndPersist(config, key),
});
return;
}
@@ -131,8 +73,16 @@ export default defineCommand({
process.exit(0);
}
const baseUrl = (flags.baseUrl as string) || undefined;
const effectiveConfig = baseUrl ? { ...config, baseUrl } : config;
if (!config.dryRun) {
await validateKeyAndPersist(config, key);
if (baseUrl) {
const existing = readConfigFile() as Record<string, unknown>;
existing.base_url = baseUrl;
await writeConfigFile(existing);
}
await validateAndPersistApiKey(effectiveConfig, key, effectiveConfig.baseUrl);
printQuickStart();
} else {
emitBare("Would validate and save API key.");
+12
View File
@@ -142,6 +142,18 @@ export default defineCommand({
name: "auth status",
description: "Show current authentication state",
usage: "bl auth status",
options: [
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: ["bl auth status", "bl auth status --output json"],
async run(config: Config, _flags: GlobalFlags) {
const format = detectOutputFormat(config.output);
+1 -9
View File
@@ -12,7 +12,6 @@ import {
import { emitResult } from "../../output/output.ts";
const VALID_KEYS = [
"region",
"base_url",
"output",
"output_dir",
@@ -59,7 +58,7 @@ export default defineCommand({
{
flag: "--key <key>",
description:
"Config key (region, base_url, output, output_dir, timeout, api_key, access_token, default_*_model, access_key_id, access_key_secret, workspace_id)",
"Config key (base_url, output, output_dir, timeout, api_key, access_token, default_*_model, access_key_id, access_key_secret, workspace_id)",
},
{ flag: "--value <value>", description: "Value to set" },
],
@@ -91,13 +90,6 @@ export default defineCommand({
}
// Validate specific values
if (resolvedKey === "region" && !["cn", "us", "intl"].includes(value)) {
throw new BailianError(
`Invalid region "${value}". Valid values: cn, us, intl`,
ExitCode.USAGE,
);
}
if (resolvedKey === "output" && !["text", "json"].includes(value)) {
throw new BailianError(
`Invalid output format "${value}". Valid values: text, json`,
+8 -12
View File
@@ -20,25 +20,21 @@ export default defineCommand({
const format = detectOutputFormat(config.output);
const result: Record<string, unknown> = {
region: config.region,
...file,
base_url: config.baseUrl,
output: config.output,
timeout: config.timeout,
config_file: getConfigPath(),
};
// Mask API key if present
if (file.api_key) {
result.api_key = maskToken(file.api_key);
if (typeof result.api_key === "string") result.api_key = maskToken(result.api_key);
if (typeof result.access_token === "string")
result.access_token = maskToken(result.access_token);
if (typeof result.access_key_id === "string")
result.access_key_id = maskToken(result.access_key_id);
if (typeof result.access_key_secret === "string") {
result.access_key_secret = maskToken(result.access_key_secret);
}
if (file.access_token) {
result.access_token = maskToken(file.access_token);
}
// Default models
if (file.default_text_model) result.default_text_model = file.default_text_model;
if (file.default_video_model) result.default_video_model = file.default_video_model;
if (file.default_image_model) result.default_image_model = file.default_image_model;
emitResult(result, format);
},
+19 -6
View File
@@ -1,6 +1,7 @@
import {
defineCommand,
callConsoleGateway,
effectiveConsoleGatewayConfig,
resolveConsoleGatewayCredential,
CONSOLE_GATEWAY_NO_TOKEN_MESSAGE,
BailianError,
@@ -27,14 +28,20 @@ export default defineCommand({
description: "Request data as JSON string",
required: true,
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
`bl console call --api zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}'`,
`bl console call --api some.api.name --data '{"key":"value"}' --region cn-beijing`,
`bl console call --api some.api.name --data '{"key":"value"}' --console-region cn-beijing`,
],
async run(config: Config, flags: GlobalFlags) {
const api = flags.api as string;
@@ -51,7 +58,6 @@ export default defineCommand({
process.exit(1);
}
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
let token: string | undefined;
@@ -64,14 +70,21 @@ export default defineCommand({
}
if (config.dryRun) {
emitResult({ api, data, region, token: token ? token.slice(0, 8) + "..." : null }, format);
emitResult(
{
api,
data,
token: token ? token.slice(0, 8) + "..." : null,
...effectiveConsoleGatewayConfig(config),
},
format,
);
return;
}
const result = await callConsoleGateway(config, token, {
api,
data,
region,
});
emitResult(result, format);
+12 -4
View File
@@ -1,6 +1,7 @@
import {
defineCommand,
callConsoleGateway,
effectiveConsoleGatewayConfig,
resolveConsoleGatewayCredential,
detectOutputFormat,
BailianError,
@@ -36,7 +37,16 @@ export default defineCommand({
},
{ flag: "--page <n>", description: "Page number (default: 1)", type: "number" },
{ flag: "--page-size <n>", description: "Results per page (default: 30)", type: "number" },
{ flag: "--region <region>", description: "API region (default: cn-beijing)" },
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: ["bl mcp list", "bl mcp list --name finance", "bl mcp list --output json"],
async run(config: Config, flags: GlobalFlags) {
@@ -44,7 +54,6 @@ export default defineCommand({
const type = (flags.type as string) || "OFFICIAL";
const pageNo = (flags.page as number) || 1;
const pageSize = (flags.pageSize as number) || 30;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
const data = {
@@ -59,7 +68,7 @@ export default defineCommand({
};
if (config.dryRun) {
emitResult({ api: MCP_LIST_API, data, region }, format);
emitResult({ api: MCP_LIST_API, data, ...effectiveConsoleGatewayConfig(config) }, format);
return;
}
@@ -68,7 +77,6 @@ export default defineCommand({
const result = (await callConsoleGateway(config, credential.token, {
api: MCP_LIST_API,
data,
region,
})) as Record<string, unknown>;
const dataField = (result?.data as Record<string, unknown> | undefined) ?? {};
+15 -16
View File
@@ -1,6 +1,7 @@
import {
defineCommand,
callConsoleGateway,
effectiveConsoleGatewayConfig,
resolveConsoleGatewayCredential,
detectOutputFormat,
type Config,
@@ -91,11 +92,7 @@ function extractResponseData(result: Record<string, unknown>): Record<string, un
return direct ?? data;
}
async function fetchAllModelsWithQpm(
config: Config,
token: string,
region: string,
): Promise<ModelWithQpm[]> {
async function fetchAllModelsWithQpm(config: Config, token: string): Promise<ModelWithQpm[]> {
const allModels: ModelWithQpm[] = [];
let pageNo = 1;
@@ -112,7 +109,6 @@ async function fetchAllModelsWithQpm(
supports: { selfServiceLimitIncrease: true },
},
},
region,
});
const resp = extractResponseData(raw as Record<string, unknown>);
@@ -130,7 +126,6 @@ async function fetchAllModelsWithQpm(
async function fetchMonitorData(
config: Config,
token: string,
region: string,
modelName: string,
windowMinutes: number,
): Promise<{ rpm: number; tpm: number }> {
@@ -155,7 +150,6 @@ async function fetchMonitorData(
endTime: now,
},
},
region,
});
const resp = extractResponseData(raw as Record<string, unknown>);
@@ -254,9 +248,15 @@ export default defineCommand({
flag: "--period <minutes>",
description: "Query usage for the last N minutes (default: 2)",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -274,23 +274,22 @@ export default defineCommand({
process.exit(1);
}
const windowMinutes = rawPeriod;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
const credential = await resolveConsoleGatewayCredential(config);
if (config.dryRun) {
emitResult(
{
apis: [MODEL_LIST_API, MONITOR_API],
region,
...effectiveConsoleGatewayConfig(config),
},
format,
);
return;
}
let models = await fetchAllModelsWithQpm(config, credential.token, region);
const credential = await resolveConsoleGatewayCredential(config);
let models = await fetchAllModelsWithQpm(config, credential.token);
if (modelFlag) {
const names = new Set(
@@ -310,7 +309,7 @@ export default defineCommand({
}
const monitorResults = await Promise.all(
models.map((m) => fetchMonitorData(config, credential.token, region, m.model, windowMinutes)),
models.map((m) => fetchMonitorData(config, credential.token, m.model, windowMinutes)),
);
const checkRows: CheckRow[] = models.map((m, idx) => {
+9 -5
View File
@@ -108,9 +108,15 @@ export default defineCommand({
flag: "--model <model>",
description: "Filter by model name",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -124,7 +130,6 @@ export default defineCommand({
const page = Number(flags.page) || 1;
const pageSize = Number(flags.pageSize) || 10;
const modelFilter = (flags.model as string) || undefined;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
const requestData = {
@@ -132,7 +137,7 @@ export default defineCommand({
};
if (config.dryRun) {
emitResult({ api: HISTORY_API, data: requestData, region }, format);
emitResult({ api: HISTORY_API, data: requestData }, format);
return;
}
@@ -143,7 +148,6 @@ export default defineCommand({
result = await callConsoleGateway(config, credential.token, {
api: HISTORY_API,
data: requestData,
region,
});
} catch (err) {
if (err instanceof BailianError && err.message.includes("NotLogined")) {
+9 -6
View File
@@ -68,7 +68,6 @@ function extractResponseData(result: Record<string, unknown>): Record<string, un
async function fetchAllModelsWithQpm(
config: Config,
token: string,
region: string,
onlySelfService: boolean,
): Promise<ModelWithQpm[]> {
const allModels: ModelWithQpm[] = [];
@@ -89,7 +88,6 @@ async function fetchAllModelsWithQpm(
const raw = await callConsoleGateway(config, token, {
api: MODEL_LIST_API,
data: { input },
region,
});
const resp = extractResponseData(raw as Record<string, unknown>);
@@ -165,9 +163,15 @@ export default defineCommand({
flag: "--all",
description: "Show all models, not just self-service ones",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -180,7 +184,6 @@ export default defineCommand({
async run(config: Config, flags: GlobalFlags) {
const modelFlag = (flags.model as string) || undefined;
const showAll = Boolean(flags.all);
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
if (config.dryRun) {
@@ -192,7 +195,7 @@ export default defineCommand({
ignoreWorkspaceServiceSite: true,
};
if (!showAll) input.supports = { selfServiceLimitIncrease: true };
emitResult({ api: MODEL_LIST_API, data: { input }, region }, format);
emitResult({ api: MODEL_LIST_API, data: { input } }, format);
return;
}
+9 -7
View File
@@ -53,7 +53,6 @@ function extractResponseData(result: Record<string, unknown>): Record<string, un
async function fetchModelQpmInfo(
config: Config,
token: string,
region: string,
modelName: string,
): Promise<{ model: string; qpmInfo: Record<string, QpmInfoItem> } | undefined> {
const raw = await callConsoleGateway(config, token, {
@@ -69,7 +68,6 @@ async function fetchModelQpmInfo(
supports: { selfServiceLimitIncrease: true },
},
},
region,
});
const resp = extractResponseData(raw as Record<string, unknown>);
@@ -99,9 +97,15 @@ export default defineCommand({
flag: "--yes",
description: "Skip downgrade confirmation",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -123,7 +127,6 @@ export default defineCommand({
}
const autoConfirm = Boolean(flags.yes) || config.yes;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
if (config.dryRun) {
@@ -139,7 +142,7 @@ export default defineCommand({
const credential = await resolveConsoleGatewayCredential(config);
const modelInfo = await fetchModelQpmInfo(config, credential.token, region, modelName);
const modelInfo = await fetchModelQpmInfo(config, credential.token, modelName);
if (!modelInfo) {
process.stderr.write(
`Error: model "${modelName}" not found or does not support self-service quota increase.\n`,
@@ -179,7 +182,6 @@ export default defineCommand({
return await callConsoleGateway(config, credential.token, {
api: UPDATE_LIMITS_API,
data: requestData,
region,
});
} catch (err) {
if (err instanceof BailianError && err.message.includes("NotLogined")) {
+9 -6
View File
@@ -201,9 +201,15 @@ export default defineCommand({
flag: "--sort <field>",
description: "Sort by: remaining (ascending), expires (ascending)",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -213,7 +219,7 @@ export default defineCommand({
"bl usage free --expiring 30",
"bl usage free --sort remaining",
"bl usage free --model qwen-turbo --output json",
"bl usage free --model qwen3-max --region cn-beijing",
"bl usage free --model qwen3-max --console-region cn-beijing",
],
async run(config: Config, flags: GlobalFlags) {
const modelFlag = (flags.model as string) || undefined;
@@ -226,7 +232,6 @@ export default defineCommand({
);
process.exit(1);
}
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
let models: string[];
@@ -286,12 +291,10 @@ export default defineCommand({
callConsoleGateway(config, credential.token, {
api: FREE_TIER_API,
data: requestData,
region,
}),
callConsoleGateway(config, credential.token, {
api: FREE_TIER_ONLY_STATUS_API,
data: { queryFreeTierOnlyStatusRequest: { models } },
region,
}),
]);
+10 -9
View File
@@ -63,7 +63,6 @@ async function pollUntilDone(
api: string,
requestKey: string,
models: string[],
region: string,
): Promise<unknown> {
let nextTaskId: string | undefined;
@@ -75,7 +74,6 @@ async function pollUntilDone(
const raw = await callConsoleGateway(config, token, {
api,
data: requestData,
region,
});
const resp = extractResponseData(raw as Record<string, unknown>);
@@ -124,9 +122,15 @@ export default defineCommand({
flag: "--off",
description: "Disable auto-stop",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -141,7 +145,6 @@ export default defineCommand({
const modelFlag = (flags.model as string) || undefined;
const all = Boolean(flags.all);
const off = Boolean(flags.off);
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
if (!modelFlag && !all) {
@@ -193,12 +196,10 @@ export default defineCommand({
callConsoleGateway(config, credential.token, {
api: FREE_TIER_API,
data: { queryFreeTierQuotaRequest: { models } },
region,
}),
callConsoleGateway(config, credential.token, {
api: FREE_TIER_ONLY_STATUS_API,
data: { queryFreeTierOnlyStatusRequest: { models } },
region,
}),
]);
@@ -222,7 +223,7 @@ export default defineCommand({
);
continue;
}
await pollUntilDone(config, credential.token, api, requestKey, [name], region);
await pollUntilDone(config, credential.token, api, requestKey, [name]);
process.stdout.write(`Disabled auto-stop for "${name}".\n`);
}
return;
@@ -230,7 +231,7 @@ export default defineCommand({
const jsonResults: unknown[] = [];
for (const name of models) {
const result = await pollUntilDone(config, credential.token, api, requestKey, [name], region);
const result = await pollUntilDone(config, credential.token, api, requestKey, [name]);
if (format === "json") {
jsonResults.push(result);
continue;
+11 -8
View File
@@ -70,7 +70,6 @@ async function pollTelemetryApi(
token: string,
api: string,
reqDTO: Record<string, unknown>,
region: string,
): Promise<unknown> {
let nextTaskId: string | undefined;
@@ -82,7 +81,6 @@ async function pollTelemetryApi(
const raw = await callConsoleGateway(config, token, {
api,
data: requestData,
region,
});
const resp = extractResponseData(raw as Record<string, unknown>);
@@ -306,9 +304,15 @@ export default defineCommand({
flag: "--workspace-id <id>",
description: "Workspace ID (env: BAILIAN_WORKSPACE_ID)",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: [
@@ -324,7 +328,6 @@ export default defineCommand({
const modelFlag = (flags.model as string) || undefined;
const daysFlag = Number(flags.days) || 7;
const typeFlag = (flags.type as string) || undefined;
const region = (flags.region as string) || "cn-beijing";
const format = detectOutputFormat(config.output);
const flagWorkspaceId = (flags.workspaceId as string) || undefined;
@@ -357,7 +360,7 @@ export default defineCommand({
if (config.dryRun) {
emitResult(
{ api: LIST_API, data: { reqDTO: { ...baseReqDTO, model: models.join(",") } }, region },
{ api: LIST_API, data: { reqDTO: { ...baseReqDTO, model: models.join(",") } } },
format,
);
return;
@@ -367,7 +370,7 @@ export default defineCommand({
const results = await Promise.all(
models.map((model) =>
pollTelemetryApi(config, credential.token, LIST_API, { ...baseReqDTO, model }, region),
pollTelemetryApi(config, credential.token, LIST_API, { ...baseReqDTO, model }),
),
);
@@ -411,7 +414,7 @@ export default defineCommand({
if (typeFlag) reqDTO.obsModelType = typeFlag;
if (config.dryRun) {
emitResult({ api: OVERVIEW_API, data: { reqDTO }, region }, format);
emitResult({ api: OVERVIEW_API, data: { reqDTO } }, format);
return;
}
+9 -5
View File
@@ -85,28 +85,32 @@ export default defineCommand({
flag: "--list <n>",
description: "Limit number of results",
},
{ flag: "--console-region <region>", description: "Console region" },
{
flag: "--region <region>",
description: "API region (default: cn-beijing)",
flag: "--console-site <site>",
description: "Console site: domestic, international",
},
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID",
type: "number",
},
],
examples: ["bl workspace list", "bl workspace list --list 5", "bl workspace list --output json"],
async run(config: Config, flags: GlobalFlags) {
const region = (flags.region as string) || "cn-beijing";
const limit = Number(flags.list) || 0;
const format = detectOutputFormat(config.output);
const credential = await resolveConsoleGatewayCredential(config);
if (config.dryRun) {
emitResult({ api: LIST_WORKSPACES_API, data: {}, region }, format);
emitResult({ api: LIST_WORKSPACES_API, data: {} }, format);
return;
}
const result = await callConsoleGateway(config, credential.token, {
api: LIST_WORKSPACES_API,
data: {},
region,
});
const resp = extractResponseData(result as Record<string, unknown>);
+9 -6
View File
@@ -9,6 +9,10 @@ import { API_KEY_PAGE } from "./urls.ts";
const LABEL_WIDTH = 13;
/** Short reminder; full resolution order matches `loadConfig` in bailian-cli-core. */
const BASE_URL_HINT =
"If the DashScope host is wrong, check baseUrl (--base-url, bl config show, or DASHSCOPE_BASE_URL).";
function pad(label: string): string {
return label.padEnd(LABEL_WIDTH);
}
@@ -79,7 +83,7 @@ function pickNetworkHint(code: string | undefined): string {
switch (code) {
case "ENOTFOUND":
case "EAI_AGAIN":
return "DNS resolution failed. Check DASHSCOPE_BASE_URL or your DNS / network.";
return `DNS resolution failed. Check DNS / network. ${BASE_URL_HINT}`;
case "ECONNREFUSED":
return "Connection refused. Check the target host/port and proxy settings.";
case "ECONNRESET":
@@ -89,13 +93,13 @@ function pickNetworkHint(code: string | undefined): string {
"export HTTPS_PROXY=http://127.0.0.1:<proxy-port>"
);
case "ETIMEDOUT":
return "Connection timed out. Check your network or try a different region.";
return `Connection timed out. Check your network. ${BASE_URL_HINT}`;
case "CERT_HAS_EXPIRED":
case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
case "DEPTH_ZERO_SELF_SIGNED_CERT":
return "TLS certificate error. Check system clock and CA bundle.";
default:
return "Check network connection, proxy settings (HTTP_PROXY / HTTPS_PROXY), and DASHSCOPE_BASE_URL.";
return `Check network and proxy (HTTP_PROXY / HTTPS_PROXY). ${BASE_URL_HINT}`;
}
}
@@ -169,9 +173,8 @@ export function handleError(err: unknown): never {
"Request timed out.",
ExitCode.TIMEOUT,
"Try increasing --timeout (e.g. --timeout 60).\n" +
"If this happens on every request with a valid API key, you may be hitting the wrong region.\n" +
"Run: bl auth status — to check your credentials and region.\n" +
"Run: bl config set --key region --value cn — to override the region.",
`${BASE_URL_HINT}\n` +
"Run: bl auth status — to check credentials.",
{ cause: err },
);
return handleError(timeout);
-4
View File
@@ -5,7 +5,6 @@ const reset = "\x1b[0m";
const dim = "\x1b[2m";
const bold = "\x1b[1m";
const mmBlue = "\x1b[38;2;43;82;255m";
const mmCyan = "\x1b[38;2;6;184;212m";
const mmPink = "\x1b[38;2;236;72;153m";
function tildePath(p: string): string {
@@ -20,7 +19,6 @@ export function maybeShowStatusBar(
if (config.quiet || !process.stderr.isTTY) return;
const filePath = config.configPath ? tildePath(config.configPath) : "~/.bailian/config.json";
const regionSrc = config.fileRegion ? `${config.fileRegion} (file)` : "cn (default)";
const authTag = resolved
? `${resolved.source} · ${resolved.method}`
: config.apiKey
@@ -32,8 +30,6 @@ export function maybeShowStatusBar(
`${bold}${mmBlue}BAILIAN${reset} ` +
`${dim}${filePath}${reset} ` +
`${dim}|${reset} ` +
`${dim}Region:${reset} ${mmCyan}${regionSrc}${reset} ` +
`${dim}|${reset} ` +
`${dim}Auth:${reset} ${mmPink}${maskedKey}${reset} ${dim}${authTag}${reset}\n`,
);
}
+22 -16
View File
@@ -17,6 +17,7 @@ describe("e2e: auth", () => {
const { stderr, exitCode } = await runCli(["auth", "login", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/login|api-key/i);
expect(stderr).toMatch(/--console-site/);
});
test("auth logout --help 正常退出", async () => {
@@ -159,20 +160,25 @@ describe("e2e: auth", () => {
expect(data.dashscope_commands?.method).toBeDefined();
});
test.skipIf(!isDashScopeE2EReady())("auth status --output json --quiet --region cn", async () => {
const { stdout, stderr, exitCode } = await runCli([
"auth",
"status",
"--non-interactive",
"--output",
"json",
"--quiet",
"--region",
"cn",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ authenticated?: boolean; dashscope_commands?: unknown }>(stdout);
expect(data.authenticated).toBe(true);
expect(data.dashscope_commands).toBeDefined();
});
test.skipIf(!isDashScopeE2EReady())(
"auth status --output json --quiet --base-url 国内",
async () => {
const { stdout, stderr, exitCode } = await runCli([
"auth",
"status",
"--non-interactive",
"--output",
"json",
"--quiet",
"--base-url",
"https://dashscope.aliyuncs.com",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ authenticated?: boolean; dashscope_commands?: unknown }>(
stdout,
);
expect(data.authenticated).toBe(true);
expect(data.dashscope_commands).toBeDefined();
},
);
});
+1 -17
View File
@@ -41,12 +41,10 @@ describe("e2e: config", () => {
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
region?: string;
config_file?: string;
base_url?: string;
timeout?: number;
}>(stdout);
expect(data.region).toBeDefined();
expect(data.config_file).toBeDefined();
expect(data.base_url).toBeDefined();
expect(data.timeout).toBeDefined();
@@ -62,7 +60,7 @@ describe("e2e: config", () => {
"--no-color",
]);
expect(exitCode, stderr).toBe(0);
expect(stdout).toMatch(/region|config_file|timeout|base_url/i);
expect(stdout).toMatch(/config_file|timeout|base_url/i);
});
test("config set 缺少 --key / --value 时退出为用法错误 (2)", async () => {
@@ -85,20 +83,6 @@ describe("e2e: config", () => {
expect(stderr).toMatch(/Invalid config key|not-a-real-key/i);
});
test("config set 非法 region", async () => {
const { stderr, exitCode } = await runCli([
"config",
"set",
"--non-interactive",
"--key",
"region",
"--value",
"invalid-region",
]);
expect(exitCode).toBe(2);
expect(stderr).toMatch(/Invalid region|cn, us, intl/i);
});
test("config set 非法 output", async () => {
const { stderr, exitCode } = await runCli([
"config",
@@ -0,0 +1,148 @@
import { describe, expect, test } from "vite-plus/test";
import { parseStdoutJson, runCli } from "./helpers.ts";
type ConsoleDryRunMeta = {
consoleRegion?: string;
consoleSite?: string;
consoleSwitchAgent?: number;
};
/**
* E2E for global console flags (`--console-region`, `--console-site`,
* `--console-switch-agent`) and DashScope `--base-url`.
*/
describe("e2e: console global flags", () => {
test("根帮助展示 --base-url 与 console 全局标志", async () => {
const { stderr, exitCode } = await runCli(["--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--base-url/);
expect(stderr).toMatch(/--console-region/);
expect(stderr).toMatch(/--console-site/);
expect(stderr).toMatch(/--console-switch-agent/);
expect(stderr).not.toMatch(/^\s*--region\s/m);
});
test("quota check --help 不重复命令级 region,并提示全局 flags", async () => {
const { stderr, exitCode } = await runCli(["quota", "check", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/Global flags.*always available/i);
expect(stderr).toMatch(/--model <model>/);
expect(stderr).toMatch(/--period <minutes>/);
expect(stderr).not.toMatch(/API region \(default: cn-beijing\)/);
});
test("console call --help 不暴露命令级 region/site,示例使用 --console-region", async () => {
const { stderr, exitCode } = await runCli(["console", "call", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--api <api>/);
expect(stderr).toMatch(/--data <json>/);
expect(stderr).not.toMatch(/^\s*--region\s/m);
expect(stderr).not.toMatch(/^\s*--site\s/m);
expect(stderr).toMatch(/--console-region cn-beijing/);
});
test("auth login --help 描述 --console 与 --console-site 配合", async () => {
const { stderr, exitCode } = await runCli(["auth", "login", "--help"]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--console-site/);
expect(stderr).toMatch(/--console.*console-site|console-site.*domestic|international/i);
});
test("console call --dry-run 默认 consoleRegion 为 cn-beijing", async () => {
const { stdout, stderr, exitCode } = await runCli([
"console",
"call",
"--api",
"some.api.name",
"--data",
"{}",
"--dry-run",
"--non-interactive",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<ConsoleDryRunMeta>(stdout);
expect(data.consoleRegion).toBe("cn-beijing");
expect(data.consoleSite).toBe("domestic");
});
test("console call --dry-run --console-region / --console-site / --console-switch-agent 透传", async () => {
const { stdout, stderr, exitCode } = await runCli([
"console",
"call",
"--api",
"some.api.name",
"--data",
"{}",
"--dry-run",
"--non-interactive",
"--output",
"json",
"--console-region",
"ap-southeast-1",
"--console-site",
"international",
"--console-switch-agent",
"12345",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<ConsoleDryRunMeta>(stdout);
expect(data.consoleRegion).toBe("ap-southeast-1");
expect(data.consoleSite).toBe("international");
expect(data.consoleSwitchAgent).toBe(12345);
});
test("console call 拒绝未知全局 flag --region", async () => {
const { stderr, exitCode } = await runCli([
"console",
"call",
"--api",
"some.api.name",
"--data",
"{}",
"--dry-run",
"--non-interactive",
"--region",
"cn",
]);
expect(exitCode).not.toBe(0);
expect(stderr).toMatch(/Unknown flag.*--region/);
});
test("mcp list --dry-run --console-region 透传", async () => {
const { stdout, stderr, exitCode } = await runCli([
"mcp",
"list",
"--dry-run",
"--non-interactive",
"--output",
"json",
"--console-region",
"cn-hangzhou",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<ConsoleDryRunMeta>(stdout);
expect(data.consoleRegion).toBe("cn-hangzhou");
});
test("quota check --dry-run --console-region 透传", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--dry-run",
"--non-interactive",
"--output",
"json",
"--console-region",
"cn-hangzhou",
"--console-site",
"international",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<ConsoleDryRunMeta>(stdout);
expect(data.consoleRegion).toBe("cn-hangzhou");
expect(data.consoleSite).toBe("international");
});
});
+5 -6
View File
@@ -66,7 +66,7 @@ describe("e2e: mcp", () => {
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
api?: string;
region?: string;
consoleRegion?: string;
data?: {
reqDTO?: {
type?: string;
@@ -79,7 +79,6 @@ describe("e2e: mcp", () => {
};
}>(stdout);
expect(data.api).toBe("zeldaEasy.broadscope-bailian.mcp-server.PageList");
expect(data.region).toBe("cn-beijing");
expect(data.data?.reqDTO?.activated).toBe(1);
expect(data.data?.reqDTO?.displayTools).toBe(false);
expect(data.data?.reqDTO?.type).toBe("OFFICIAL");
@@ -88,7 +87,7 @@ describe("e2e: mcp", () => {
expect(data.data?.reqDTO?.pageSize).toBe(5);
});
test("mcp list --dry-run 自定义 --region 透传", async () => {
test("mcp list --dry-run 自定义 --console-region 透传", async () => {
const { stdout, stderr, exitCode } = await runCli([
"mcp",
"list",
@@ -96,12 +95,12 @@ describe("e2e: mcp", () => {
"--non-interactive",
"--output",
"json",
"--region",
"--console-region",
"cn-hangzhou",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ region?: string }>(stdout);
expect(data.region).toBe("cn-hangzhou");
const data = parseStdoutJson<{ consoleRegion?: string }>(stdout);
expect(data.consoleRegion).toBe("cn-hangzhou");
});
test("mcp tools <server-code> --dry-run 输出 /api/v1/mcps/<code>/mcp 形态 URL", async () => {
+18 -1
View File
@@ -231,11 +231,28 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => {
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ apis?: string[] }>(stdout);
const data = parseStdoutJson<{ apis?: string[]; consoleRegion?: string }>(stdout);
expect(data.apis).toContain(
"zeldaHttp.dashscopeModel./zelda/api/v1/modelCenter/listFoundationModels",
);
expect(data.apis).toContain("zeldaEasy.bailian-telemetry.monitor.getMonitorData");
expect(data.consoleRegion).toBe("cn-beijing");
});
test("quota check --dry-run --console-region 透传", async () => {
const { stdout, stderr, exitCode } = await runCli([
"quota",
"check",
"--dry-run",
"--non-interactive",
"--output",
"json",
"--console-region",
"cn-hangzhou",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ consoleRegion?: string }>(stdout);
expect(data.consoleRegion).toBe("cn-hangzhou");
});
test("quota check 文本输出包含英文表头", async () => {
@@ -273,13 +273,13 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => {
expect(hasAutoStop).toBe(true);
});
test("usage free --model --region cn-beijing 指定区域查询", async () => {
test("usage free --model --console-region cn-beijing 指定区域查询", async () => {
const { stdout, stderr, exitCode } = await runCli([
"usage",
"free",
"--model",
"qwen3-max",
"--region",
"--console-region",
"cn-beijing",
"--output",
"json",
+6 -17
View File
@@ -1,5 +1,5 @@
import { readFileSync, writeFileSync, renameSync, existsSync } from "fs";
import { parseConfigFile, REGIONS, type Config, type ConfigFile, type Region } from "./schema.ts";
import { parseConfigFile, REGIONS, type Config, type ConfigFile } from "./schema.ts";
import { ensureConfigDir, getConfigPath } from "./paths.ts";
import { detectOutputFormat, type OutputFormat } from "../output/formatter.ts";
import { BailianError } from "../errors/base.ts";
@@ -36,16 +36,7 @@ export function loadConfig(flags: GlobalFlags): Config {
const accessTokenEnv = process.env.DASHSCOPE_ACCESS_TOKEN?.trim() || undefined;
const fileAccessToken = file.access_token?.trim() || undefined;
const explicitRegion = (flags.region as string) || process.env.DASHSCOPE_REGION || undefined;
const cachedRegion = file.region;
const region = (explicitRegion || cachedRegion || "cn") as Region;
const baseUrl =
flags.baseUrl ||
process.env.DASHSCOPE_BASE_URL ||
file.base_url ||
REGIONS[region] ||
REGIONS.cn;
const baseUrl = flags.baseUrl || file.base_url || process.env.DASHSCOPE_BASE_URL || REGIONS.cn;
const output: OutputFormat = detectOutputFormat(
flags.output || process.env.DASHSCOPE_OUTPUT || file.output,
@@ -68,9 +59,7 @@ export function loadConfig(flags: GlobalFlags): Config {
accessTokenEnv,
fileAccessToken,
fileApiKey,
fileRegion: file.region,
configPath: getConfigPath(),
region,
baseUrl,
output,
outputDir: file.output_dir || undefined,
@@ -84,10 +73,10 @@ export function loadConfig(flags: GlobalFlags): Config {
accessKeySecret:
process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET || file.access_key_secret || undefined,
workspaceId: process.env.BAILIAN_WORKSPACE_ID || file.workspace_id || undefined,
consoleGatewayUrl:
process.env.BAILIAN_CONSOLE_GATEWAY_URL ||
file.console_gateway_url ||
"https://bailian-cs.console.aliyun.com",
consoleSite: (flags.consoleSite as Config["consoleSite"]) || file.console_site || undefined,
consoleRegion: (flags.consoleRegion as string) || file.console_region || undefined,
consoleSwitchAgent:
(flags.consoleSwitchAgent as number) || file.console_switch_agent || undefined,
verbose: flags.verbose || process.env.DASHSCOPE_VERBOSE === "1",
quiet: flags.quiet || false,
noColor: flags.noColor || process.env.NO_COLOR !== undefined || !process.stdout.isTTY,
+15 -12
View File
@@ -18,7 +18,6 @@ export interface ConfigFile {
api_key?: string;
/** OAuth-style token from `bl auth login --console` callback; sent as `Authorization: Bearer …` */
access_token?: string;
region?: Region;
base_url?: string;
output?: "text" | "json";
output_dir?: string;
@@ -31,16 +30,18 @@ export interface ConfigFile {
access_key_id?: string;
access_key_secret?: string;
workspace_id?: string;
console_gateway_url?: string;
console_site?: "domestic" | "international";
console_region?: string;
console_switch_agent?: number;
telemetry?: boolean;
}
const VALID_REGIONS = new Set<string>(["cn", "us", "intl"]);
const VALID_OUTPUTS = new Set<string>(["text", "json"]);
const VALID_CONSOLE_SITES = new Set<string>(["domestic", "international"]);
/**
* A syntactically valid absolute http(s) URL. Used to validate `base_url` and
* `console_gateway_url` from the config file: the credential-bearing client
* A syntactically valid absolute http(s) URL. Used to validate `base_url`
* from the config file: the credential-bearing client
* sends the Bearer token to these origins, so a bare `startsWith("http")` check
* (which also accepts e.g. "httpfoo://…") is too loose.
*/
@@ -63,8 +64,6 @@ export function parseConfigFile(raw: unknown): ConfigFile {
out.access_token = obj.access_token;
else if (typeof obj.accessToken === "string" && obj.accessToken.length > 0)
out.access_token = obj.accessToken;
if (typeof obj.region === "string" && VALID_REGIONS.has(obj.region))
out.region = obj.region as Region;
if (typeof obj.base_url === "string" && isHttpUrl(obj.base_url)) out.base_url = obj.base_url;
if (typeof obj.output === "string" && VALID_OUTPUTS.has(obj.output))
out.output = obj.output as ConfigFile["output"];
@@ -87,8 +86,12 @@ export function parseConfigFile(raw: unknown): ConfigFile {
out.access_key_secret = obj.access_key_secret;
if (typeof obj.workspace_id === "string" && obj.workspace_id.length > 0)
out.workspace_id = obj.workspace_id;
if (typeof obj.console_gateway_url === "string" && isHttpUrl(obj.console_gateway_url))
out.console_gateway_url = obj.console_gateway_url;
if (typeof obj.console_site === "string" && VALID_CONSOLE_SITES.has(obj.console_site))
out.console_site = obj.console_site as ConfigFile["console_site"];
if (typeof obj.console_region === "string" && obj.console_region.length > 0)
out.console_region = obj.console_region;
if (typeof obj.console_switch_agent === "number" && obj.console_switch_agent > 0)
out.console_switch_agent = obj.console_switch_agent;
if (typeof obj.telemetry === "boolean") out.telemetry = obj.telemetry;
return out;
@@ -103,9 +106,7 @@ export interface Config {
/** `access_token` in config file (console login). */
fileAccessToken?: string;
fileApiKey?: string;
fileRegion?: Region;
configPath?: string;
region: Region;
baseUrl: string;
output: "text" | "json";
outputDir?: string;
@@ -118,7 +119,9 @@ export interface Config {
accessKeyId?: string;
accessKeySecret?: string;
workspaceId?: string;
consoleGatewayUrl: string;
consoleSite?: "domestic" | "international";
consoleRegion?: string;
consoleSwitchAgent?: number;
verbose: boolean;
quiet: boolean;
noColor: boolean;
+73 -9
View File
@@ -2,18 +2,70 @@ import type { Config } from "../config/schema.ts";
import { BailianError } from "../errors/base.ts";
import { ExitCode } from "../errors/codes.ts";
const GATEWAY_ACTION = "BroadScopeAspnGateway";
const GATEWAY_PRODUCT = "sfm_bailian";
export type ConsoleSite = "domestic" | "international";
interface ConsoleGatewayInfo {
csGateway: string;
action: string;
}
const REGION_GATEWAYS: Record<string, Record<ConsoleSite, ConsoleGatewayInfo>> = {
"cn-beijing": {
domestic: { csGateway: "bailian-cs.console.aliyun.com", action: "BroadScopeAspnGateway" },
international: {
csGateway: "bailian-cs.console.alibabacloud.com",
action: "BroadScopeAspnGateway",
},
},
"ap-southeast-1": {
domestic: {
csGateway: "modelstudio-cs.console.aliyun.com",
action: "IntlBroadScopeAspnGateway",
},
international: {
csGateway: "bailian-singapore-cs.alibabacloud.com",
action: "IntlBroadScopeAspnGateway",
},
},
};
function resolveGateway(region: string, site: ConsoleSite): ConsoleGatewayInfo {
return REGION_GATEWAYS[region]?.[site] ?? REGION_GATEWAYS["cn-beijing"]![site];
}
/** Resolved console gateway settings (same defaults as {@link callConsoleGateway}). */
export function effectiveConsoleGatewayConfig(config: Config): {
consoleRegion: string;
consoleSite: ConsoleSite;
consoleSwitchAgent?: number;
} {
const consoleRegion = config.consoleRegion ?? "cn-beijing";
const consoleSite = config.consoleSite ?? "domestic";
const consoleSwitchAgent = config.consoleSwitchAgent;
return consoleSwitchAgent != null
? { consoleRegion, consoleSite, consoleSwitchAgent }
: { consoleRegion, consoleSite };
}
export interface ConsoleGatewayRequest {
/** Console API name, e.g. zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota */
api: string;
data: Record<string, unknown>;
/** Console region (default: cn-beijing), distinct from DashScope `config.region`. */
/** Console region (e.g. cn-beijing, ap-southeast-1). Falls back to config.consoleRegion, then "cn-beijing". */
region?: string;
/** Console site. Falls back to config.consoleSite, then "domestic". */
site?: ConsoleSite;
/** Switch-agent UID for delegated access. Falls back to config.consoleSwitchAgent. */
switchAgent?: number;
}
function buildGatewayParams(api: string, data: Record<string, unknown>): string {
function buildGatewayParams(
api: string,
data: Record<string, unknown>,
switchAgent?: number,
): string {
return JSON.stringify({
Api: api,
V: "1.0",
@@ -24,6 +76,7 @@ function buildGatewayParams(api: string, data: Record<string, unknown>): string
console: "ONE_CONSOLE",
productCode: "p_efm",
consoleSite: "BAILIAN_ALIYUN",
...(switchAgent != null ? { switchAgent } : {}),
...(typeof data.cornerstoneParam === "object" && data.cornerstoneParam !== null
? (data.cornerstoneParam as Record<string, unknown>)
: {}),
@@ -37,17 +90,28 @@ function buildGatewayParams(api: string, data: Record<string, unknown>): string
* `token` is the console `access_token` (from `bl auth login --console`); when
* omitted the request is sent without an Authorization header, which works for
* public console APIs that don't require a login session.
*
* Gateway URL and action are resolved from `region + site` via {@link REGION_GATEWAYS}.
* Each parameter falls back to the corresponding config value, then to a hardcoded default.
*/
export async function callConsoleGateway(
config: Config,
token: string | undefined,
{ api, data, region = "cn-beijing" }: ConsoleGatewayRequest,
{ api, data }: ConsoleGatewayRequest,
): Promise<unknown> {
const params = buildGatewayParams(api, data);
const body = new URLSearchParams({ params, region });
const timeoutMs = config.timeout * 1000;
const {
consoleRegion: effectiveRegion,
consoleSite: effectiveSite,
consoleSwitchAgent: effectiveSwitchAgent,
} = effectiveConsoleGatewayConfig(config);
const gatewayBase = config.consoleGatewayUrl;
const resolved = resolveGateway(effectiveRegion, effectiveSite);
const gatewayBase = `https://${resolved.csGateway}`;
const action = resolved.action;
const params = buildGatewayParams(api, data, effectiveSwitchAgent);
const body = new URLSearchParams({ params, region: effectiveRegion });
const timeoutMs = config.timeout * 1000;
const headers: Record<string, string> = {
Accept: "*/*",
@@ -56,7 +120,7 @@ export async function callConsoleGateway(
if (token) headers.Authorization = `Bearer ${token}`;
const res = await fetch(
`${gatewayBase}/cli/api.json?action=${GATEWAY_ACTION}&product=${GATEWAY_PRODUCT}&api=${encodeURIComponent(api)}`,
`${gatewayBase}/cli/api.json?action=${action}&product=${GATEWAY_PRODUCT}&api=${encodeURIComponent(api)}`,
{
method: "POST",
headers,
+2 -2
View File
@@ -1,4 +1,4 @@
export type { ConsoleGatewayRequest } from "./gateway.ts";
export { callConsoleGateway } from "./gateway.ts";
export type { ConsoleGatewayRequest, ConsoleSite } from "./gateway.ts";
export { callConsoleGateway, effectiveConsoleGatewayConfig } from "./gateway.ts";
export type { ModelListParams, ModelListResult } from "./models.ts";
export { fetchModelList } from "./models.ts";
+1 -10
View File
@@ -9,7 +9,6 @@ export interface ModelListParams {
name?: string;
providers?: string[];
capabilities?: string[];
region?: string;
}
export interface ModelListResult {
@@ -22,14 +21,7 @@ export async function fetchModelList(
token: string,
params: ModelListParams = {},
): Promise<ModelListResult> {
const {
pageNo = 1,
pageSize = 50,
name = "",
providers = [],
capabilities = [],
region = "cn-beijing",
} = params;
const { pageNo = 1, pageSize = 50, name = "", providers = [], capabilities = [] } = params;
const result = (await callConsoleGateway(config, token, {
api: MODEL_LIST_API,
@@ -46,7 +38,6 @@ export async function fetchModelList(
contextWindows: [],
},
},
region,
})) as any;
const responseData = result?.data?.DataV2?.data ?? result?.data ?? {};
-3
View File
@@ -7,7 +7,6 @@ export interface TrackingEvent {
httpStatus?: number;
requestId?: string;
cliVersion: string;
region: string;
nodeVersion: string;
os: string;
authMethod?: string;
@@ -20,7 +19,6 @@ export function createTrackingEvent(opts: {
success: boolean;
error?: { message?: string; httpStatus?: number; requestId?: string };
cliVersion: string;
region: string;
authMethod?: string;
params?: Record<string, unknown>;
}): TrackingEvent {
@@ -30,7 +28,6 @@ export function createTrackingEvent(opts: {
durationMs: opts.durationMs,
success: opts.success,
cliVersion: opts.cliVersion,
region: opts.region,
nodeVersion: process.version,
os: process.platform,
};
-2
View File
@@ -17,7 +17,6 @@ const GLOBAL_FLAG_KEYS = new Set([
"help",
"nonInteractive",
"async",
"region",
"console",
]);
@@ -131,7 +130,6 @@ export async function trackCommandExecution(
success,
error: success ? undefined : { message: errorMessage, httpStatus, requestId },
cliVersion: config.clientVersion ?? "unknown",
region: config.region,
authMethod,
params: extractParams(flags),
});
+10 -1
View File
@@ -46,7 +46,6 @@ export function defineCommand(spec: CommandSpec): Command {
/** Global flags shared by all commands — drives the parser's type resolution. */
export const GLOBAL_OPTIONS: OptionDef[] = [
{ flag: "--api-key <key>", description: "API key" },
{ flag: "--region <region>", description: "API region: cn (default), us, intl" },
{ flag: "--base-url <url>", description: "API base URL" },
{ flag: "--output <format>", description: "Output format: text, json" },
{ flag: "--timeout <seconds>", description: "Request timeout", type: "number" },
@@ -56,6 +55,16 @@ export const GLOBAL_OPTIONS: OptionDef[] = [
{ flag: "--dry-run", description: "Dry run mode" },
{ flag: "--non-interactive", description: "Disable interactive prompts" },
{ flag: "--concurrent <n>", description: "Run N parallel requests (default: 1)", type: "number" },
{
flag: "--console-region <region>",
description: "Console gateway region (e.g. cn-beijing, ap-southeast-1)",
},
{ flag: "--console-site <site>", description: "Console site: domestic, international" },
{
flag: "--console-switch-agent <uid>",
description: "Switch agent UID for delegated access",
type: "number",
},
{ flag: "--help", description: "Show help" },
{ flag: "--version", description: "Print version" },
];
+3
View File
@@ -11,5 +11,8 @@ export interface GlobalFlags {
help: boolean;
nonInteractive: boolean;
async: boolean;
consoleRegion?: string;
consoleSite?: string;
consoleSwitchAgent?: number;
[key: string]: unknown;
}
+6 -4
View File
@@ -10,7 +10,6 @@ import {
function testConfig(overrides: Partial<Config> = {}): Config {
return {
region: "cn",
baseUrl: "https://dashscope.aliyuncs.com",
output: "json",
timeout: 30,
@@ -22,7 +21,6 @@ function testConfig(overrides: Partial<Config> = {}): Config {
nonInteractive: true,
async: false,
telemetry: true,
consoleGatewayUrl: "https://bailian-cs.console.aliyun.com",
...overrides,
};
}
@@ -200,7 +198,12 @@ test("parseBooleanValue accepts only true and false strings (case-insensitive)",
expect(() => parseBooleanValue("maybe")).toThrow(BailianError);
});
test("parseConfigFile accepts only well-formed http(s) base_url / console_gateway_url", () => {
test("parseConfigFile ignores obsolete region field", () => {
const f = parseConfigFile({ region: "intl" });
expect("region" in f).toBe(false);
});
test("parseConfigFile accepts only well-formed http(s) base_url", () => {
expect(parseConfigFile({ base_url: "https://dashscope.aliyuncs.com" }).base_url).toBe(
"https://dashscope.aliyuncs.com",
);
@@ -210,5 +213,4 @@ test("parseConfigFile accepts only well-formed http(s) base_url / console_gatewa
// Previously accepted because the value merely "starts with http".
expect(parseConfigFile({ base_url: "httpfoo://evil" }).base_url).toBeUndefined();
expect(parseConfigFile({ base_url: "not a url" }).base_url).toBeUndefined();
expect(parseConfigFile({ console_gateway_url: "ftp://x" }).console_gateway_url).toBeUndefined();
});
+9 -9
View File
@@ -116,7 +116,7 @@ bl auth logout --console # clear console token only
Get an API key: https://bailian.console.aliyun.com/cn-beijing/?tab=app#/api-key
**Region:** `cn` (default), `us`, `intl` — `--region` or `DASHSCOPE_REGION` or `bl config set --key region --value us`.
**DashScope endpoint:** default `https://dashscope.aliyuncs.com` (China). Override with `--base-url`, `bl config set --key base_url --value https://dashscope-us.aliyuncs.com` (US), or `DASHSCOPE_BASE_URL` / `https://dashscope-intl.aliyuncs.com` (international).
---
@@ -126,13 +126,13 @@ See [`reference/index.md` → Global flags](reference/index.md#global-flags) for
Commonly used:
| Flag | Purpose |
| ------------------------------------- | --------------------------------------------------------- |
| `--output text\|json` | Structured output (default: text in TTY, json when piped) |
| `--api-key`, `--region`, `--base-url` | Override auth / endpoint |
| `--quiet`, `--verbose`, `--dry-run` | Output control |
| `--non-interactive` | CI / agent mode (no prompts) |
| `--help` | Per-command help |
| Flag | Purpose |
| ----------------------------------- | --------------------------------------------------------- |
| `--output text\|json` | Structured output (default: text in TTY, json when piped) |
| `--api-key`, `--base-url` | Override auth / endpoint |
| `--quiet`, `--verbose`, `--dry-run` | Output control |
| `--non-interactive` | CI / agent mode (no prompts) |
| `--help` | Per-command help |
---
@@ -175,7 +175,7 @@ ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
## Configuration
- **Config file:** `~/.bailian/config.json`
- **Env:** `DASHSCOPE_API_KEY`, `DASHSCOPE_REGION`, `DASHSCOPE_BASE_URL`, `DASHSCOPE_OUTPUT`
- **Env:** `DASHSCOPE_API_KEY`, `DASHSCOPE_BASE_URL`, `DASHSCOPE_OUTPUT`
```bash
bl config show
+3 -3
View File
@@ -20,7 +20,7 @@ function shouldOfferIssueReport(exitCode, apiCode, message, hint):
# Step 2: NETWORK / TIMEOUT — exclude if hint is actionable
if exitCode in [5 (TIMEOUT), 6 (NETWORK)] AND hint is actionable:
return EXCLUDE # user can self-service (DNS, proxy, --timeout, region)
return EXCLUDE # user can self-service (DNS, proxy, --timeout, base_url)
# Step 3: GENERAL (exit code 1) — shared by CLI bugs AND service passthrough
# MUST inspect api_code / message to disambiguate
@@ -80,7 +80,7 @@ These are **user**, **environment**, or **service business** errors. Give fix hi
| **Free quota query** | `bl usage free` business result | Quota used up — not a CLI defect |
| **Obvious local env** | Hint is sufficient | `ENOENT` / `EACCES`, wrong file path, disk full |
| **Network (self-service)** | Exit code **6** (NETWORK) + clear hint | DNS, proxy, TLS — user fixes `DASHSCOPE_BASE_URL`, proxy, or network |
| **Timeout (self-service)** | Exit code **5** (TIMEOUT) + hint works | Increase `--timeout`, check region with `bl auth status` |
| **Timeout (self-service)** | Exit code **5** (TIMEOUT) + hint works | Increase `--timeout`, check `base_url` with `bl auth status` |
**Rule:** If the authoritative source of the error is the **service response** or **user input**, treat it as non-reportable (same boundary as the CLI repo’s error-handling docs).
@@ -172,7 +172,7 @@ Before any paste or `gh issue create`:
- Replace `Authorization: Bearer ...` headers in verbose output → `Authorization: Bearer [REDACTED]`
- Redact `--prompt` / `--message` / `--biz-params` contents if they contain user business data → summarize as `[user prompt about <topic>]`
- Redact `account`, `uid`, `aliuid` from `bl auth status` output → `[REDACTED]`
- Redact sensitive fields from `bl config show` (keep non-secret keys like `region`, model defaults)
- Redact sensitive fields from `bl config show` (keep non-secret keys like `base_url`, model defaults)
- **Keep** `Request ID` / `request_id` — helps the team trace logs
- Local paths may stay or be generalized (`~/path/to/file.png`)
+8 -6
View File
@@ -73,12 +73,14 @@ bl app call --app-id abc123 --prompt "Start" --biz-params '{"key":"value"}'
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------ | -------- | ----------------------------------- |
| `--name <name>` | string | no | Filter by app name (keyword search) |
| `--page <n>` | number | no | Page number (default: 1) |
| `--page-size <n>` | number | no | Results per page (default: 30) |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------- |
| `--name <name>` | string | no | Filter by app name (keyword search) |
| `--page <n>` | number | no | Page number (default: 1) |
| `--page-size <n>` | number | no | Results per page (default: 30) |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
+10 -5
View File
@@ -25,10 +25,11 @@ Index: [index.md](index.md)
#### Options
| Flag | Type | Required | Description |
| ----------------- | ------- | -------- | ------------------------------------------------------------------------ |
| `--api-key <key>` | string | no | DashScope API key to store |
| `--console` | boolean | no | Sign in via browser; opens the console login URL in your default browser |
| Flag | Type | Required | Description |
| ------------------ | ------- | -------- | ------------------------------------------------------------------------------------- |
| `--api-key <key>` | string | no | DashScope API key to store |
| `--base-url <url>` | string | no | DashScope API base URL (used with --api-key for validation) |
| `--console` | boolean | no | Sign in via browser; use --console-site to choose domestic (default) or international |
#### Examples
@@ -83,7 +84,11 @@ bl auth logout --yes
#### Options
_No command-specific options._
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------- |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
+4 -4
View File
@@ -49,10 +49,10 @@ bl config export-schema --command "video generate"
#### Options
| Flag | Type | Required | Description |
| ----------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--key <key>` | string | no | Config key (region, base*url, output, output_dir, timeout, api_key, access_token, default*\*\_model, access_key_id, access_key_secret, workspace_id) |
| `--value <value>` | string | no | Value to set |
| Flag | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `--key <key>` | string | no | Config key (base*url, output, output_dir, timeout, api_key, access_token, default*\*\_model, access_key_id, access_key_secret, workspace_id) |
| `--value <value>` | string | no | Value to set |
#### Examples
+8 -6
View File
@@ -23,11 +23,13 @@ Index: [index.md](index.md)
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------ |
| `--api <api>` | string | yes | API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries) |
| `--data <json>` | string | yes | Request data as JSON string |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------ |
| `--api <api>` | string | yes | API name (e.g. zeldaEasy.broadscope-bailian.memory-library.getLibraries) |
| `--data <json>` | string | yes | Request data as JSON string |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
@@ -36,5 +38,5 @@ bl console call --api zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota
```
```bash
bl console call --api some.api.name --data '{"key":"value"}' --region cn-beijing
bl console call --api some.api.name --data '{"key":"value"}' --console-region cn-beijing
```
+17 -15
View File
@@ -87,21 +87,23 @@ Use this index for the full quick index and global flags.
Available on every command (in addition to command-specific options):
| Flag | Type | Required | Description |
| --------------------- | ------- | -------- | ------------------------------------ |
| `--api-key <key>` | string | no | API key |
| `--region <region>` | string | no | API region: cn (default), us, intl |
| `--base-url <url>` | string | no | API base URL |
| `--output <format>` | string | no | Output format: text, json |
| `--timeout <seconds>` | number | no | Request timeout |
| `--quiet` | boolean | no | Suppress non-essential output |
| `--verbose` | boolean | no | Print HTTP request/response details |
| `--no-color` | boolean | no | Disable ANSI colors |
| `--dry-run` | boolean | no | Dry run mode |
| `--non-interactive` | boolean | no | Disable interactive prompts |
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
| `--help` | boolean | no | Show help |
| `--version` | boolean | no | Print version |
| Flag | Type | Required | Description |
| ------------------------------ | ------- | -------- | -------------------------------------------------------- |
| `--api-key <key>` | string | no | API key |
| `--base-url <url>` | string | no | API base URL |
| `--output <format>` | string | no | Output format: text, json |
| `--timeout <seconds>` | number | no | Request timeout |
| `--quiet` | boolean | no | Suppress non-essential output |
| `--verbose` | boolean | no | Print HTTP request/response details |
| `--no-color` | boolean | no | Disable ANSI colors |
| `--dry-run` | boolean | no | Dry run mode |
| `--non-interactive` | boolean | no | Disable interactive prompts |
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
| `--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 |
| `--help` | boolean | no | Show help |
| `--version` | boolean | no | Print version |
## Notes
+9 -7
View File
@@ -57,13 +57,15 @@ bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg min
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------ | -------- | ---------------------------------------------------- |
| `--name <text>` | string | no | Filter by server name (substring match) |
| `--type <type>` | string | no | Server type: OFFICIAL \| PRIVATE (default: OFFICIAL) |
| `--page <n>` | number | no | Page number (default: 1) |
| `--page-size <n>` | number | no | Results per page (default: 30) |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ---------------------------------------------------- |
| `--name <text>` | string | no | Filter by server name (substring match) |
| `--type <type>` | string | no | Server type: OFFICIAL \| PRIVATE (default: OFFICIAL) |
| `--page <n>` | number | no | Page number (default: 1) |
| `--page-size <n>` | number | no | Results per page (default: 30) |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
+30 -22
View File
@@ -26,11 +26,13 @@ Index: [index.md](index.md)
#### Options
| Flag | Type | Required | Description |
| -------------------- | ------ | -------- | ----------------------------------------------- |
| `--model <model>` | string | no | Model name(s), comma-separated |
| `--period <minutes>` | string | no | Query usage for the last N minutes (default: 2) |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ----------------------------------------------- |
| `--model <model>` | string | no | Model name(s), comma-separated |
| `--period <minutes>` | string | no | Query usage for the last N minutes (default: 2) |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
@@ -64,12 +66,14 @@ bl quota check --output json
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------ | -------- | -------------------------------- |
| `--page <n>` | string | no | Page number (default: 1) |
| `--page-size <n>` | string | no | Page size (default: 10) |
| `--model <model>` | string | no | Filter by model name |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------- |
| `--page <n>` | string | no | Page number (default: 1) |
| `--page-size <n>` | string | no | Page size (default: 10) |
| `--model <model>` | string | no | Filter by model name |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
@@ -103,11 +107,13 @@ bl quota history --output json
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------- | -------- | ------------------------------------------- |
| `--model <model>` | string | no | Model name(s), comma-separated |
| `--all` | boolean | no | Show all models, not just self-service ones |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------- | -------- | ------------------------------------------- |
| `--model <model>` | string | no | Model name(s), comma-separated |
| `--all` | boolean | no | Show all models, not just self-service ones |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
@@ -141,12 +147,14 @@ bl quota list --output json
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------- | -------- | -------------------------------- |
| `--model <model>` | string | yes | Model name (required) |
| `--tpm <value>` | string | yes | Target TPM value (required) |
| `--yes` | boolean | no | Skip downgrade confirmation |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------- | -------- | ------------------------------------- |
| `--model <model>` | string | yes | Model name (required) |
| `--tpm <value>` | string | yes | Target TPM value (required) |
| `--yes` | boolean | no | Skip downgrade confirmation |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
+27 -21
View File
@@ -25,12 +25,14 @@ Index: [index.md](index.md)
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------- |
| `--model <model>` | string | no | Model name(s) to query, comma-separated for multiple; omit for all models |
| `--expiring <days>` | string | no | Only show quotas expiring within N days |
| `--sort <field>` | string | no | Sort by: remaining (ascending), expires (ascending) |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------- |
| `--model <model>` | string | no | Model name(s) to query, comma-separated for multiple; omit for all models |
| `--expiring <days>` | string | no | Only show quotas expiring within N days |
| `--sort <field>` | string | no | Sort by: remaining (ascending), expires (ascending) |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
@@ -59,7 +61,7 @@ bl usage free --model qwen-turbo --output json
```
```bash
bl usage free --model qwen3-max --region cn-beijing
bl usage free --model qwen3-max --console-region cn-beijing
```
### `bl usage freetier`
@@ -72,13 +74,15 @@ bl usage free --model qwen3-max --region cn-beijing
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------- | -------- | ------------------------------------------- |
| `--model <model>` | string | no | Model name(s), comma-separated for multiple |
| `--all` | boolean | no | Apply to all free-tier models |
| `--on` | boolean | no | Enable auto-stop (default behavior) |
| `--off` | boolean | no | Disable auto-stop |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------- | -------- | ------------------------------------------- |
| `--model <model>` | string | no | Model name(s), comma-separated for multiple |
| `--all` | boolean | no | Apply to all free-tier models |
| `--on` | boolean | no | Enable auto-stop (default behavior) |
| `--off` | boolean | no | Disable auto-stop |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
@@ -116,13 +120,15 @@ bl usage freetier --off --all
#### Options
| Flag | Type | Required | Description |
| --------------------- | ------ | -------- | ------------------------------------------------------ |
| `--model <model>` | string | no | Model name(s), comma-separated; omit for overview |
| `--days <days>` | string | no | Number of days (default: 7) |
| `--type <type>` | string | no | Model type: Text, Vision, Multimodal, Audio, Embedding |
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------------------------ |
| `--model <model>` | string | no | Model name(s), comma-separated; omit for overview |
| `--days <days>` | string | no | Number of days (default: 7) |
| `--type <type>` | string | no | Model type: Text, Vision, Multimodal, Audio, Embedding |
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples
+6 -4
View File
@@ -23,10 +23,12 @@ Index: [index.md](index.md)
#### Options
| Flag | Type | Required | Description |
| ------------------- | ------ | -------- | -------------------------------- |
| `--list <n>` | string | no | Limit number of results |
| `--region <region>` | string | no | API region (default: cn-beijing) |
| Flag | Type | Required | Description |
| ------------------------------ | ------ | -------- | ------------------------------------- |
| `--list <n>` | string | no | Limit number of results |
| `--console-region <region>` | string | no | Console region |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID |
#### Examples