mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
refactor(commands): migrate remaining confirmations to runtime gate
This commit is contained in:
@@ -34,13 +34,6 @@ const APPLY_FLAGS = {
|
|||||||
"zh-CN": "目标 Provider(默认:全部已配置项)",
|
"zh-CN": "目标 Provider(默认:全部已配置项)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
yes: {
|
|
||||||
type: "switch",
|
|
||||||
description: {
|
|
||||||
"en-US": "Confirm and apply without an interactive prompt (required to mutate)",
|
|
||||||
"zh-CN": "无需交互提示直接确认并应用(执行变更时必填)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
noRefresh: {
|
noRefresh: {
|
||||||
type: "switch",
|
type: "switch",
|
||||||
description: {
|
description: {
|
||||||
@@ -64,7 +57,15 @@ export default defineCommand({
|
|||||||
"zh-CN": "应用规划的变更,创建、更新或删除 Agent 资源",
|
"zh-CN": "应用规划的变更,创建、更新或删除 Agent 资源",
|
||||||
},
|
},
|
||||||
auth: "apiKey",
|
auth: "apiKey",
|
||||||
usageArgs: "[--file <path>] [--provider <name>] [--yes] [--concurrency <n>]",
|
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 <path>] [--provider <name>] [--concurrency <n>]",
|
||||||
flags: APPLY_FLAGS,
|
flags: APPLY_FLAGS,
|
||||||
exampleArgs: ["--yes", "--provider bailian --yes"],
|
exampleArgs: ["--yes", "--provider bailian --yes"],
|
||||||
notes: CREDENTIALS_NOTE,
|
notes: CREDENTIALS_NOTE,
|
||||||
@@ -124,23 +125,11 @@ export default defineCommand({
|
|||||||
return;
|
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) {
|
for (const action of actionable) {
|
||||||
const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-";
|
const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-";
|
||||||
emitProgress(` ${icon} ${formatResourceLabel(action.address)}`);
|
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(() =>
|
const result = await withAgentErrors(() =>
|
||||||
withStdoutProtected(() =>
|
withStdoutProtected(() =>
|
||||||
executePlannedProject(planned, {
|
executePlannedProject(planned, {
|
||||||
|
|||||||
@@ -21,13 +21,6 @@ const DESTROY_FLAGS = {
|
|||||||
"zh-CN": "配置文件路径(默认:agents.yaml)",
|
"zh-CN": "配置文件路径(默认:agents.yaml)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
yes: {
|
|
||||||
type: "switch",
|
|
||||||
description: {
|
|
||||||
"en-US": "Confirm and destroy without an interactive prompt (required)",
|
|
||||||
"zh-CN": "无需交互提示直接确认并销毁(必填)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cascade: {
|
cascade: {
|
||||||
type: "switch",
|
type: "switch",
|
||||||
description: {
|
description: {
|
||||||
@@ -43,7 +36,15 @@ export default defineCommand({
|
|||||||
"zh-CN": "销毁 State 中跟踪的全部托管 Agent 资源",
|
"zh-CN": "销毁 State 中跟踪的全部托管 Agent 资源",
|
||||||
},
|
},
|
||||||
auth: "apiKey",
|
auth: "apiKey",
|
||||||
usageArgs: "[--file <path>] [--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 <path>] [--cascade]",
|
||||||
flags: DESTROY_FLAGS,
|
flags: DESTROY_FLAGS,
|
||||||
exampleArgs: ["--yes", "--yes --cascade"],
|
exampleArgs: ["--yes", "--yes --cascade"],
|
||||||
notes: CREDENTIALS_NOTE,
|
notes: CREDENTIALS_NOTE,
|
||||||
@@ -86,14 +87,6 @@ export default defineCommand({
|
|||||||
else emitBare(line);
|
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(() =>
|
const result = await withAgentErrors(() =>
|
||||||
withStdoutProtected(() =>
|
withStdoutProtected(() =>
|
||||||
destroyPlannedProjectResources(planned, {
|
destroyPlannedProjectResources(planned, {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineCommand, BailianError, ExitCode } from "bailian-cli-core";
|
import { defineCommand } from "bailian-cli-core";
|
||||||
import { runPermissionChange, validatePermissionChange } from "./shared.ts";
|
import { runPermissionChange, validatePermissionChange } from "./shared.ts";
|
||||||
|
|
||||||
export default defineCommand({
|
export default defineCommand({
|
||||||
@@ -7,7 +7,16 @@ export default defineCommand({
|
|||||||
"zh-CN": "撤销模型权限(推理 / 微调 / 部署)",
|
"zh-CN": "撤销模型权限(推理 / 微调 / 部署)",
|
||||||
},
|
},
|
||||||
auth: "apiKey",
|
auth: "apiKey",
|
||||||
usageArgs: "--model <models> [--action <actions>] | --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 <models> [--action <actions>] | --all [flags]",
|
||||||
flags: {
|
flags: {
|
||||||
model: {
|
model: {
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -33,17 +42,10 @@ export default defineCommand({
|
|||||||
"zh-CN": "关闭一键授权并清除所有历史推理授权",
|
"zh-CN": "关闭一键授权并清除所有历史推理授权",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
yes: {
|
|
||||||
type: "switch",
|
|
||||||
description: {
|
|
||||||
"en-US": "Confirm --all without an interactive prompt (required)",
|
|
||||||
"zh-CN": "无需交互提示确认执行 --all(必填)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
exampleArgs: [
|
exampleArgs: [
|
||||||
"--model qwen-plus",
|
"--model qwen-plus --yes",
|
||||||
"--model qwen-plus,qwen3-max --action inference,finetune",
|
"--model qwen-plus,qwen3-max --action inference,finetune --yes",
|
||||||
"--all --yes",
|
"--all --yes",
|
||||||
"--model qwen-plus --dry-run --output json",
|
"--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.",
|
"en-US": "Grants apply to the business workspace your API key belongs to.",
|
||||||
"zh-CN": "授权将应用于 API Key 所属的业务 Workspace。",
|
"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":
|
"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.",
|
"--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),
|
validate: (flags) => validatePermissionChange(flags),
|
||||||
async run(ctx) {
|
async run(ctx) {
|
||||||
const { flags, settings } = ctx;
|
await runPermissionChange(ctx, ctx.flags, false);
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -132,6 +132,23 @@ describe("e2e: managed-agent", () => {
|
|||||||
expect(stderr).toMatch(/--file|--provider|--yes/i);
|
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 () => {
|
test("managed-agent session delete 缺少 --session-id 时退出为用法错误 (2)", async () => {
|
||||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||||
"managed-agent",
|
"managed-agent",
|
||||||
@@ -223,7 +240,6 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () =>
|
|||||||
"managed-agent",
|
"managed-agent",
|
||||||
"apply",
|
"apply",
|
||||||
"--dry-run",
|
"--dry-run",
|
||||||
"--yes",
|
|
||||||
"--output",
|
"--output",
|
||||||
"json",
|
"json",
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -98,18 +98,39 @@ describe("e2e: permission", () => {
|
|||||||
expect(stderr).toContain("at most 20");
|
expect(stderr).toContain("at most 20");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("permission revoke --all 缺 --yes 拒绝执行", async () => {
|
test("permission revoke --all 无 --yes 返回确认请求 (7)", async () => {
|
||||||
// --yes 护栏在 run() 开头、任何网络调用之前抛出;带 dummy key 让用例不依赖环境凭证(否则 auth stage 先报 AUTH(3))。
|
|
||||||
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
const { stderr, exitCode } = await runCommandE2e(PERMISSION_ROUTES, [
|
||||||
"permission",
|
"permission",
|
||||||
"revoke",
|
"revoke",
|
||||||
"--all",
|
"--all",
|
||||||
"--api-key",
|
"--api-key",
|
||||||
"e2e-dummy-key",
|
"e2e-dummy-key",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
]);
|
]);
|
||||||
expect(exitCode).toBe(2);
|
expect(exitCode).toBe(7);
|
||||||
expect(stderr).toContain("Refusing");
|
expect(JSON.parse(stderr)).toMatchObject({
|
||||||
expect(stderr).toContain("--yes");
|
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),无需凭证即可断言请求形状。
|
// --dry-run 跳过 auth stage(见 runtime middleware),无需凭证即可断言请求形状。
|
||||||
|
|||||||
@@ -109,12 +109,12 @@ bl permission list --output text
|
|||||||
|
|
||||||
### `bl permission revoke`
|
### `bl permission revoke`
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| ------------------ | --------------------------------------------------------------------------- |
|
| ------------------ | ----------------------------------------------------------------------------- |
|
||||||
| **Name** | `permission revoke` |
|
| **Name** | `permission revoke` |
|
||||||
| **Description** | Revoke model permissions (inference / finetune / deploy) |
|
| **Description** | Revoke model permissions (inference / finetune / deploy) |
|
||||||
| **Authentication** | API Key |
|
| **Authentication** | API Key |
|
||||||
| **Usage** | `bl permission revoke --model <models> [--action <actions>] \| --all --yes` |
|
| **Usage** | `bl permission revoke --model <models> [--action <actions>] \| --all [flags]` |
|
||||||
|
|
||||||
#### Flags
|
#### Flags
|
||||||
|
|
||||||
@@ -123,24 +123,25 @@ bl permission list --output text
|
|||||||
| `--model <models>` | string | no | Model ID(s), comma-separated (max 20) |
|
| `--model <models>` | string | no | Model ID(s), comma-separated (max 20) |
|
||||||
| `--action <actions>` | string | no | Permission action(s), comma-separated: inference, finetune, deploy (default: inference) |
|
| `--action <actions>` | 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 |
|
| `--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 <key>` | string | no | API key |
|
| `--api-key <key>` | string | no | API key |
|
||||||
| `--base-url <url>` | string | no | API base URL |
|
| `--base-url <url>` | string | no | API base URL |
|
||||||
|
|
||||||
#### Notes
|
#### Notes
|
||||||
|
|
||||||
- Grants apply to the business workspace your API key belongs to.
|
- 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.
|
- --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).
|
- Actions you omit keep their current grants (server-side tri-state patch).
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bl permission revoke --model qwen-plus
|
bl permission revoke --model qwen-plus --yes
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
|
|||||||
@@ -31,24 +31,24 @@ Index: [index.md](index.md)
|
|||||||
|
|
||||||
### `bl managed-agent apply`
|
### `bl managed-agent apply`
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| ------------------ | ---------------------------------------------------------------------------------------- |
|
| ------------------ | -------------------------------------------------------------------------------- |
|
||||||
| **Name** | `managed-agent apply` |
|
| **Name** | `managed-agent apply` |
|
||||||
| **Description** | Apply planned changes to create/update/delete agent resources |
|
| **Description** | Apply planned changes to create/update/delete agent resources |
|
||||||
| **Authentication** | API Key |
|
| **Authentication** | API Key |
|
||||||
| **Usage** | `bl managed-agent apply [--file <path>] [--provider <name>] [--yes] [--concurrency <n>]` |
|
| **Usage** | `bl managed-agent apply [--file <path>] [--provider <name>] [--concurrency <n>]` |
|
||||||
|
|
||||||
#### Flags
|
#### Flags
|
||||||
|
|
||||||
| Flag | Type | Required | Description |
|
| Flag | Type | Required | Description |
|
||||||
| ------------------- | ------ | -------- | -------------------------------------------------------------------- |
|
| ------------------- | ------ | -------- | ------------------------------------------------------------------ |
|
||||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||||
| `--provider <name>` | string | no | Target provider (default: all configured) |
|
| `--provider <name>` | 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 |
|
||||||
| `--no-refresh` | switch | no | Skip refreshing state from remote before planning |
|
| `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) |
|
||||||
| `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) |
|
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||||
| `--api-key <key>` | string | no | API key |
|
| `--api-key <key>` | string | no | API key |
|
||||||
| `--base-url <url>` | string | no | API base URL |
|
| `--base-url <url>` | string | no | API base URL |
|
||||||
|
|
||||||
#### Notes
|
#### Notes
|
||||||
|
|
||||||
@@ -68,20 +68,20 @@ bl managed-agent apply --provider bailian --yes
|
|||||||
|
|
||||||
### `bl managed-agent destroy`
|
### `bl managed-agent destroy`
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| ------------------ | -------------------------------------------------------------- |
|
| ------------------ | ------------------------------------------------------ |
|
||||||
| **Name** | `managed-agent destroy` |
|
| **Name** | `managed-agent destroy` |
|
||||||
| **Description** | Destroy all managed agent resources tracked in state |
|
| **Description** | Destroy all managed agent resources tracked in state |
|
||||||
| **Authentication** | API Key |
|
| **Authentication** | API Key |
|
||||||
| **Usage** | `bl managed-agent destroy [--file <path>] [--yes] [--cascade]` |
|
| **Usage** | `bl managed-agent destroy [--file <path>] [--cascade]` |
|
||||||
|
|
||||||
#### Flags
|
#### Flags
|
||||||
|
|
||||||
| Flag | Type | Required | Description |
|
| Flag | Type | Required | Description |
|
||||||
| ------------------ | ------ | -------- | -------------------------------------------------------------------------- |
|
| ------------------ | ------ | -------- | -------------------------------------------------------------------------- |
|
||||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
| `--file <path>` | 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) |
|
| `--cascade` | switch | no | Auto-delete dependent resources (e.g. sessions referencing an environment) |
|
||||||
|
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||||
| `--api-key <key>` | string | no | API key |
|
| `--api-key <key>` | string | no | API key |
|
||||||
| `--base-url <url>` | string | no | API base URL |
|
| `--base-url <url>` | string | no | API base URL |
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user