Skip to content

Commit bd90d49

Browse files
committed
feat(cli): support aliyun bl invocation display
1 parent e66e686 commit bd90d49

80 files changed

Lines changed: 695 additions & 277 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ _Built for AI Agents. Every command works as a structured tool call._
2323

2424
## Features
2525

26+
When a host sets `ALIBABA_CLOUD_BL_COMPAT_MODE=aliyun bl`, CLI help, examples,
27+
version output, and operation hints use `aliyun bl` as the invocation prefix.
28+
Unset, empty, or unrecognized values retain `bl`. Product identity, API channel
29+
headers, and credential resolution are unchanged. This display contract does not
30+
by itself indicate that the Aliyun CLI extension has been released.
31+
2632
- **Model generation** — Full-modality generation across text, image, video, and speech, with editing and reference-based generation
2733
- **Asset understanding** — Parse and ask questions about images, documents, audio, and long videos
2834
- **App orchestration** — Call Managed Agents, agents, and workflows published on Aliyun Model Studio, wired to knowledge bases, memory, web search, and MCP tools

‎README.zh.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ _专为 AI Agent 打造,每个命令均可作为结构化工具调用。_
2222

2323
## 功能特性
2424

25+
宿主设置 `ALIBABA_CLOUD_BL_COMPAT_MODE=aliyun bl` 时,帮助、示例、版本输出和操作提示
26+
使用 `aliyun bl` 命令前缀;未设置、空值或未识别的值仍使用 `bl`。产品身份、API 渠道
27+
标识和凭证解析保持原样。这一展示契约本身不表示阿里云 CLI 扩展已经发布。
28+
2529
- **模型生成** — 文本、图像、视频、语音全模态生成,支持编辑与参考生成
2630
- **素材理解** — 图像、文档、音频、长视频的解析与问答
2731
- **应用编排** — 调用百炼已发布的 Managed Agent、智能体和工作流,接入知识库、记忆库、联网搜索与 MCP 工具

‎docs/agents/error-hint-change.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ process.exit(err.exitCode)
7676

7777
### 4. runtime / 产品层可以使用入口名 + URL
7878

79+
- 有宿主调用前缀时,用 `getCommandPrefix(identity)` 生成用户操作提示;`identity.binName`
80+
保持产品身份,用于渠道、安装包和真实可执行文件名。不要全局替换输出或服务端文本。
81+
- core 本地鉴权错误可通过 `authDomain` 提供结构化上下文,由 runtime 生成带展示前缀的登录引导。
82+
7983
- `packages/runtime/src/error-handler.ts` 通过 `binName` 渲染 `bl` / `kscli` 等入口名,不要硬编码
8084
- 产品入口 / README / E2E 可以写具体入口命令
8185
- shared command 实现不写 `bl` / `kscli` 前缀;`usageArgs` / `exampleArgs` 只写参数片段

‎packages/cli/src/main.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ void createCli(
1515
{ ...commands, ...commandAliases },
1616
{
1717
binName: "bl",
18+
commandPrefix:
19+
process.env.ALIBABA_CLOUD_BL_COMPAT_MODE?.trim() === "aliyun bl" ? "aliyun bl" : undefined,
1820
version: pkg.version,
1921
clientName: "bailian-cli",
2022
npmPackage: "bailian-cli",
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import { join } from "node:path";
4+
import { afterAll, expect, test } from "vite-plus/test";
5+
import { runCli } from "./helpers.ts";
6+
7+
const configDirectory = mkdtempSync(join(tmpdir(), "bl-aliyun-compat-"));
8+
afterAll(() => rmSync(configDirectory, { recursive: true, force: true }));
9+
10+
function runCompatible(arguments_: string[], language = "en-US", compatibility = "aliyun bl") {
11+
writeFileSync(join(configDirectory, "config.json"), JSON.stringify({ language }));
12+
return runCli(arguments_, {
13+
BAILIAN_CONFIG_DIR: configDirectory,
14+
ALIBABA_CLOUD_BL_COMPAT_MODE: compatibility,
15+
DASHSCOPE_API_KEY: "",
16+
ALIBABA_CLOUD_ACCESS_KEY_ID: "",
17+
ALIBABA_CLOUD_ACCESS_KEY_SECRET: "",
18+
ALIBABA_CLOUD_SECURITY_TOKEN: "",
19+
NO_UPDATE_NOTIFIER: "1",
20+
DO_NOT_TRACK: "1",
21+
});
22+
}
23+
24+
test.each(["en-US", "zh-CN"])(
25+
"host help and onboarding use the invocation prefix (%s)",
26+
async (language) => {
27+
const root = await runCompatible(["--help"], language);
28+
expect(root.exitCode, root.stderr).toBe(0);
29+
expect(root.stderr).toContain("aliyun bl <resource> <command> [flags]");
30+
expect(root.stderr).toContain("aliyun bl auth login --console");
31+
},
32+
);
33+
34+
test.each(
35+
["en-US", "zh-CN"].flatMap((language) =>
36+
["app", "app list", "skill init", "managed-agent plan"].map((path) => ({ language, path })),
37+
),
38+
)("host nested help uses the invocation prefix ($language, $path)", async ({ language, path }) => {
39+
const result = await runCompatible([...path.split(" "), "--help"], language);
40+
expect(result.exitCode, result.stderr).toBe(0);
41+
expect(result.stderr).toContain(`aliyun bl ${path}`);
42+
expect(result.stderr).not.toMatch(/(?<!aliyun )\bbl (?:auth|skill|managed-agent) /);
43+
});
44+
45+
test("host introspection keeps product identity but qualifies executable examples", async () => {
46+
const result = await runCompatible(["auth", "status", "--introspect"]);
47+
expect(result.exitCode, result.stderr).toBe(0);
48+
const schema = JSON.parse(result.stdout);
49+
expect(schema.bin).toBe("bl");
50+
expect(schema.commands[0].path).toEqual(["auth", "status"]);
51+
expect(schema.commands[0].usage).toMatch(/^aliyun bl auth status/);
52+
expect(schema.commands[0].examples[0]).toBe("aliyun bl auth status");
53+
});
54+
55+
test("host version is display-only and absent or unknown COMPAT keeps standalone", async () => {
56+
const hosted = await runCompatible(["--version"]);
57+
expect(hosted.stdout).toMatch(/^aliyun bl \d+\.\d+\.\d+\n$/);
58+
for (const compatibility of ["", " ", "unrecognized-host"]) {
59+
const standalone = await runCompatible(["--version"], "en-US", compatibility);
60+
expect(standalone.stdout).toMatch(/^bl \d+\.\d+\.\d+\n$/);
61+
}
62+
});
63+
64+
test.each(["text", "json"])(
65+
"missing Console credentials use host instructions (%s)",
66+
async (output) => {
67+
const result = await runCompatible(["app", "list", "--output", output]);
68+
expect(result.exitCode, result.stderr).toBe(3);
69+
const diagnostic = output === "json" ? JSON.parse(result.stderr).error.hint : result.stderr;
70+
expect(diagnostic).toContain("aliyun bl auth login --console");
71+
expect(diagnostic).not.toMatch(/(?<!aliyun )\bbl auth login/);
72+
},
73+
);
74+
75+
test("auth status operation hints use the host prefix without changing authentication", async () => {
76+
const result = await runCompatible(["auth", "status", "--output", "json"]);
77+
expect(result.exitCode, result.stderr).toBe(0);
78+
const status = JSON.parse(result.stdout);
79+
expect(status.authenticated).toBe(false);
80+
expect(status.hint).toContain("aliyun bl auth login --api-key");
81+
});

‎packages/commands/src/commands/advisor/recommend.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
defineCommand,
88
detectOutputFormat,
99
ExitCode,
10+
getCommandPrefix,
1011
type EngineRecommendResult,
1112
type Degradation,
1213
LLMError,
@@ -45,7 +46,7 @@ function emitDegradations(degradations: Degradation[]): void {
4546
}
4647

4748
/** Map a classified engine LLM failure onto a CLI-native BailianError with an actionable hint. */
48-
function toBailianError(err: LLMError): { message: string; hint?: string } {
49+
function toBailianError(err: LLMError, commandPrefix: string): { message: string; hint?: string } {
4950
const stage = STAGE_LABEL[err.stage ?? ""] ?? "LLM call";
5051
const requestId = err.requestId ? ` (request_id: ${err.requestId})` : "";
5152
const message = `Recommendation aborted: ${stage} failed — ${err.message}${requestId}`;
@@ -64,7 +65,7 @@ function toBailianError(err: LLMError): { message: string; hint?: string } {
6465
case "auth":
6566
return {
6667
message,
67-
hint: "The API key is invalid or lacks access to the model. Check DASHSCOPE_API_KEY / --api-key, or run `bl auth login`. / API Key 无效或无该模型访问权限,请检查 DASHSCOPE_API_KEY / --api-key 或执行 `bl auth login`。",
68+
hint: `The API key is invalid or lacks access to the model. Check DASHSCOPE_API_KEY / --api-key, or run \`${commandPrefix} auth login\`. / API Key 无效或无该模型访问权限,请检查 DASHSCOPE_API_KEY / --api-key 或执行 \`${commandPrefix} auth login\`。`,
6869
};
6970
case "timeout":
7071
return {
@@ -245,7 +246,7 @@ export default defineCommand({
245246
}
246247
} catch (err) {
247248
if (err instanceof LLMError) {
248-
const { message: errMessage, hint } = toBailianError(err);
249+
const { message: errMessage, hint } = toBailianError(err, getCommandPrefix(ctx.identity));
249250
throw new BailianError(errMessage, ExitCode.GENERAL, hint);
250251
}
251252
throw err;

‎packages/commands/src/commands/alert/create.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { detectOutputFormat, unwrapResponse } from "bailian-cli-core";
1+
import { getCommandPrefix, detectOutputFormat, unwrapResponse } from "bailian-cli-core";
22
import { defineTelemetryCommand, telemetryGatewayPreview } from "../shared/telemetry.ts";
33
import { emitResult } from "bailian-cli-runtime";
44
import {
@@ -54,7 +54,12 @@ export default defineTelemetryCommand({
5454
return;
5555
}
5656

57-
await ensureAlertReady(ctx.client, settings.workspaceId, ctx.identity.binName, settings);
57+
await ensureAlertReady(
58+
ctx.client,
59+
settings.workspaceId,
60+
getCommandPrefix(ctx.identity),
61+
settings,
62+
);
5863

5964
const raw = await ctx.client.console(CREATE_RULE_API, { reqDTO });
6065
const resp = unwrapResponse(raw as Record<string, unknown>);

‎packages/commands/src/commands/alert/delete.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { detectOutputFormat } from "bailian-cli-core";
1+
import { getCommandPrefix, detectOutputFormat } from "bailian-cli-core";
22
import { defineTelemetryCommand, telemetryGatewayPreview } from "../shared/telemetry.ts";
33
import { emitResult } from "bailian-cli-runtime";
44
import { parseCommaList } from "../shared/params.ts";
@@ -56,7 +56,12 @@ export default defineTelemetryCommand({
5656
return;
5757
}
5858

59-
await ensureAlertReady(ctx.client, settings.workspaceId, ctx.identity.binName, settings);
59+
await ensureAlertReady(
60+
ctx.client,
61+
settings.workspaceId,
62+
getCommandPrefix(ctx.identity),
63+
settings,
64+
);
6065

6166
await ctx.client.console(DELETE_RULES_API, { reqDTO });
6267

‎packages/commands/src/commands/alert/disable.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { detectOutputFormat } from "bailian-cli-core";
1+
import { getCommandPrefix, detectOutputFormat } from "bailian-cli-core";
22
import { defineTelemetryCommand, telemetryGatewayPreview } from "../shared/telemetry.ts";
33
import { emitResult } from "bailian-cli-runtime";
44
import { ensureAlertReady } from "./shared.ts";
@@ -46,7 +46,12 @@ export default defineTelemetryCommand({
4646
return;
4747
}
4848

49-
await ensureAlertReady(ctx.client, settings.workspaceId, ctx.identity.binName, settings);
49+
await ensureAlertReady(
50+
ctx.client,
51+
settings.workspaceId,
52+
getCommandPrefix(ctx.identity),
53+
settings,
54+
);
5055

5156
await ctx.client.console(DISABLE_RULE_API, { reqDTO });
5257

‎packages/commands/src/commands/alert/enable.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { detectOutputFormat } from "bailian-cli-core";
1+
import { getCommandPrefix, detectOutputFormat } from "bailian-cli-core";
22
import { defineTelemetryCommand, telemetryGatewayPreview } from "../shared/telemetry.ts";
33
import { emitResult } from "bailian-cli-runtime";
44
import { ensureAlertReady } from "./shared.ts";
@@ -44,7 +44,12 @@ export default defineTelemetryCommand({
4444
return;
4545
}
4646

47-
await ensureAlertReady(ctx.client, settings.workspaceId, ctx.identity.binName, settings);
47+
await ensureAlertReady(
48+
ctx.client,
49+
settings.workspaceId,
50+
getCommandPrefix(ctx.identity),
51+
settings,
52+
);
4853

4954
await ctx.client.console("zeldaEasy.bailian-telemetry.alertRule.enableAlertRule", { reqDTO });
5055

0 commit comments

Comments
 (0)