feat(cli): expose high-risk confirmation guidance in help and skills

This commit is contained in:
若麒
2026-08-28 17:19:24 +08:00
parent afb547e0c8
commit 0872ff6a20
14 changed files with 288 additions and 114 deletions
+20
View File
@@ -1,6 +1,7 @@
import type {
AnyCommand,
AuthRequirement,
CommandRiskLevel,
FlagDef,
FlagsDef,
LocalizedText,
@@ -41,10 +42,16 @@ const AUTH_LABELS = {
none: { "en-US": "No Auth", "zh-CN": "无需鉴权" },
} satisfies Record<AuthRequirement, LocalizedText>;
const RISK_LEVEL_LABELS = {
high: { "en-US": "high", "zh-CN": "高风险" },
} satisfies Record<CommandRiskLevel, LocalizedText>;
const HELP_TEXT = {
usage: { "en-US": "Usage:", "zh-CN": "用法:" },
commands: { "en-US": "Commands:", "zh-CN": "命令:" },
authentication: { "en-US": "Authentication:", "zh-CN": "鉴权方式:" },
risk: { "en-US": "Risk:", "zh-CN": "风险等级:" },
riskMessage: { "en-US": "Risk message:", "zh-CN": "风险说明:" },
flags: { "en-US": "Flags:", "zh-CN": "选项:" },
globalFlags: { "en-US": "Global Flags:", "zh-CN": "全局选项:" },
modelAuthFlags: { "en-US": "Model Auth Flags:", "zh-CN": "模型鉴权选项:" },
@@ -64,6 +71,10 @@ const HELP_TEXT = {
},
notes: { "en-US": "Notes:", "zh-CN": "说明:" },
examples: { "en-US": "Examples:", "zh-CN": "示例:" },
confirmedExample: {
"en-US": "# Only after explicit confirmation:",
"zh-CN": "# 仅在明确确认后执行:",
},
minimalWorkflow: { "en-US": "Minimal workflow.yaml:", "zh-CN": "最小 workflow.yaml:" },
tryIt: { "en-US": "Try it:", "zh-CN": "试一试:" },
} satisfies Record<string, LocalizedText>;
@@ -432,6 +443,12 @@ ${authFlagSections ? `${authFlagSections}\n\n` : ""}${b(this.localize(HELP_TEXT.
out.write(
`${b(this.localize(HELP_TEXT.authentication))} ${a(this.localize(AUTH_LABELS[cmd.auth]))}\n`,
);
if (cmd.risk !== undefined) {
out.write(
`${b(this.localize(HELP_TEXT.risk))} ${a(this.localize(RISK_LEVEL_LABELS[cmd.risk.level]))}\n`,
);
out.write(`${b(this.localize(HELP_TEXT.riskMessage))} ${this.localize(cmd.risk.message)}\n`);
}
const flagEntries = [
...Object.entries(cmd.flags ?? {}),
...Object.entries(confirmationFlagDefs(cmd)),
@@ -460,6 +477,9 @@ ${authFlagSections ? `${authFlagSections}\n\n` : ""}${b(this.localize(HELP_TEXT.
out.write(`\n${b(this.localize(HELP_TEXT.examples))}\n`);
for (const example of cmd.exampleArgs) {
const localizedExample = this.localize(example);
if (cmd.risk !== undefined && /(?:^|\s)--yes(?:\s|$)/.test(localizedExample)) {
out.write(` ${d(this.localize(HELP_TEXT.confirmedExample))}\n`);
}
const line = localizedExample.startsWith("#")
? localizedExample
: localizedExample
+9
View File
@@ -47,6 +47,13 @@ test("registry renders runtime help copy with the selected language", async () =
},
],
auth: "none",
risk: {
level: "high",
message: {
"en-US": "This operation is permanent.",
"zh-CN": "该操作无法撤销。",
},
},
run: async () => {},
});
const registry = new CommandRegistry({ test: command }, "bl", translator);
@@ -69,6 +76,8 @@ test("registry renders runtime help copy with the selected language", async () =
output = "";
registry.printHelp(["test"], stream);
expect(output).toContain("风险等级: 高风险");
expect(output).toContain("风险说明: 该操作无法撤销。");
expect(output).toContain("测试说明");
expect(output).toContain('bl test --message "你好"');
expect(output).toContain(" # 流式输出响应");
@@ -53,11 +53,12 @@ test("high risk 命令不能自行声明 runtime 保留的 yes", () => {
expect(() => new CommandRegistry({ "x normal": normal }, "bl")).not.toThrow();
});
test("命令 help 只为 high risk 展示 runtime 注入的 --yes", () => {
test("命令 help 只为 high risk 展示风险信息和 runtime 注入的 --yes", () => {
const high = defineCommand({
description: "danger",
auth: "none",
risk: { level: "high", message: "dangerous operation" },
exampleArgs: ["--dry-run", "--yes"],
run: noopRun,
});
const normal = defineCommand({
@@ -77,5 +78,10 @@ test("命令 help 只为 high risk 展示 runtime 注入的 --yes", () => {
} as unknown as NodeJS.WriteStream);
expect(highHelp).toContain("--yes");
expect(highHelp).toContain("Risk: high");
expect(highHelp).toContain("Risk message: dangerous operation");
expect(highHelp).toMatch(/# Only after explicit confirmation:\n\s+bl asset delete --yes/);
expect(normalHelp).not.toContain("--yes");
expect(normalHelp).not.toContain("Risk:");
expect(normalHelp).not.toContain("Risk message:");
});