diff --git a/packages/commands/src/commands/managed-agent/apply.ts b/packages/commands/src/commands/managed-agent/apply.ts index 6040a0a..90cfca8 100644 --- a/packages/commands/src/commands/managed-agent/apply.ts +++ b/packages/commands/src/commands/managed-agent/apply.ts @@ -34,13 +34,6 @@ const APPLY_FLAGS = { "zh-CN": "目标 Provider(默认:全部已配置项)", }, }, - yes: { - type: "switch", - description: { - "en-US": "Confirm and apply without an interactive prompt (required to mutate)", - "zh-CN": "无需交互提示直接确认并应用(执行变更时必填)", - }, - }, noRefresh: { type: "switch", description: { @@ -64,7 +57,15 @@ export default defineCommand({ "zh-CN": "应用规划的变更,创建、更新或删除 Agent 资源", }, auth: "apiKey", - usageArgs: "[--file ] [--provider ] [--yes] [--concurrency ]", + risk: { + level: "high", + message: { + "en-US": + "This applies the current plan and may create, update, or delete remote managed Agent resources.", + "zh-CN": "该操作会应用当前计划,可能创建、更新或删除远端托管 Agent 资源。", + }, + }, + usageArgs: "[--file ] [--provider ] [--concurrency ]", flags: APPLY_FLAGS, exampleArgs: ["--yes", "--provider bailian --yes"], notes: CREDENTIALS_NOTE, @@ -124,23 +125,11 @@ export default defineCommand({ return; } - const creates = actionable.filter((action) => action.action === "create").length; - const updates = actionable.filter((action) => action.action === "update").length; - const deletes = planned.destructiveActions; - for (const action of actionable) { const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-"; emitProgress(` ${icon} ${formatResourceLabel(action.address)}`); } - if (!flags.yes) { - throw new BailianError( - `Refusing to apply ${actionable.length} change(s) (${creates} create, ${updates} update, ${deletes.length} destroy) without confirmation.`, - ExitCode.USAGE, - "Review with `bl managed-agent plan`, then re-run with --yes to apply.", - ); - } - const result = await withAgentErrors(() => withStdoutProtected(() => executePlannedProject(planned, { diff --git a/packages/commands/src/commands/managed-agent/destroy.ts b/packages/commands/src/commands/managed-agent/destroy.ts index 722faea..7cafc44 100644 --- a/packages/commands/src/commands/managed-agent/destroy.ts +++ b/packages/commands/src/commands/managed-agent/destroy.ts @@ -21,13 +21,6 @@ const DESTROY_FLAGS = { "zh-CN": "配置文件路径(默认:agents.yaml)", }, }, - yes: { - type: "switch", - description: { - "en-US": "Confirm and destroy without an interactive prompt (required)", - "zh-CN": "无需交互提示直接确认并销毁(必填)", - }, - }, cascade: { type: "switch", description: { @@ -43,7 +36,15 @@ export default defineCommand({ "zh-CN": "销毁 State 中跟踪的全部托管 Agent 资源", }, auth: "apiKey", - usageArgs: "[--file ] [--yes] [--cascade]", + risk: { + level: "high", + message: { + "en-US": + "This deletes every managed Agent resource tracked in state; --cascade may also delete dependent resources.", + "zh-CN": "该操作会删除 State 中跟踪的全部托管 Agent 资源;--cascade 还可能删除依赖资源。", + }, + }, + usageArgs: "[--file ] [--cascade]", flags: DESTROY_FLAGS, exampleArgs: ["--yes", "--yes --cascade"], notes: CREDENTIALS_NOTE, @@ -86,14 +87,6 @@ export default defineCommand({ else emitBare(line); } - if (!flags.yes) { - throw new BailianError( - `Refusing to destroy ${resources.length} resource(s) without confirmation.`, - ExitCode.USAGE, - "Re-run with --yes to destroy (add --cascade to remove dependents).", - ); - } - const result = await withAgentErrors(() => withStdoutProtected(() => destroyPlannedProjectResources(planned, { diff --git a/packages/commands/src/commands/permission/revoke.ts b/packages/commands/src/commands/permission/revoke.ts index d85d9e2..c02c8e8 100644 --- a/packages/commands/src/commands/permission/revoke.ts +++ b/packages/commands/src/commands/permission/revoke.ts @@ -1,4 +1,4 @@ -import { defineCommand, BailianError, ExitCode } from "bailian-cli-core"; +import { defineCommand } from "bailian-cli-core"; import { runPermissionChange, validatePermissionChange } from "./shared.ts"; export default defineCommand({ @@ -7,7 +7,16 @@ export default defineCommand({ "zh-CN": "撤销模型权限(推理 / 微调 / 部署)", }, auth: "apiKey", - usageArgs: "--model [--action ] | --all --yes", + risk: { + level: "high", + message: { + "en-US": + "This revokes model permissions and may interrupt inference, fine-tuning, or deployment workloads. With --all, it also clears all historical inference grants.", + "zh-CN": + "该操作会撤销模型权限,可能导致推理、精调或部署任务中断;使用 --all 时会清除全部历史推理授权。", + }, + }, + usageArgs: "--model [--action ] | --all [flags]", flags: { model: { type: "string", @@ -33,17 +42,10 @@ export default defineCommand({ "zh-CN": "关闭一键授权并清除所有历史推理授权", }, }, - yes: { - type: "switch", - description: { - "en-US": "Confirm --all without an interactive prompt (required)", - "zh-CN": "无需交互提示确认执行 --all(必填)", - }, - }, }, exampleArgs: [ - "--model qwen-plus", - "--model qwen-plus,qwen3-max --action inference,finetune", + "--model qwen-plus --yes", + "--model qwen-plus,qwen3-max --action inference,finetune --yes", "--all --yes", "--model qwen-plus --dry-run --output json", ], @@ -52,6 +54,11 @@ export default defineCommand({ "en-US": "Grants apply to the business workspace your API key belongs to.", "zh-CN": "授权将应用于 API Key 所属的业务 Workspace。", }, + { + "en-US": + "All revoke operations require --yes; use --dry-run to preview the request without confirmation.", + "zh-CN": "所有撤权操作均需使用 --yes;可通过 --dry-run 免确认预览请求。", + }, { "en-US": "--all maps to the server one-key switch (access_all_entities: CLOSE): it clears every historical inference grant and cannot be undone, so it requires --yes.", @@ -65,14 +72,6 @@ export default defineCommand({ ], validate: (flags) => validatePermissionChange(flags), async run(ctx) { - const { flags, settings } = ctx; - if (flags.all && !flags.yes && !settings.dryRun) { - throw new BailianError( - "Refusing to clear all historical inference grants without confirmation.", - ExitCode.USAGE, - "Re-run with --yes to close one-key authorization (or preview with --dry-run).", - ); - } - await runPermissionChange(ctx, flags, false); + await runPermissionChange(ctx, ctx.flags, false); }, }); diff --git a/packages/commands/tests/e2e/managed-agent.e2e.test.ts b/packages/commands/tests/e2e/managed-agent.e2e.test.ts index fc6b058..e17aec0 100644 --- a/packages/commands/tests/e2e/managed-agent.e2e.test.ts +++ b/packages/commands/tests/e2e/managed-agent.e2e.test.ts @@ -132,6 +132,23 @@ describe("e2e: managed-agent", () => { expect(stderr).toMatch(/--file|--provider|--yes/i); }); + test.each(["apply", "destroy"])("managed-agent %s 无 --yes 返回确认请求 (7)", async (command) => { + const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ + "managed-agent", + command, + "--file", + "agents.e2e-missing.yaml", + "--api-key", + "e2e-dummy-key", + "--output", + "json", + ]); + expect(exitCode).toBe(7); + expect(JSON.parse(stderr)).toMatchObject({ + error: { code: 7, type: "requires_confirmation" }, + }); + }); + test("managed-agent session delete 缺少 --session-id 时退出为用法错误 (2)", async () => { const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [ "managed-agent", @@ -223,7 +240,6 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () => "managed-agent", "apply", "--dry-run", - "--yes", "--output", "json", ]); diff --git a/packages/commands/tests/e2e/permission.e2e.test.ts b/packages/commands/tests/e2e/permission.e2e.test.ts index f2d03e6..210e01c 100644 --- a/packages/commands/tests/e2e/permission.e2e.test.ts +++ b/packages/commands/tests/e2e/permission.e2e.test.ts @@ -98,18 +98,39 @@ describe("e2e: permission", () => { expect(stderr).toContain("at most 20"); }); - test("permission revoke --all 缺 --yes 拒绝执行", async () => { - // --yes 护栏在 run() 开头、任何网络调用之前抛出;带 dummy key 让用例不依赖环境凭证(否则 auth stage 先报 AUTH(3))。 + test("permission revoke --all 无 --yes 返回确认请求 (7)", async () => { const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [ "permission", "revoke", "--all", "--api-key", "e2e-dummy-key", + "--output", + "json", ]); - expect(exitCode).toBe(2); - expect(stderr).toContain("Refusing"); - expect(stderr).toContain("--yes"); + expect(exitCode).toBe(7); + expect(JSON.parse(stderr)).toMatchObject({ + error: { code: 7, type: "requires_confirmation" }, + }); + }); + + test("permission revoke --model 无 --yes 返回确认请求 (7)", async () => { + const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [ + "permission", + "revoke", + "--model", + "qwen-plus", + "--api-key", + "e2e-dummy-key", + "--base-url", + "http://127.0.0.1:1", + "--output", + "json", + ]); + expect(exitCode).toBe(7); + expect(JSON.parse(stderr)).toMatchObject({ + error: { code: 7, type: "requires_confirmation" }, + }); }); // --dry-run 跳过 auth stage(见 runtime middleware),无需凭证即可断言请求形状。 diff --git a/skills/bailian-cli/reference/permission.md b/skills/bailian-cli/reference/permission.md index 9666cfe..776f60f 100644 --- a/skills/bailian-cli/reference/permission.md +++ b/skills/bailian-cli/reference/permission.md @@ -109,12 +109,12 @@ bl permission list --output text ### `bl permission revoke` -| Field | Value | -| ------------------ | --------------------------------------------------------------------------- | -| **Name** | `permission revoke` | -| **Description** | Revoke model permissions (inference / finetune / deploy) | -| **Authentication** | API Key | -| **Usage** | `bl permission revoke --model [--action ] \| --all --yes` | +| Field | Value | +| ------------------ | ----------------------------------------------------------------------------- | +| **Name** | `permission revoke` | +| **Description** | Revoke model permissions (inference / finetune / deploy) | +| **Authentication** | API Key | +| **Usage** | `bl permission revoke --model [--action ] \| --all [flags]` | #### Flags @@ -123,24 +123,25 @@ bl permission list --output text | `--model ` | string | no | Model ID(s), comma-separated (max 20) | | `--action ` | string | no | Permission action(s), comma-separated: inference, finetune, deploy (default: inference) | | `--all` | switch | no | Close one-key authorization and clear ALL historical inference grants | -| `--yes` | switch | no | Confirm --all without an interactive prompt (required) | +| `--yes` | switch | no | Confirm this high-risk operation | | `--api-key ` | string | no | API key | | `--base-url ` | string | no | API base URL | #### Notes - Grants apply to the business workspace your API key belongs to. +- All revoke operations require --yes; use --dry-run to preview the request without confirmation. - --all maps to the server one-key switch (access_all_entities: CLOSE): it clears every historical inference grant and cannot be undone, so it requires --yes. - Actions you omit keep their current grants (server-side tri-state patch). #### Examples ```bash -bl permission revoke --model qwen-plus +bl permission revoke --model qwen-plus --yes ``` ```bash -bl permission revoke --model qwen-plus,qwen3-max --action inference,finetune +bl permission revoke --model qwen-plus,qwen3-max --action inference,finetune --yes ``` ```bash diff --git a/skills/bailian-managed-agent/reference/managed-agent.md b/skills/bailian-managed-agent/reference/managed-agent.md index e3975b7..e5ce1b7 100644 --- a/skills/bailian-managed-agent/reference/managed-agent.md +++ b/skills/bailian-managed-agent/reference/managed-agent.md @@ -31,24 +31,24 @@ Index: [index.md](index.md) ### `bl managed-agent apply` -| Field | Value | -| ------------------ | ---------------------------------------------------------------------------------------- | -| **Name** | `managed-agent apply` | -| **Description** | Apply planned changes to create/update/delete agent resources | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent apply [--file ] [--provider ] [--yes] [--concurrency ]` | +| Field | Value | +| ------------------ | -------------------------------------------------------------------------------- | +| **Name** | `managed-agent apply` | +| **Description** | Apply planned changes to create/update/delete agent resources | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent apply [--file ] [--provider ] [--concurrency ]` | #### Flags -| Flag | Type | Required | Description | -| ------------------- | ------ | -------- | -------------------------------------------------------------------- | -| `--file ` | string | no | Config file path (default: agents.yaml) | -| `--provider ` | string | no | Target provider (default: all configured) | -| `--yes` | switch | no | Confirm and apply without an interactive prompt (required to mutate) | -| `--no-refresh` | switch | no | Skip refreshing state from remote before planning | -| `--concurrency ` | number | no | Max independent resources to apply in parallel (default 6, max 10) | -| `--api-key ` | string | no | API key | -| `--base-url ` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | ------------------------------------------------------------------ | +| `--file ` | string | no | Config file path (default: agents.yaml) | +| `--provider ` | string | no | Target provider (default: all configured) | +| `--no-refresh` | switch | no | Skip refreshing state from remote before planning | +| `--concurrency ` | number | no | Max independent resources to apply in parallel (default 6, max 10) | +| `--yes` | switch | no | Confirm this high-risk operation | +| `--api-key ` | string | no | API key | +| `--base-url ` | string | no | API base URL | #### Notes @@ -68,20 +68,20 @@ bl managed-agent apply --provider bailian --yes ### `bl managed-agent destroy` -| Field | Value | -| ------------------ | -------------------------------------------------------------- | -| **Name** | `managed-agent destroy` | -| **Description** | Destroy all managed agent resources tracked in state | -| **Authentication** | API Key | -| **Usage** | `bl managed-agent destroy [--file ] [--yes] [--cascade]` | +| Field | Value | +| ------------------ | ------------------------------------------------------ | +| **Name** | `managed-agent destroy` | +| **Description** | Destroy all managed agent resources tracked in state | +| **Authentication** | API Key | +| **Usage** | `bl managed-agent destroy [--file ] [--cascade]` | #### Flags | Flag | Type | Required | Description | | ------------------ | ------ | -------- | -------------------------------------------------------------------------- | | `--file ` | string | no | Config file path (default: agents.yaml) | -| `--yes` | switch | no | Confirm and destroy without an interactive prompt (required) | | `--cascade` | switch | no | Auto-delete dependent resources (e.g. sessions referencing an environment) | +| `--yes` | switch | no | Confirm this high-risk operation | | `--api-key ` | string | no | API key | | `--base-url ` | string | no | API base URL |