mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
refactor(knowledge): migrate high-risk commands to runtime confirmation
This commit is contained in:
@@ -213,7 +213,7 @@ bl knowledge chunk delete --index-id <id> --chunk-id <id> [flags]
|
||||
| ----------------- | ------ | ---- | ------------------------------------------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--chunk-id <id>` | array | 是 | Chunk ID(可重复,每批最多 10 个,超出自动分批) |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
@@ -232,10 +232,10 @@ bl knowledge category delete --category-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| -------------------- | ------ | ---- | ------------ |
|
||||
| `--category-id <id>` | string | 是 | 分类 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| -------------------- | ------ | ---- | ---------------------- |
|
||||
| `--category-id <id>` | string | 是 | 分类 ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
@@ -120,10 +120,10 @@ bl knowledge file delete --file-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | ------ | ---- | --------------- |
|
||||
| `--file-id <id>` | string | 是 | 数据中心文件 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | ------ | ---- | ---------------------- |
|
||||
| `--file-id <id>` | string | 是 | 数据中心文件 ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
@@ -254,10 +254,10 @@ bl knowledge delete --index-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ---------------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
@@ -275,7 +275,7 @@ json 模式:返回 API 原始响应。
|
||||
|
||||
- **不可逆操作**:知识库及所有索引内容被永久删除。
|
||||
- 数据中心中的源文件不受影响,仅删除知识库索引。
|
||||
- 不带 `--yes` 时,CLI 会先查询知识库名称和文档数量作为确认摘要。
|
||||
- 不带 `--yes` 时,runtime 会在调用知识库业务 API、执行删除前返回确认请求。
|
||||
|
||||
**示例**
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
|
||||
### 危险操作确认
|
||||
|
||||
涉及删除的命令(`kb delete`、`doc delete`、`chunk delete`、`file delete`、`category delete`、`service delete`、`service deploy`)在执行前会弹出二次确认提示。使用 `--yes` 可跳过确认,适用于自动化脚本。
|
||||
涉及删除的命令(`kb delete`、`doc delete`、`chunk delete`、`file delete`、`category delete`、`service delete`、`service deploy`)属于高风险操作。未带 `--yes` 时 CLI 不会执行,也不会弹出交互式 Y/N,而是返回 exit code 7 和 `requires_confirmation`;确认后在原命令中添加 `--yes` 重新执行。
|
||||
|
||||
### Dry-run 模式
|
||||
|
||||
|
||||
@@ -270,11 +270,11 @@ bl knowledge service deploy --agent-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ------ | ---- | ---------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--version-desc <text>` | string | 否 | 新版本的描述说明 |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ------ | ---- | ---------------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--version-desc <text>` | string | 否 | 新版本的描述说明 |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
@@ -319,10 +319,10 @@ bl knowledge service delete --agent-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | --------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ---------------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
+1
-1
@@ -213,7 +213,7 @@ kscli chunk delete --index-id <id> --chunk-id <id> [flags]
|
||||
| ----------------- | ------ | ---- | ------------------------------------------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--chunk-id <id>` | array | 是 | Chunk ID(可重复,每批最多 10 个,超出自动分批) |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
@@ -232,10 +232,10 @@ kscli category delete --category-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| -------------------- | ------ | ---- | ------------ |
|
||||
| `--category-id <id>` | string | 是 | 分类 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| -------------------- | ------ | ---- | ---------------------- |
|
||||
| `--category-id <id>` | string | 是 | 分类 ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
+4
-4
@@ -120,10 +120,10 @@ kscli file delete --file-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | ------ | ---- | --------------- |
|
||||
| `--file-id <id>` | string | 是 | 数据中心文件 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | ------ | ---- | ---------------------- |
|
||||
| `--file-id <id>` | string | 是 | 数据中心文件 ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
+5
-5
@@ -254,10 +254,10 @@ kscli kb delete --index-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ---------------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
@@ -275,7 +275,7 @@ json 模式:返回 API 原始响应。
|
||||
|
||||
- **不可逆操作**:知识库及所有索引内容被永久删除。
|
||||
- 数据中心中的源文件不受影响,仅删除知识库索引。
|
||||
- 不带 `--yes` 时,CLI 会先查询知识库名称和文档数量作为确认摘要。
|
||||
- 不带 `--yes` 时,runtime 会在调用知识库业务 API、执行删除前返回确认请求。
|
||||
|
||||
**示例**
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ kscli --help
|
||||
|
||||
### 危险操作确认
|
||||
|
||||
涉及删除的命令(`kb delete`、`doc delete`、`chunk delete`、`file delete`、`category delete`、`service delete`)以及 `service deploy` 在执行前会弹出二次确认提示。使用 `--yes` 可跳过确认,适用于自动化脚本。
|
||||
涉及删除的命令(`kb delete`、`doc delete`、`chunk delete`、`file delete`、`category delete`、`service delete`)以及 `service deploy` 属于高风险操作。未带 `--yes` 时 CLI 不会执行,也不会弹出交互式 Y/N,而是返回 exit code 7 和 `requires_confirmation`;确认后在原命令中添加 `--yes` 重新执行。
|
||||
|
||||
### Dry-run 模式
|
||||
|
||||
|
||||
@@ -270,11 +270,11 @@ kscli service deploy --agent-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ------ | ---- | ---------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--version-desc <text>` | string | 否 | 新版本的描述说明 |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ------ | ---- | ---------------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--version-desc <text>` | string | 否 | 新版本的描述说明 |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
@@ -319,10 +319,10 @@ kscli service delete --agent-id <id> [flags]
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | --------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ---------------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--yes` | switch | 否 | 显式确认执行高风险操作 |
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
@@ -116,9 +116,9 @@
|
||||
|
||||
**Flags**:`--index-id` 必填;`--doc-id` array 必填(可重复);`--yes`。
|
||||
|
||||
**实现方案**:`doc-delete.ts`;确认摘要含 index_id + doc_id 列表(≤5 个全列,超出显示前 5 + 总数);输出以 `data.deleted` 为准(与入参数量不一致时 text 模式警告差异)。
|
||||
**实现方案**:`doc-delete.ts`;命令在 `risk` 对象中同时声明 `level: "high"` 和双语 `message`,由 runtime 在 `run()` 前统一确认;输出以 `data.deleted` 为准(与入参数量不一致时 text 模式警告差异)。
|
||||
|
||||
**测试方案**:help / 缺参×2 / dry-run 断言 `doc_ids` 数组 / 非 TTY 无 `--yes` exitCode 2 / live 配合 upload 清理链。
|
||||
**测试方案**:help / 缺参×2 / dry-run 断言 `doc_ids` 数组 / 无 `--yes` 返回 exitCode 7 + `requires_confirmation` / live 配合 upload 清理链。
|
||||
|
||||
## doc tag — 批量更新文档标签
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type FlagsDef,
|
||||
type RagConnectorResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
|
||||
|
||||
const CATEGORY_DELETE_FLAGS = {
|
||||
@@ -16,16 +16,19 @@ const CATEGORY_DELETE_FLAGS = {
|
||||
description: { "en-US": "Category ID to delete", "zh-CN": "要删除的类目 ID" },
|
||||
required: true,
|
||||
},
|
||||
yes: {
|
||||
type: "switch",
|
||||
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
|
||||
},
|
||||
...WORKSPACE_FLAG,
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export default defineCommand({
|
||||
description: { "en-US": "Delete a data-center category", "zh-CN": "删除数据中心类目" },
|
||||
auth: "apiKey",
|
||||
risk: {
|
||||
level: "high",
|
||||
message: {
|
||||
"en-US": "This deletes the selected data-center category and cannot be undone.",
|
||||
"zh-CN": "该操作会删除所选数据中心类目,且无法撤销。",
|
||||
},
|
||||
},
|
||||
usageArgs: "--category-id <id> [flags]",
|
||||
flags: CATEGORY_DELETE_FLAGS,
|
||||
notes: [
|
||||
@@ -49,11 +52,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
await confirmDangerousAction(
|
||||
`Delete category ${flags.categoryId}\nThis cannot be undone.`,
|
||||
flags.yes ?? false,
|
||||
);
|
||||
|
||||
const response = await ctx.client.requestJson<
|
||||
RagConnectorResponse<Record<string, unknown> | undefined>
|
||||
>({
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type FlagsDef,
|
||||
type RagMutationResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
|
||||
|
||||
const CHUNK_DELETE_FLAGS = {
|
||||
@@ -26,10 +26,6 @@ const CHUNK_DELETE_FLAGS = {
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
yes: {
|
||||
type: "switch",
|
||||
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
|
||||
},
|
||||
...WORKSPACE_FLAG,
|
||||
} satisfies FlagsDef;
|
||||
|
||||
@@ -48,6 +44,13 @@ export default defineCommand({
|
||||
"zh-CN": "从知识库中删除 Chunk(不可撤销)",
|
||||
},
|
||||
auth: "apiKey",
|
||||
risk: {
|
||||
level: "high",
|
||||
message: {
|
||||
"en-US": "This permanently deletes the selected chunks and cannot be undone.",
|
||||
"zh-CN": "该操作会永久删除所选 Chunk,且无法撤销。",
|
||||
},
|
||||
},
|
||||
usageArgs: "--index-id <id> --chunk-id <id> [flags]",
|
||||
flags: CHUNK_DELETE_FLAGS,
|
||||
notes: [
|
||||
@@ -81,11 +84,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
await confirmDangerousAction(
|
||||
`Delete ${flags.chunkId.length} chunk(s) from knowledge base ${flags.indexId} in ${batches.length} batch(es).\nChunks are permanently removed. This cannot be undone.`,
|
||||
flags.yes ?? false,
|
||||
);
|
||||
|
||||
// Sequential batches; any batch failure aborts, listing already-deleted batches in the error
|
||||
let deletedCount = 0;
|
||||
for (const batchIds of batches) {
|
||||
|
||||
@@ -3,12 +3,10 @@ import {
|
||||
ragEndpoint,
|
||||
RAG_PATHS,
|
||||
detectOutputFormat,
|
||||
type Client,
|
||||
type FlagsDef,
|
||||
type RagConnectorResponse,
|
||||
type RagDescribeFileResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
|
||||
|
||||
const FILE_DELETE_FLAGS = {
|
||||
@@ -21,39 +19,23 @@ const FILE_DELETE_FLAGS = {
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
yes: {
|
||||
type: "switch",
|
||||
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
|
||||
},
|
||||
...WORKSPACE_FLAG,
|
||||
} satisfies FlagsDef;
|
||||
|
||||
/** Confirmation summary lookup (file name/size); failure degrades to id-only */
|
||||
async function buildDeleteSummary(
|
||||
client: Client,
|
||||
workspaceId: string,
|
||||
fileId: string,
|
||||
): Promise<string> {
|
||||
let infoPart = "";
|
||||
try {
|
||||
const detail = await client.requestJson<RagDescribeFileResponse>({
|
||||
path: ragEndpoint(workspaceId, RAG_PATHS.describeFile),
|
||||
method: "POST",
|
||||
body: { fileId },
|
||||
});
|
||||
if (detail.data?.fileName) infoPart = ` name: ${detail.data.fileName}`;
|
||||
} catch {
|
||||
// Degrade gracefully: a failed lookup does not block confirmation
|
||||
}
|
||||
return `Delete data-center file ${fileId}${infoPart}\nPERMANENT: if the file is referenced by knowledge bases, their document indexes break too. This differs from removing a document from one knowledge base.`;
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US": "Permanently delete a file from the data center",
|
||||
"zh-CN": "从数据中心永久删除文件",
|
||||
},
|
||||
auth: "apiKey",
|
||||
risk: {
|
||||
level: "high",
|
||||
message: {
|
||||
"en-US":
|
||||
"This permanently deletes the data-center file. Knowledge-base document indexes that reference it may become invalid.",
|
||||
"zh-CN": "该操作会永久删除数据中心文件;引用该文件的知识库文档索引可能失效。",
|
||||
},
|
||||
},
|
||||
usageArgs: "--file-id <id> [flags]",
|
||||
flags: FILE_DELETE_FLAGS,
|
||||
notes: [
|
||||
@@ -82,11 +64,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = flags.yes
|
||||
? ""
|
||||
: await buildDeleteSummary(ctx.client, workspaceId, flags.fileId);
|
||||
await confirmDangerousAction(summary, flags.yes ?? false);
|
||||
|
||||
const response = await ctx.client.requestJson<
|
||||
RagConnectorResponse<Record<string, unknown> | undefined>
|
||||
>({
|
||||
|
||||
@@ -4,12 +4,10 @@ import {
|
||||
RAG_PATHS,
|
||||
detectOutputFormat,
|
||||
type FlagsDef,
|
||||
type RagIndexFilesResponse,
|
||||
type RagMutationResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
|
||||
import { fetchIndexDetail } from "./kb-info.ts";
|
||||
|
||||
const KB_DELETE_FLAGS = {
|
||||
indexId: {
|
||||
@@ -18,50 +16,23 @@ const KB_DELETE_FLAGS = {
|
||||
description: { "en-US": "Knowledge base ID", "zh-CN": "知识库 ID" },
|
||||
required: true,
|
||||
},
|
||||
yes: {
|
||||
type: "switch",
|
||||
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
|
||||
},
|
||||
...WORKSPACE_FLAG,
|
||||
} satisfies FlagsDef;
|
||||
|
||||
/** Confirmation summary lookup: name + document count; any lookup failure degrades to id-only (never blocks deletion) */
|
||||
async function buildDeleteSummary(
|
||||
ctx: { client: Parameters<typeof fetchIndexDetail>[0] },
|
||||
workspaceId: string,
|
||||
indexId: string,
|
||||
): Promise<string> {
|
||||
let namePart = "";
|
||||
let docCountPart = "";
|
||||
try {
|
||||
const detail = await fetchIndexDetail(ctx.client, workspaceId, indexId);
|
||||
namePart = ` name: ${detail.name}`;
|
||||
} catch {
|
||||
// Degrade gracefully: a missing name does not block confirmation
|
||||
}
|
||||
try {
|
||||
const filesUrl = new URL(ragEndpoint(workspaceId, RAG_PATHS.indexFiles));
|
||||
filesUrl.searchParams.set("index_id", indexId);
|
||||
filesUrl.searchParams.set("page_num", "1");
|
||||
filesUrl.searchParams.set("page_size", "1");
|
||||
const files = await ctx.client.requestJson<RagIndexFilesResponse>({
|
||||
path: filesUrl.toString(),
|
||||
method: "GET",
|
||||
});
|
||||
const totalCount = files.data?.total_count;
|
||||
if (typeof totalCount === "number") docCountPart = ` documents: ${totalCount}`;
|
||||
} catch {
|
||||
// Same graceful degradation as above
|
||||
}
|
||||
return `Delete knowledge base ${indexId}${namePart}${docCountPart}\nThis permanently removes the knowledge base with all documents and chunks. It cannot be undone.`;
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US": "Delete a knowledge base with all its documents and chunks",
|
||||
"zh-CN": "删除知识库及其所有文档和 Chunk",
|
||||
},
|
||||
auth: "apiKey",
|
||||
risk: {
|
||||
level: "high",
|
||||
message: {
|
||||
"en-US":
|
||||
"This permanently deletes the knowledge base and all of its documents and chunks. Data-center files are not deleted.",
|
||||
"zh-CN": "该操作会永久删除知识库及其全部文档和 Chunk,但不会删除数据中心文件。",
|
||||
},
|
||||
},
|
||||
usageArgs: "--index-id <id> [flags]",
|
||||
flags: KB_DELETE_FLAGS,
|
||||
notes: [
|
||||
@@ -90,11 +61,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = flags.yes
|
||||
? "" // --yes bypasses the prompt, so skip the summary lookups
|
||||
: await buildDeleteSummary(ctx, workspaceId, flags.indexId);
|
||||
await confirmDangerousAction(summary, flags.yes ?? false);
|
||||
|
||||
const response = await ctx.client.requestJson<RagMutationResponse>({
|
||||
path: endpoint,
|
||||
method: "POST",
|
||||
|
||||
@@ -3,12 +3,10 @@ import {
|
||||
ragEndpoint,
|
||||
RAG_PATHS,
|
||||
detectOutputFormat,
|
||||
type Client,
|
||||
type FlagsDef,
|
||||
type RagAgentGetResponse,
|
||||
type RagAgentMutationResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { agentMutationField, resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
|
||||
|
||||
const SERVICE_DELETE_FLAGS = {
|
||||
@@ -18,48 +16,23 @@ const SERVICE_DELETE_FLAGS = {
|
||||
description: { "en-US": "Service (agent) ID", "zh-CN": "服务(Agent)ID" },
|
||||
required: true,
|
||||
},
|
||||
yes: {
|
||||
type: "switch",
|
||||
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
|
||||
},
|
||||
...WORKSPACE_FLAG,
|
||||
} satisfies FlagsDef;
|
||||
|
||||
/** Confirmation summary lookup (name/status); failure degrades to id-only */
|
||||
async function buildDeleteSummary(
|
||||
client: Client,
|
||||
workspaceId: string,
|
||||
agentId: string,
|
||||
): Promise<string> {
|
||||
let infoPart = "";
|
||||
let liveWarning = "";
|
||||
try {
|
||||
const detail = await client.requestJson<RagAgentGetResponse>({
|
||||
path: ragEndpoint(workspaceId, RAG_PATHS.agentGet),
|
||||
method: "POST",
|
||||
body: { agent_id: agentId },
|
||||
});
|
||||
const name = detail.data?.agent_name;
|
||||
const status = detail.data?.agent_status;
|
||||
if (name) infoPart += ` name: ${name}`;
|
||||
if (status) {
|
||||
infoPart += ` status: ${status}`;
|
||||
if (status === "deployed" || status === "edited") {
|
||||
liveWarning = "\nWARNING: this service is LIVE — deleting it breaks existing callers.";
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Degrade gracefully: a failed lookup does not block confirmation
|
||||
}
|
||||
return `Delete service ${agentId}${infoPart}${liveWarning}\nDeletion cannot be undone; the agent_id can no longer be used for search or chat calls.`;
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US": "Delete a retrieval / Q&A service (soft delete, idempotent)",
|
||||
"zh-CN": "删除检索/问答服务(软删除,幂等)",
|
||||
},
|
||||
auth: "apiKey",
|
||||
risk: {
|
||||
level: "high",
|
||||
message: {
|
||||
"en-US":
|
||||
"This deletes the service and makes its agent ID unavailable for search and chat calls. The operation cannot be undone.",
|
||||
"zh-CN": "该操作会删除服务,使其 Agent ID 无法再用于搜索和对话调用,且无法撤销。",
|
||||
},
|
||||
},
|
||||
usageArgs: "--agent-id <id> [flags]",
|
||||
flags: SERVICE_DELETE_FLAGS,
|
||||
notes: [
|
||||
@@ -91,11 +64,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = flags.yes
|
||||
? ""
|
||||
: await buildDeleteSummary(ctx.client, workspaceId, flags.agentId);
|
||||
await confirmDangerousAction(summary, flags.yes ?? false);
|
||||
|
||||
const response = await ctx.client.requestJson<RagAgentMutationResponse>({
|
||||
path: endpoint,
|
||||
method: "POST",
|
||||
|
||||
@@ -3,12 +3,10 @@ import {
|
||||
ragEndpoint,
|
||||
RAG_PATHS,
|
||||
detectOutputFormat,
|
||||
type Client,
|
||||
type FlagsDef,
|
||||
type RagAgentGetResponse,
|
||||
type RagAgentMutationResponse,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import { agentMutationField, resolveWorkspaceId, WORKSPACE_FLAG } from "./shared.ts";
|
||||
|
||||
const SERVICE_DEPLOY_FLAGS = {
|
||||
@@ -26,49 +24,23 @@ const SERVICE_DEPLOY_FLAGS = {
|
||||
"zh-CN": "新发布版本的描述",
|
||||
},
|
||||
},
|
||||
yes: {
|
||||
type: "switch",
|
||||
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
|
||||
},
|
||||
...WORKSPACE_FLAG,
|
||||
} satisfies FlagsDef;
|
||||
|
||||
/** Confirmation summary lookup (name/status); warns that deploying an edited draft overwrites live behavior; failure degrades to id-only */
|
||||
async function buildDeploySummary(
|
||||
client: Client,
|
||||
workspaceId: string,
|
||||
agentId: string,
|
||||
): Promise<string> {
|
||||
let infoPart = "";
|
||||
let editedWarning = "";
|
||||
try {
|
||||
const detail = await client.requestJson<RagAgentGetResponse>({
|
||||
path: ragEndpoint(workspaceId, RAG_PATHS.agentGet),
|
||||
method: "POST",
|
||||
body: { agent_id: agentId },
|
||||
});
|
||||
const name = detail.data?.agent_name;
|
||||
const status = detail.data?.agent_status;
|
||||
if (name) infoPart += ` name: ${name}`;
|
||||
if (status) {
|
||||
infoPart += ` status: ${status}`;
|
||||
if (status === "edited") {
|
||||
editedWarning =
|
||||
"\nWARNING: a published version is live — deploying replaces its behavior with the current draft.";
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Degrade gracefully: a failed lookup does not block confirmation
|
||||
}
|
||||
return `Deploy service ${agentId}${infoPart}${editedWarning}\nPublishing changes what live callers get from this service.`;
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US": "Publish the beta draft of a service as a new version",
|
||||
"zh-CN": "将服务的 beta 草稿发布为新版本",
|
||||
},
|
||||
auth: "apiKey",
|
||||
risk: {
|
||||
level: "high",
|
||||
message: {
|
||||
"en-US":
|
||||
"This publishes the current draft as a new version and changes the behavior seen by live callers.",
|
||||
"zh-CN": "该操作会将当前草稿发布为新版本,并改变线上调用方使用的服务行为。",
|
||||
},
|
||||
},
|
||||
usageArgs: "--agent-id <id> [flags]",
|
||||
flags: SERVICE_DEPLOY_FLAGS,
|
||||
notes: [
|
||||
@@ -109,11 +81,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = flags.yes
|
||||
? ""
|
||||
: await buildDeploySummary(ctx.client, workspaceId, flags.agentId);
|
||||
await confirmDangerousAction(summary, flags.yes ?? false);
|
||||
|
||||
const response = await ctx.client.requestJson<RagAgentMutationResponse>({
|
||||
path: endpoint,
|
||||
method: "POST",
|
||||
|
||||
@@ -433,7 +433,7 @@ describe("e2e: knowledge chunk 组 (静态)", () => {
|
||||
expect(data.batches[1]!.request.chunkIds).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("chunk delete: 非 TTY 无 --yes 报 USAGE (2)", async () => {
|
||||
test("chunk delete: 无 --yes 返回确认请求 (7)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
|
||||
"knowledge",
|
||||
"chunk",
|
||||
@@ -446,9 +446,13 @@ describe("e2e: knowledge chunk 组 (静态)", () => {
|
||||
"sk-fake",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--yes/);
|
||||
expect(exitCode).toBe(7);
|
||||
expect(JSON.parse(stderr)).toMatchObject({
|
||||
error: { code: 7, type: "requires_confirmation" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -549,7 +553,22 @@ describe("e2e: kb stats / category / file / connector / import-oss (静态)", ()
|
||||
expect(data.request?.connectorId).toBe("conn_test");
|
||||
});
|
||||
|
||||
test("category delete: 非 TTY 无 --yes 报 USAGE (2)", async () => {
|
||||
test("category delete: dry-run 断言 categoryId", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
|
||||
"knowledge",
|
||||
"category",
|
||||
"delete",
|
||||
"--category-id",
|
||||
"cate_test",
|
||||
...COMMON,
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<DryRunBody>(stdout);
|
||||
expect(data.endpoint).toMatch(/deleteCategory/);
|
||||
expect(data.request?.categoryId).toBe("cate_test");
|
||||
});
|
||||
|
||||
test("category delete: 无 --yes 返回确认请求 (7)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
|
||||
"knowledge",
|
||||
"category",
|
||||
@@ -560,9 +579,13 @@ describe("e2e: kb stats / category / file / connector / import-oss (静态)", ()
|
||||
"sk-fake",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--yes/);
|
||||
expect(exitCode).toBe(7);
|
||||
expect(JSON.parse(stderr)).toMatchObject({
|
||||
error: { code: 7, type: "requires_confirmation" },
|
||||
});
|
||||
});
|
||||
|
||||
test("file list: 缺 --category-id 报 USAGE (2)", async () => {
|
||||
@@ -618,7 +641,22 @@ describe("e2e: kb stats / category / file / connector / import-oss (静态)", ()
|
||||
expect(data.request?.fileId).toBe("file_test");
|
||||
});
|
||||
|
||||
test("file delete: 非 TTY 无 --yes 报 USAGE (2)", async () => {
|
||||
test("file delete: dry-run 断言 fileId", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
|
||||
"knowledge",
|
||||
"file",
|
||||
"delete",
|
||||
"--file-id",
|
||||
"file_test",
|
||||
...COMMON,
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<DryRunBody>(stdout);
|
||||
expect(data.endpoint).toMatch(/deleteFile/);
|
||||
expect(data.request?.fileId).toBe("file_test");
|
||||
});
|
||||
|
||||
test("file delete: 无 --yes 返回确认请求 (7)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_CHUNK_CATEGORY_FILE_ROUTES, [
|
||||
"knowledge",
|
||||
"file",
|
||||
@@ -629,9 +667,13 @@ describe("e2e: kb stats / category / file / connector / import-oss (静态)", ()
|
||||
"sk-fake",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--yes/);
|
||||
expect(exitCode).toBe(7);
|
||||
expect(JSON.parse(stderr)).toMatchObject({
|
||||
error: { code: 7, type: "requires_confirmation" },
|
||||
});
|
||||
});
|
||||
|
||||
test("collection create: --name 21 字符 USAGE (2)", async () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("e2e: knowledge kb delete", () => {
|
||||
expect(data.request?.index_id).toBe("idx_test");
|
||||
});
|
||||
|
||||
test("非 TTY 无 --yes 报 USAGE (2)", async () => {
|
||||
test("无 --yes 返回确认请求 (7)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_KB_DELETE_ROUTES, [
|
||||
"knowledge",
|
||||
"delete",
|
||||
@@ -58,9 +58,17 @@ describe("e2e: knowledge kb delete", () => {
|
||||
"sk-fake",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--yes/);
|
||||
expect(exitCode).toBe(7);
|
||||
expect(JSON.parse(stderr)).toMatchObject({
|
||||
error: {
|
||||
code: 7,
|
||||
type: "requires_confirmation",
|
||||
hint: expect.stringContaining("--yes"),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -454,7 +454,7 @@ describe("e2e: knowledge service update", () => {
|
||||
});
|
||||
|
||||
describe("e2e: knowledge service deploy / delete (危险)", () => {
|
||||
test("deploy: 非 TTY 无 --yes 报 USAGE (2)", async () => {
|
||||
test("deploy: 无 --yes 返回确认请求 (7)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SERVICE_ROUTES, [
|
||||
"knowledge",
|
||||
"service",
|
||||
@@ -465,9 +465,13 @@ describe("e2e: knowledge service deploy / delete (危险)", () => {
|
||||
"sk-fake",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--yes/);
|
||||
expect(exitCode).toBe(7);
|
||||
expect(JSON.parse(stderr)).toMatchObject({
|
||||
error: { code: 7, type: "requires_confirmation" },
|
||||
});
|
||||
});
|
||||
|
||||
test("deploy: --dry-run 断言 body", async () => {
|
||||
@@ -491,7 +495,7 @@ describe("e2e: knowledge service deploy / delete (危险)", () => {
|
||||
expect(data.request?.agent_version_desc).toBe("v1 desc");
|
||||
});
|
||||
|
||||
test("delete: 非 TTY 无 --yes 报 USAGE (2)", async () => {
|
||||
test("delete: 无 --yes 返回确认请求 (7)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SERVICE_ROUTES, [
|
||||
"knowledge",
|
||||
"service",
|
||||
@@ -502,9 +506,32 @@ describe("e2e: knowledge service deploy / delete (危险)", () => {
|
||||
"sk-fake",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--yes/);
|
||||
expect(exitCode).toBe(7);
|
||||
expect(JSON.parse(stderr)).toMatchObject({
|
||||
error: { code: 7, type: "requires_confirmation" },
|
||||
});
|
||||
});
|
||||
|
||||
test("delete: --dry-run 断言 body", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(KNOWLEDGE_SERVICE_ROUTES, [
|
||||
"knowledge",
|
||||
"service",
|
||||
"delete",
|
||||
"--agent-id",
|
||||
"aid_test",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<DryRunBody>(stdout);
|
||||
expect(data.endpoint).toMatch(/rag\/app\/delete/);
|
||||
expect(data.request?.agent_id).toBe("aid_test");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -79,6 +79,13 @@ function isLocalizedText(value: unknown): value is LocalizedText {
|
||||
);
|
||||
}
|
||||
|
||||
function isCommandRisk(value: unknown): boolean {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
||||
|
||||
const risk = value as Record<string, unknown>;
|
||||
return risk.level === "high" && isLocalizedText(risk.message);
|
||||
}
|
||||
|
||||
function assertCommand(path: string, value: unknown): asserts value is CommandPackCommand<any> {
|
||||
if (!value || typeof value !== "object") {
|
||||
throw new Error(`Command "${path}" must export an object.`);
|
||||
@@ -90,8 +97,8 @@ function assertCommand(path: string, value: unknown): asserts value is CommandPa
|
||||
if (!command.auth || !AUTH_REQUIREMENTS.has(command.auth)) {
|
||||
throw new Error(`Command "${path}" has an invalid auth requirement.`);
|
||||
}
|
||||
if (command.risk !== undefined && !isLocalizedText(command.risk.message)) {
|
||||
throw new Error(`Command "${path}" has an invalid risk message.`);
|
||||
if (command.risk !== undefined && !isCommandRisk(command.risk)) {
|
||||
throw new Error(`Command "${path}" has invalid risk metadata.`);
|
||||
}
|
||||
if (typeof command.run !== "function") {
|
||||
throw new Error(`Command "${path}" is missing run(ctx).`);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import {
|
||||
BailianError,
|
||||
ExitCode,
|
||||
@@ -22,39 +21,6 @@ export function confirmationFlagDefs(command: { risk?: CommandRisk }): FlagsDef
|
||||
return command.risk === undefined ? {} : CONFIRMATION_FLAGS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitional compatibility for commands that have not moved to command-level
|
||||
* risk metadata yet.
|
||||
* TODO(next commit): migrate every remaining caller to command-level risk metadata, then
|
||||
* remove this helper and update their confirmation wording/examples together.
|
||||
*
|
||||
* @deprecated Declare command-level risk metadata and let runtime gate confirmation.
|
||||
*/
|
||||
export async function confirmDangerousAction(summary: string, yes: boolean): Promise<void> {
|
||||
if (yes) return;
|
||||
if (!process.stdin.isTTY) {
|
||||
throw new BailianError(
|
||||
"Confirmation required for this destructive action.",
|
||||
ExitCode.USAGE,
|
||||
"Re-run with --yes to confirm in non-interactive mode",
|
||||
);
|
||||
}
|
||||
process.stderr.write(`${summary}\n`);
|
||||
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
||||
try {
|
||||
const answer = (await readline.question("Proceed? [y/N] ")).trim().toLowerCase();
|
||||
if (answer !== "y" && answer !== "yes") {
|
||||
process.stderr.write("Cancelled.\n");
|
||||
// Intentional: a user-initiated cancellation is not an error, and we want
|
||||
// to exit here rather than unwind through the middleware stack (which
|
||||
// would still print a success report for an action that did not happen).
|
||||
process.exit(ExitCode.SUCCESS);
|
||||
}
|
||||
} finally {
|
||||
readline.close();
|
||||
}
|
||||
}
|
||||
|
||||
export function confirmationHint(): LocalizedText {
|
||||
return {
|
||||
"en-US":
|
||||
|
||||
@@ -63,7 +63,6 @@ export {
|
||||
|
||||
// Utility facilities consumed by commands
|
||||
export { poll } from "./utils/polling.ts";
|
||||
export { confirmDangerousAction } from "./confirm.ts";
|
||||
export { downloadFile, formatBytes } from "./utils/download.ts";
|
||||
export { runConcurrent, getConcurrency, downloadParallel } from "./utils/concurrent.ts";
|
||||
export { resolveImageSize } from "./utils/image-size.ts";
|
||||
|
||||
@@ -163,6 +163,42 @@ test("loads an API 1 Command Pack and preserves its command contract", async ()
|
||||
expect(commands["agent ping"]?.flags?.message).toMatchObject({ required: true, type: "string" });
|
||||
});
|
||||
|
||||
test.each([
|
||||
["null", null],
|
||||
["a non-object value", "high"],
|
||||
["a missing level", { message: "Dangerous operation." }],
|
||||
["an unsupported level", { level: "low", message: "Dangerous operation." }],
|
||||
["an invalid message", { level: "high", message: "" }],
|
||||
])("rejects Command Pack risk metadata with %s", async (_caseName, risk) => {
|
||||
const root = await mkdtemp(join(tmpdir(), "command-pack-risk-test-"));
|
||||
|
||||
try {
|
||||
await writeFile(
|
||||
join(root, "commands.mjs"),
|
||||
`export default {
|
||||
"agent dangerous": {
|
||||
description: "Dangerous command",
|
||||
auth: "none",
|
||||
risk: ${JSON.stringify(risk)},
|
||||
async run() {},
|
||||
},
|
||||
};\n`,
|
||||
);
|
||||
|
||||
await expect(
|
||||
loadAndValidateCommandPack(
|
||||
"@ali/bailian-plugin-agent",
|
||||
packageJson,
|
||||
policy.supported["@ali/bailian-plugin-agent"]!,
|
||||
identity,
|
||||
root,
|
||||
),
|
||||
).rejects.toThrow(/invalid risk/);
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects incompatible protocol versions and invalid command prefixes", async () => {
|
||||
await expect(
|
||||
loadAndValidateCommandPack(
|
||||
|
||||
@@ -1,32 +1,8 @@
|
||||
import { afterEach, describe, expect, test, vi } from "vite-plus/test";
|
||||
import { describe, expect, test, vi } from "vite-plus/test";
|
||||
import { defineCommand, ExitCode, type CommandRisk, type LocalizedText } from "bailian-cli-core";
|
||||
import {
|
||||
ConfirmationRequiredError,
|
||||
confirmDangerousAction,
|
||||
confirmationFlagDefs,
|
||||
} from "../src/confirm.ts";
|
||||
import { ConfirmationRequiredError, confirmationFlagDefs } from "../src/confirm.ts";
|
||||
import { confirmationStage, type RunContext } from "../src/middleware.ts";
|
||||
|
||||
const readlineMocks = vi.hoisted(() => {
|
||||
const question = vi.fn(async () => "y");
|
||||
const close = vi.fn();
|
||||
return {
|
||||
question,
|
||||
close,
|
||||
createInterface: vi.fn(() => ({ question, close })),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("node:readline/promises", () => ({ createInterface: readlineMocks.createInterface }));
|
||||
|
||||
const originalIsTTY = process.stdin.isTTY;
|
||||
|
||||
afterEach(() => {
|
||||
process.stdin.isTTY = originalIsTTY;
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const HIGH_RISK_MESSAGE = {
|
||||
"en-US": "This permanently deletes the document and its chunks.",
|
||||
"zh-CN": "该操作会永久删除文档及其 Chunk,且无法撤销。",
|
||||
@@ -83,26 +59,6 @@ describe("confirmation metadata", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("legacy commands fail closed without opening a TTY prompt", async () => {
|
||||
process.stdin.isTTY = false;
|
||||
await expect(confirmDangerousAction("legacy summary", false)).rejects.toMatchObject({
|
||||
exitCode: ExitCode.USAGE,
|
||||
});
|
||||
await expect(confirmDangerousAction("legacy summary", true)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("legacy commands retain their existing TTY confirmation during migration", async () => {
|
||||
process.stdin.isTTY = true;
|
||||
const stderrWrite = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
||||
|
||||
await expect(confirmDangerousAction("legacy summary", false)).resolves.toBeUndefined();
|
||||
|
||||
expect(stderrWrite).toHaveBeenCalledWith("legacy summary\n");
|
||||
expect(readlineMocks.createInterface).toHaveBeenCalledOnce();
|
||||
expect(readlineMocks.question).toHaveBeenCalledWith("Proceed? [y/N] ");
|
||||
expect(readlineMocks.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe("confirmationStage", () => {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { expect, test } from "vite-plus/test";
|
||||
import * as runtimeApi from "../src/index.ts";
|
||||
|
||||
test("confirmation orchestration stays internal to createCli", () => {
|
||||
expect(runtimeApi).toHaveProperty("confirmDangerousAction");
|
||||
expect(runtimeApi).not.toHaveProperty("confirmationStage");
|
||||
expect(runtimeApi).not.toHaveProperty("CONFIRMATION_FLAGS");
|
||||
expect(runtimeApi).not.toHaveProperty("confirmationFlagDefs");
|
||||
expect(runtimeApi).not.toHaveProperty("ConfirmationRequiredError");
|
||||
expect(runtimeApi.CommandRegistry.prototype).not.toHaveProperty("localizeText");
|
||||
});
|
||||
@@ -94,8 +94,8 @@ bl knowledge category add --name sub --parent-id cate-xxx
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | --------------------------------------------------------------- |
|
||||
| `--category-id <id>` | string | yes | Category ID to delete |
|
||||
| `--yes` | switch | no | Skip the confirmation prompt |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
@@ -255,8 +255,8 @@ bl knowledge chunk add --index-id idx-xxx --field columnA=v1 --field columnB=v2
|
||||
| --------------------- | ------ | -------- | --------------------------------------------------------------------- |
|
||||
| `--index-id <id>` | string | yes | Knowledge base ID |
|
||||
| `--chunk-id <id>` | array | yes | Chunk ID to delete (repeatable; batches of 10 are sent automatically) |
|
||||
| `--yes` | switch | no | Skip the confirmation prompt |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
@@ -473,8 +473,8 @@ bl knowledge create --name demo --description 'product docs' --category-id cate-
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | --------------------------------------------------------------- |
|
||||
| `--index-id <id>` | string | yes | Knowledge base ID |
|
||||
| `--yes` | switch | no | Skip the confirmation prompt |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
@@ -740,8 +740,8 @@ bl knowledge doc upload --file ./docs/ --dry-run --verbose
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | --------------------------------------------------------------- |
|
||||
| `--file-id <id>` | string | yes | Data-center file ID to delete |
|
||||
| `--yes` | switch | no | Skip the confirmation prompt |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
@@ -1044,8 +1044,8 @@ bl knowledge service create --name my-search --scene search --index-id idx-xxx
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | --------------------------------------------------------------- |
|
||||
| `--agent-id <id>` | string | yes | Service (agent) ID |
|
||||
| `--yes` | switch | no | Skip the confirmation prompt |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
@@ -1080,8 +1080,8 @@ bl knowledge service delete --agent-id aid-xxx --yes
|
||||
| ----------------------- | ------ | -------- | --------------------------------------------------------------- |
|
||||
| `--agent-id <id>` | string | yes | Service (agent) ID |
|
||||
| `--version-desc <text>` | string | no | Description for the newly published version |
|
||||
| `--yes` | switch | no | Skip the confirmation prompt |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--yes` | switch | no | Confirm this high-risk operation |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user