mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b93e0d586d | |||
| e76ebee681 | |||
| 78a1c547d3 | |||
| a95ad7242b | |||
| b830a14e11 | |||
| a6291e00e1 | |||
| 0a63115aed | |||
| abad3a6643 |
@@ -14,6 +14,7 @@ git add \
|
||||
skills/bailian-finetune/SKILL.md \
|
||||
skills/bailian-finetune/reference \
|
||||
skills/bailian-managed-agent/SKILL.md \
|
||||
skills/bailian-managed-agent/reference
|
||||
skills/bailian-managed-agent/reference \
|
||||
skills/bailian-web-search/SKILL.md
|
||||
|
||||
vp staged
|
||||
|
||||
@@ -35,7 +35,7 @@ packages/core/src/auth/ # apiKey / console credential 解析与落盘
|
||||
packages/core/src/client/ # HTTP client / endpoints / console gateway
|
||||
```
|
||||
|
||||
Skill / 命令手册随 `skills/bailian-*/` 经 `bl skill init` 安装(装齐 registry 中全部 `bailian-*`,含共享协议 `bailian-protocol`)。业务 skill(`bailian-cli` / `bailian-gen` / `bailian-finetune` / `bailian-managed-agent`)执行前读 `skills/bailian-protocol/`;不要依赖 frontmatter `companions`(安装器不强制)。`tools/generate-reference.ts` 从 **`packages/cli/src/commands.ts`** 按一级命令归属表分流写入各 `skills/<skill>/reference/`(纳入 git);`tools/sync-skill-metadata.ts` 从 `packages/cli/package.json` 同步各 `skills/*/SKILL.md` 的 `metadata.version`。两者由根脚本 `pnpm run sync:skill-assets` 和 `.vite-hooks/pre-commit` 执行。hub `bailian-cli` 的路由表不复述领域命令明细;SKILL 文案 / 安装约定 / hand-off 见 [docs/agents/skill-change.md](docs/agents/skill-change.md)。
|
||||
Skill / 命令手册随 `skills/bailian-*/` 经 `bl skill init` 安装(装齐 registry 中全部 `bailian-*`,含共享协议 `bailian-protocol`)。业务 skill(`bailian-cli` / `bailian-gen` / `bailian-finetune` / `bailian-managed-agent` / `bailian-web-search`)执行前读 `skills/bailian-protocol/`;不要依赖 frontmatter `companions`(安装器不强制)。`tools/generate-reference.ts` 从 **`packages/cli/src/commands.ts`** 按一级命令归属表分流写入各 `skills/<skill>/reference/`(纳入 git);`tools/sync-skill-metadata.ts` 从 `packages/cli/package.json` 同步各 `skills/*/SKILL.md` 的 `metadata.version`。两者由根脚本 `pnpm run sync:skill-assets` 和 `.vite-hooks/pre-commit` 执行。hub `bailian-cli` 的路由表不复述领域命令明细;SKILL 文案 / 安装约定 / hand-off 见 [docs/agents/skill-change.md](docs/agents/skill-change.md)。
|
||||
|
||||
约定:
|
||||
|
||||
|
||||
@@ -6,6 +6,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||||
|
||||
[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)
|
||||
|
||||
## [1.17.1] - 2026-08-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`knowledge create` now requires `--description`** — aligns with the server's required-description validation: the new `--description` flag is mandatory and its 1-500 character limit is checked locally before the request goes out. `bl knowledge create` / `kscli kb create` calls need to pass it.
|
||||
- **`knowledge service update` warned about config fields the server itself returned** — updating the draft config through scalar flags such as `--policy` reads the full draft and merges before writing back; the draft's `user_system_prompt`, `anti_leak_prompt`, `refusal_prompt`, `credibility_prompt`, `session_file_parse_mode`, and `enable_thinking` / `enable_temperature` / `enable_credibility` / `enable_max_completion_tokens` were not recognized by the CLI, so every update printed a run of `unknown agent_config field passed through` warnings. The config itself was always written correctly; the spurious warnings are gone.
|
||||
|
||||
### Added
|
||||
|
||||
- **`bailian-web-search` routing skill** — `bl skill init` now also installs a dedicated web-search routing skill, so agents pick the right search entry point instead of guessing.
|
||||
- **Knowledge Studio CLI command manual** — full `kscli` reference docs covering knowledge bases, documents, chunks, collections/categories, files, retrieval/Q&A services, and search/chat, with runnable examples for every command.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Description flags explain what to write** — help text for the collection and service `--description` flags now states what the field is for (telling similar items apart in lists; for services, agents read it to pick the right one) rather than just repeating "required".
|
||||
- **`knowledge retrieve --rerank-model` documents its precondition** — help now states that the target knowledge base must already have a rerank model configured, otherwise every value is rejected.
|
||||
|
||||
## [1.17.0] - 2026-08-18
|
||||
|
||||
### Added
|
||||
|
||||
@@ -6,6 +6,23 @@
|
||||
|
||||
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
|
||||
|
||||
## [1.17.1] - 2026-08-22
|
||||
|
||||
### 修复
|
||||
|
||||
- **`knowledge create` 的 `--description` 更新为必填** —— 对齐服务端对知识库描述的必填校验:新增 `--description` 参数并设为必填,在发出请求前于本地校验 1–500 个字符的长度限制。`bl knowledge create` / `kscli kb create` 调用需带上该参数。
|
||||
- **`knowledge service update` 对服务端自己返回的配置字段误报警告** —— 通过 `--policy` 等标量参数更新草稿配置时,CLI 会先读取完整草稿再合并回写;草稿中的 `user_system_prompt`、`anti_leak_prompt`、`refusal_prompt`、`credibility_prompt`、`session_file_parse_mode` 以及 `enable_thinking` / `enable_temperature` / `enable_credibility` / `enable_max_completion_tokens` 此前不被 CLI 识别,导致每次更新都刷出一串 `unknown agent_config field passed through` 警告。配置本身始终被正确写入,现在不再误报。
|
||||
|
||||
### 新增
|
||||
|
||||
- **`bailian-web-search` 路由技能** —— `bl skill init` 现在会一并安装专门的联网搜索路由技能,让 agent 直接选中正确的搜索入口,不再靠猜。
|
||||
- **Knowledge Studio CLI 命令手册** —— 完整的 `kscli` 参考文档,覆盖知识库、文档、切片、集合/类目、文件、检索/问答服务以及 search/chat,每条命令均附可运行示例。
|
||||
|
||||
### 变更
|
||||
|
||||
- **描述类参数说明写清该填什么** —— 数据集合与服务的 `--description` 帮助文案现在会说明该字段的用途(在列表中区分同类项;服务描述供 agent 判断该调用哪个服务),不再只是重复「必填」。
|
||||
- **`knowledge retrieve --rerank-model` 补充前置条件说明** —— 帮助文案现在会说明目标知识库必须已配置重排序模型,否则任何取值都会被拒绝。
|
||||
|
||||
## [1.17.0] - 2026-08-18
|
||||
|
||||
### 新增
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
bailian-protocol ← 共享协议(consent / 鉴权 / 版本 / 错误上报)
|
||||
▲ 靠 `bl skill init` 与业务 skill 同装;非安装器强制 companions
|
||||
│
|
||||
┌───────┴────────┬────────────────┬──────────────────┐
|
||||
bailian-gen bailian-finetune bailian-managed-agent
|
||||
(领域路由表) (领域工作流) (IaC 安全闸)
|
||||
│ │ │
|
||||
└────────────────┼──────────────────┘
|
||||
┌───────┴────────┬────────────────┬──────────────────┬───────────────────┐
|
||||
bailian-gen bailian-finetune bailian-managed-agent bailian-web-search
|
||||
(领域路由表) (领域工作流) (IaC 安全闸) (搜索路由+兜底)
|
||||
│ │ │ │
|
||||
└────────────────┼──────────────────┴─────────────────────┘
|
||||
▼ 软 hand-off(按 skill 名)
|
||||
bailian-cli(hub)
|
||||
hub 路由表:本职命令 + 领域 hand-off 行
|
||||
|
||||
@@ -128,7 +128,7 @@ bl knowledge info --index-id idx-xxx --workspace-id ws-xxx
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
bl knowledge create --name <text> (--doc-id <id> | --category-id <id>) [flags]
|
||||
bl knowledge create --name <text> --description <text> (--doc-id <id> | --category-id <id>) [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
@@ -136,6 +136,7 @@ bl knowledge create --name <text> (--doc-id <id> | --category-id <id>) [flags]
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------------------- | ------ | ---- | -------------------------------------------------------- |
|
||||
| `--name <text>` | string | 是 | 知识库名称(1-20 字符,工作区内唯一) |
|
||||
| `--description <text>` | string | 是 | 知识库装了什么内容、给谁用(1-500 字符) |
|
||||
| `--doc-id <id>` | array | 否¹ | 数据中心文件 ID(可重复);与 `--category-id` 互斥 |
|
||||
| `--category-id <id>` | array | 否¹ | 按分类导入该分类下所有文件(可重复);与 `--doc-id` 互斥 |
|
||||
| `--embedding-model <name>` | string | 否 | 向量模型名称(默认:`text-embedding-v4`) |
|
||||
@@ -148,6 +149,7 @@ bl knowledge create --name <text> (--doc-id <id> | --category-id <id>) [flags]
|
||||
**参数约束**
|
||||
|
||||
- `--name` 长度 1-20 字符
|
||||
- `--description` 长度 1-500 字符,缺失或超长会在本地被拦截
|
||||
- `--doc-id` 和 `--category-id` 互斥,必须提供其一
|
||||
|
||||
**输出**
|
||||
@@ -176,13 +178,13 @@ json 模式:返回 API 原始响应,包含 `pipelineId`(知识库 ID)和
|
||||
|
||||
```bash
|
||||
# 从指定文件创建知识库
|
||||
bl knowledge create --name demo --doc-id file-xxx --workspace-id ws-xxx
|
||||
bl knowledge create --name demo --description '产品文档' --doc-id file-xxx --workspace-id ws-xxx
|
||||
|
||||
# 从分类导入并等待导入完成
|
||||
bl knowledge create --name demo --category-id cate-xxx --wait
|
||||
bl knowledge create --name demo --description '产品文档' --category-id cate-xxx --wait
|
||||
|
||||
# 指定向量模型和切片大小
|
||||
bl knowledge create --name my-kb --doc-id file-a --doc-id file-b --embedding-model text-embedding-v4 --chunk-size 400 --workspace-id ws-xxx
|
||||
bl knowledge create --name my-kb --description '产品文档 v2' --doc-id file-a --doc-id file-b --embedding-model text-embedding-v4 --chunk-size 400 --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -153,7 +153,7 @@ bl knowledge doc upload --file ./docs/intro.md --workspace-id ws-xxx
|
||||
# → 返回 file-id
|
||||
|
||||
# 2. 用文件创建知识库
|
||||
bl knowledge create --name my-kb --doc-id file-xxx --workspace-id ws-xxx --wait
|
||||
bl knowledge create --name my-kb --description '产品文档' --doc-id file-xxx --workspace-id ws-xxx --wait
|
||||
# → 返回 index-id (pipelineId) 和导入任务状态
|
||||
|
||||
# 3. 创建检索服务(search 场景)
|
||||
@@ -245,7 +245,7 @@ bl knowledge doc import-oss \
|
||||
# → 返回各文件的 fileId
|
||||
|
||||
# 2. 创建知识库并导入这些文件
|
||||
bl knowledge create --name oss-kb --doc-id file-a --doc-id file-b --workspace-id ws-xxx --wait
|
||||
bl knowledge create --name oss-kb --description 'OSS 导入文档' --doc-id file-a --doc-id file-b --workspace-id ws-xxx --wait
|
||||
|
||||
# 3. 检索
|
||||
bl knowledge search --query "相关内容" --agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
@@ -142,14 +142,14 @@ bl knowledge service create --name <text> --scene <chat|search> [flags]
|
||||
| ------------------------ | ------ | ---- | ------------------------------------------------- |
|
||||
| `--name <text>` | string | 是 | 服务名称(最多 200 字符,同一场景下工作区内唯一) |
|
||||
| `--scene <chat\|search>` | string | 是 | 服务场景:`chat`(Q&A)或 `search`(检索) |
|
||||
| `--description <text>` | string | 否 | 服务描述(最多 1000 字符) |
|
||||
| `--description <text>` | string | 建议 | 这个服务能回答什么、给谁用(最多 1000 字符) |
|
||||
| `--index-id <id>` | string | 否 | 绑定此知识库;其他配置使用服务端默认值 |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--name` 最多 200 字符
|
||||
- `--scene` 只能是 `chat` 或 `search`
|
||||
- `--description` 最多 1000 字符
|
||||
- `--description` 最多 1000 字符;建议填写 —— agent 靠它判断该调用哪个服务
|
||||
|
||||
**输出**
|
||||
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
# Chunk 管理命令手册
|
||||
|
||||
Chunk 是知识库中最小的检索单元。文档导入后自动切分为 chunk,也可以手动添加。
|
||||
|
||||
> **通用约定**(鉴权、Workspace ID、全局参数、输出格式、危险操作确认、Dry-run 模式)请参阅 [总览文档](./kscli-cli-guide.md#通用约定)。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chunk add`
|
||||
|
||||
直接向知识库添加 chunk。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli chunk add --index-id <id> (--content <text> | --field <k=v>) [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ------ | ---- | ------------------------------------------------------------------------------------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--doc-id <id>` | string | 否² | 所属文档 ID;表格/图片知识库必填,文档型可选 |
|
||||
| `--content <text>` | string | 否¹ | Chunk 正文,最多 6000 字符(文档型);与 `--content-file` 互斥 |
|
||||
| `--content-file <path>` | string | 否¹ | 从 UTF-8 文本文件读取正文(`.md`/`.txt` 等);与 `--content` 互斥 |
|
||||
| `--title <text>` | string | 否 | Chunk 标题,最多 50 字符(文档型) |
|
||||
| `--image-url <url>` | array | 否 | Chunk 图片 URL(可重复,最多 10 个;文档型) |
|
||||
| `--field <key=value>` | array | 否¹ | 任意字段键值对(可重复),用于表格/图片知识库,键为 Excel 列名;与 content/title/image 互斥 |
|
||||
|
||||
> ¹ `--content`/`--content-file`/`--title`/`--image-url` 与 `--field` 互斥,必须提供其一。
|
||||
> ² 表格/图片知识库必须提供 `--doc-id`。文档型知识库可选。
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--field` 与 `--content`/`--content-file`/`--title`/`--image-url` 互斥
|
||||
- `--content` 与 `--content-file` 互斥
|
||||
- `--content` 最多 6000 字符
|
||||
- `--title` 最多 50 字符
|
||||
- `--image-url` 最多 10 个
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
chunk created (pipeline: idx-xxx)
|
||||
List chunks to find the new chunk id.
|
||||
```
|
||||
|
||||
quiet 模式:无输出(成功退出码 0)。
|
||||
|
||||
json 模式:返回 API 原始响应(不含 chunk ID)。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 支持文档/表格/图片知识库;音视频知识库不支持。
|
||||
- API 响应不含 chunk ID,需用 `chunk list` 查找新 chunk。
|
||||
- API 幂等但限流 10 次/秒,批量脚本需自行节流。
|
||||
- 表格/图片知识库用 `--field`,键为 Excel 列名,值为字符串。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 添加文本 chunk
|
||||
kscli chunk add --index-id idx-xxx --content "chunk text" --title intro --workspace-id ws-xxx
|
||||
|
||||
# 添加表格行(字段方式)
|
||||
kscli chunk add --index-id idx-xxx --field 列A=v1 --field 列B=v2
|
||||
|
||||
# 从文件读取内容
|
||||
kscli chunk add --index-id idx-xxx --content-file ./chunk.md --doc-id doc-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chunk list`
|
||||
|
||||
列出知识库中的 chunk,含内容和状态。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli chunk list --index-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------------- | ------ | ---- | ------------------------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--doc-id <id>` | string | 否 | 只显示属于此文档的 chunk |
|
||||
| `--page-number <n>` | number | 否 | 页码(默认:1) |
|
||||
| `--page-size <n>` | number | 否 | 每页条数(默认:20,最大 100) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--page-size` 范围 1-100
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
[chunk] chunk-xxx (doc: intro.md, doc_id: file-xxx) status: COMPLETED
|
||||
chunk content preview (truncated at 200 chars)…
|
||||
total: 1
|
||||
```
|
||||
|
||||
> 如果 chunk 被排除检索,行尾会显示 `[excluded from retrieval]`。
|
||||
|
||||
quiet 模式:每行一个 `metadata._id`(chunk ID),用于管道传给 update/delete。
|
||||
|
||||
json 模式:返回 API 原始响应,`data.nodes[]` 含完整 chunk 数据。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 用 `metadata._id` 作为 chunk ID,`metadata.doc_id` 作为文档 ID,在 chunk update/delete 中使用。
|
||||
- 页大小默认 20,最大 100。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 列出所有 chunk
|
||||
kscli chunk list --index-id idx-xxx --workspace-id ws-xxx
|
||||
|
||||
# 只看某文档的 chunk
|
||||
kscli chunk list --index-id idx-xxx --doc-id file-xxx --page-size 50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chunk update`
|
||||
|
||||
更新 chunk 内容或切换其检索可见性。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli chunk update --index-id <id> --chunk-id <id> --doc-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ------ | ---- | ------------------------------------------------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--chunk-id <id>` | string | 是 | Chunk ID(`metadata._id`,来自 chunk list 输出) |
|
||||
| `--doc-id <id>` | string | 是 | 所属文档 ID(`metadata.doc_id`,来自 chunk list 输出) |
|
||||
| `--content <text>` | string | 否¹ | 新内容,10-6000 字符;与 `--content-file` 互斥 |
|
||||
| `--content-file <path>` | string | 否¹ | 从 UTF-8 文本文件读取新内容 |
|
||||
| `--title <text>` | string | 否 | Chunk 标题,0-50 字符(空字符串清除标题;不传则不变) |
|
||||
| `--exclude` | switch | 否² | 将此 chunk 排除出检索 |
|
||||
| `--include` | switch | 否² | 将此 chunk 恢复检索(默认行为) |
|
||||
|
||||
> ¹ `--content` 与 `--content-file` 互斥。
|
||||
> ² `--exclude` 与 `--include` 互斥。
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--content` 与 `--content-file` 互斥
|
||||
- `--exclude` 与 `--include` 互斥
|
||||
- 至少提供一个更新项(`--content`/`--content-file`/`--title`/`--exclude`/`--include`)
|
||||
- `--content` 长度 10-6000 字符
|
||||
- `--title` 最多 50 字符
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
updated: chunk-xxx
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 内容必须 10-6000 字符,且不超过知识库的 max chunk size。
|
||||
- `--content-file` 期望 UTF-8 纯文本文件,不解析 `.docx`/`.pdf` 等文档格式。
|
||||
- 仅切换 `--exclude`/`--include` 而不提供新内容时,CLI 自动读回当前内容并重新提交(API 要求 content 字段必填,CLI 隐藏了此限制)。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 修改内容
|
||||
kscli chunk update --index-id idx-xxx --chunk-id chunk-xxx --doc-id file-xxx --content "corrected text" --workspace-id ws-xxx
|
||||
|
||||
# 排除 chunk 不参与检索
|
||||
kscli chunk update --index-id idx-xxx --chunk-id chunk-xxx --doc-id file-xxx --exclude
|
||||
|
||||
# 恢复检索
|
||||
kscli chunk update --index-id idx-xxx --chunk-id chunk-xxx --doc-id file-xxx --include
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chunk delete`
|
||||
|
||||
从知识库中删除 chunk(不可逆)。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli chunk delete --index-id <id> --chunk-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ------------------------------------------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--chunk-id <id>` | array | 是 | Chunk ID(可重复,每批最多 10 个,超出自动分批) |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
deleted: 2 chunk(s) in 1 batch(es)
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 `{ deleted_count, batches }`。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 服务端每次最多接受 10 个 chunk ID,CLI 自动分批。
|
||||
- 如果某批失败,操作停止,已删除的批次会在错误 hint 中列出。
|
||||
- Chunk 被永久移除,不可恢复。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 删除多个 chunk
|
||||
kscli chunk delete --index-id idx-xxx --chunk-id chunk-a --chunk-id chunk-b --workspace-id ws-xxx
|
||||
|
||||
# 跳过确认
|
||||
kscli chunk delete --index-id idx-xxx --chunk-id chunk-a --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
← [返回总览](./kscli-cli-guide.md)
|
||||
@@ -0,0 +1,268 @@
|
||||
# 数据中心集合与分类命令手册
|
||||
|
||||
集合(collection)是数据中心的顶层容器,对应服务端的 connector。分类(category)用于组织集合内的文件,支持多级嵌套。
|
||||
|
||||
> **通用约定**(鉴权、Workspace ID、全局参数、输出格式、危险操作确认、Dry-run 模式)请参阅 [总览文档](./kscli-cli-guide.md#通用约定)。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli collection create`
|
||||
|
||||
创建 FILE 数据集合。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli collection create --name <text> --description <text> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------------- | ------ | ---- | ---------------------------------------------------------------- |
|
||||
| `--name <text>` | string | 是 | 集合名称(1-20 字符) |
|
||||
| `--description <text>` | string | 是 | 集合描述 |
|
||||
| `--store-type <type>` | string | 否 | 存储类型:`platform`(托管,默认)或 `custom`(自有 OSS bucket) |
|
||||
| `--oss-region <id>` | string | 否 | OSS region ID(`--store-type custom` 时必填) |
|
||||
| `--oss-bucket <name>` | string | 否 | OSS bucket 名称(`--store-type custom` 时必填) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--name` 长度 1-20 字符
|
||||
- `--store-type` 只能是 `platform` 或 `custom`
|
||||
- `--store-type custom` 时 `--oss-region` 和 `--oss-bucket` 必填
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
created: conn-xxx (my-collection, PLATFORM)
|
||||
```
|
||||
|
||||
quiet 模式:输出集合 ID。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- `platform` 使用平台托管存储;`custom` 使用已授权的 OSS bucket。
|
||||
- 自定义 bucket 必须携带标签 `bailian-connector-access=ReadAndWrite`(百炼的标签访问控制),否则服务端报 `setBucketCORS failed` 误导性错误。
|
||||
- **无集合删除 API**,创建需谨慎。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 创建平台托管的集合
|
||||
kscli collection create --name my-collection --description "team docs" --workspace-id ws-xxx
|
||||
|
||||
# 创建使用自有 OSS bucket 的集合
|
||||
kscli collection create --name oss-coll --description "own bucket" --store-type custom --oss-region cn-beijing --oss-bucket my-bucket
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli collection get`
|
||||
|
||||
查看数据集合详情。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli collection get (--collection-id <id> | --name <text>) [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------------- | ------ | ---- | -------- |
|
||||
| `--collection-id <id>` | string | 否¹ | 集合 ID |
|
||||
| `--name <text>` | string | 否¹ | 集合名称 |
|
||||
|
||||
> ¹ `--collection-id` 和 `--name` 二选一,必须提供其一。
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--collection-id` 和 `--name` 互斥,必须提供其一
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
id: conn-xxx
|
||||
name: my-collection
|
||||
description: team docs
|
||||
```
|
||||
|
||||
quiet 模式:输出集合 ID。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- getConnector 不返回 `fileConnectorConfig`(`storeType`/`regionId`/`bucketName`),这些字段仅在创建时通过请求体传入,查询时不可读回。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 按 ID 查询
|
||||
kscli collection get --collection-id conn-xxx --workspace-id ws-xxx
|
||||
|
||||
# 按名称查询
|
||||
kscli collection get --name my-collection
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli category list`
|
||||
|
||||
列出数据中心分类。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli category list [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------------- | ------ | ---- | ------------------------------------------------------ |
|
||||
| `--collection-id <id>` | string | 否 | 按集合 ID 过滤 |
|
||||
| `--parent-id <id>` | string | 否 | 列出此分类的子分类 |
|
||||
| `--name <text>` | string | 否 | 按分类名称过滤(精确匹配,与知识库列表的模糊匹配不同) |
|
||||
| `--next-token <token>` | string | 否 | 游标分页令牌 |
|
||||
| `--max-result <n>` | number | 否 | 每页条数(默认:20) |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
cate-xxx product-docs
|
||||
cate-yyy system-docs [default]
|
||||
next: --next-token eyJ...
|
||||
```
|
||||
|
||||
> 标记 `[default]` 的是文件未指定分类时的默认归属。
|
||||
|
||||
quiet 模式:每行一个 `categoryId`。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 分页是游标方式:使用输出的 `next: --next-token <token>` 继续翻页。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 列出所有分类
|
||||
kscli category list --workspace-id ws-xxx
|
||||
|
||||
# 按名称过滤
|
||||
kscli category list --name my-category
|
||||
|
||||
# 翻页
|
||||
kscli category list --next-token eyJ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli category add`
|
||||
|
||||
创建数据中心分类。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli category add --name <text> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------------- | ------ | ---- | -------------------------------- |
|
||||
| `--name <text>` | string | 是 | 分类名称(1-20 字符) |
|
||||
| `--parent-id <id>` | string | 否 | 创建为指定分类的子分类 |
|
||||
| `--collection-id <id>` | string | 否 | 创建在此集合下(默认:平台集合) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--name` 长度 1-20 字符
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
created: cate-xxx (product-docs)
|
||||
```
|
||||
|
||||
quiet 模式:输出分类 ID。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 用分类按业务域组织数据中心文件。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 创建分类
|
||||
kscli category add --name product-docs --workspace-id ws-xxx
|
||||
|
||||
# 创建子分类
|
||||
kscli category add --name sub --parent-id cate-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli category delete`
|
||||
|
||||
删除数据中心分类。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli category delete --category-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| -------------------- | ------ | ---- | ------------ |
|
||||
| `--category-id <id>` | string | 是 | 分类 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
deleted: cate-xxx
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 含文件或子分类的分类的删除行为由服务端定义——服务端错误原样透传。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 删除分类(交互确认)
|
||||
kscli category delete --category-id cate-xxx --workspace-id ws-xxx
|
||||
|
||||
# 跳过确认
|
||||
kscli category delete --category-id cate-xxx --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
← [返回总览](./kscli-cli-guide.md)
|
||||
@@ -0,0 +1,344 @@
|
||||
# 文档管理命令手册
|
||||
|
||||
文档管理覆盖文件上传、OSS 导入、解析状态跟踪、文档删除和标签管理。文档导入知识库后自动解析为 chunk。
|
||||
|
||||
> **通用约定**(鉴权、Workspace ID、全局参数、输出格式、危险操作确认、Dry-run 模式)请参阅 [总览文档](./kscli-cli-guide.md#通用约定)。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc list`
|
||||
|
||||
列出知识库中的文档及其解析/索引状态。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli doc list --index-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------------- | ------ | ---- | ------------------------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--page-number <n>` | number | 否 | 页码(默认:1) |
|
||||
| `--page-size <n>` | number | 否 | 每页条数(默认:10,最大 100) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--page-size` 范围 1-100
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:每行一个文档,`FAILED` 状态的文档红色高亮。
|
||||
|
||||
```
|
||||
doc-xxx COMPLETED intro.md md 1024
|
||||
total: 1
|
||||
```
|
||||
|
||||
quiet 模式:每行一个 `doc_id`。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- `doc_id` 与 `file_id` 的关系:通过 `kb create --doc-id` 导入的文档,`doc_id` 等于 `fileId`;通过 `doc upload --index-id` 导入的,`doc_id` 可能包含 workspace 后缀。
|
||||
- 页大小默认 10(服务端默认),最大 100。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 列出文档
|
||||
kscli doc list --index-id idx-xxx --workspace-id ws-xxx
|
||||
|
||||
# 每页 100 条
|
||||
kscli doc list --index-id idx-xxx --page-size 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc status`
|
||||
|
||||
查看知识库导入任务状态。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli doc status --index-id <id> --job-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------------------- | ------ | ---- | --------------------------------------------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--job-id <id>` | string | 是 | 导入任务 ID(`ingestionId`,由 create/upload 返回) |
|
||||
| `--page-number <n>` | number | 否 | 页码 |
|
||||
| `--page-size <n>` | number | 否 | 每页条数 |
|
||||
| `--wait` | switch | 否 | 轮询直到任务到达终态 |
|
||||
| `--poll-interval <seconds>` | number | 否 | 轮询间隔秒数(默认:5) |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
status: COMPLETED
|
||||
doc-xxx COMPLETED intro.md
|
||||
```
|
||||
|
||||
quiet 模式:输出任务状态(`PENDING`/`RUNNING`/`COMPLETED`)。
|
||||
|
||||
json 模式:返回 API 原始响应,`data.rows[]` 包含每个文档的状态。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- `--index-id` 和 `--job-id` 服务端均要求必传,只传一个会返回 `SystemError`。
|
||||
- 整体任务状态为 `PENDING` / `RUNNING` / `COMPLETED`(无 `FAILED` 值)。
|
||||
- 单个文档可能解析失败(如 `PARSE_FAILED`),此时 CLI 以非零退出码报错,服务端消息原样透传。
|
||||
- 如果服务端对空闲知识库返回 `SystemError`,说明该 job 可能不存在。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 查看任务状态
|
||||
kscli doc status --index-id idx-xxx --job-id job-xxx --workspace-id ws-xxx
|
||||
|
||||
# 轮询等待完成,10 秒间隔
|
||||
kscli doc status --index-id idx-xxx --job-id job-xxx --wait --poll-interval 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc upload`
|
||||
|
||||
上传本地文件或目录到数据中心,可选导入到知识库。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli doc upload --file <path> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------------------- | ------ | ---- | ---------------------------------------------------------------- |
|
||||
| `--file <path>` | array | 是 | 本地文件或目录路径(可重复)。目录递归扫描,不支持的格式自动跳过 |
|
||||
| `--index-id <id>` | string | 否 | 上传后导入到此知识库(所有文件合并为一个导入任务) |
|
||||
| `--category-id <id>` | string | 否 | 目标数据中心分类(默认:工作区默认分类) |
|
||||
| `--tag <text>` | array | 否 | 文件标签(可重复),应用到每个上传的文件 |
|
||||
| `--wait` | switch | 否 | 轮询导入任务直到终态(需要 `--index-id`) |
|
||||
| `--poll-interval <seconds>` | number | 否 | 轮询间隔秒数(默认:5) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--wait` 要求同时指定 `--index-id`
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
intro.md file-xxx registered
|
||||
job: job-xxx
|
||||
status: COMPLETED
|
||||
|
||||
Uploaded 1 file.
|
||||
```
|
||||
|
||||
quiet 模式:每行一个 `fileId`。
|
||||
|
||||
json 模式:返回自定义结构,包含 `files`(路径和 fileId)、`skipped`、`index_id`、`ingestion_id`、`final_status`。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 上传管道:申请 lease → PUT 到 OSS → 注册文件 →(可选)创建导入任务。
|
||||
- 目录递归扫描,`node_modules`、`.git` 等自动跳过。
|
||||
- 多文件按顺序处理(无并发),避免 OSS 限流。
|
||||
- 支持的文件格式:`.pdf .doc .docx .ppt .pptx .xls .xlsx .csv .md .txt .html .png .jpg .jpeg .bmp .gif`
|
||||
- 部分文件上传失败时,已注册的 fileId 会在错误 hint 中列出。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 上传单个文件
|
||||
kscli doc upload --file ./a.md --workspace-id ws-xxx
|
||||
|
||||
# 上传多个文件并导入到知识库,等待完成
|
||||
kscli doc upload --file ./a.md --file ./b.pdf --index-id idx-xxx --wait
|
||||
|
||||
# 上传整个目录
|
||||
kscli doc upload --file ./docs/ --workspace-id ws-xxx
|
||||
|
||||
# 干跑预览(查看将上传和跳过的文件)
|
||||
kscli doc upload --file ./docs/ --dry-run --verbose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc delete`
|
||||
|
||||
从知识库中删除文档及其 chunk。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli doc delete --index-id <id> --doc-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ----------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--doc-id <id>` | array | 是 | 文档 ID(可重复) |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
deleted: 2 document(s)
|
||||
doc-a
|
||||
doc-b
|
||||
```
|
||||
|
||||
quiet 模式:每行一个已删除的 `doc_id`。
|
||||
|
||||
json 模式:返回 API 原始响应,`data.deleted[]` 为实际删除的 ID 列表。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 只从知识库索引中移除文档,数据中心源文件不受影响(用 `file delete` 删除源文件)。
|
||||
- `doc_id` 应从 `doc list --quiet` 获取,而非 `doc upload` 返回的 `fileId`。
|
||||
- 删除是异步的:服务端立即返回 Success,但 `doc list` 中可能仍显示该文档(约 30 秒后传播完成)。
|
||||
- 输出的是服务端实际删除的 ID 列表,可能与请求的数量不一致(会在 stderr 警告)。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 删除单个文档
|
||||
kscli doc delete --index-id idx-xxx --doc-id doc-xxx --workspace-id ws-xxx
|
||||
|
||||
# 批量删除,跳过确认
|
||||
kscli doc delete --index-id idx-xxx --doc-id doc-a --doc-id doc-b --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc tag`
|
||||
|
||||
批量更新数据中心文件的标签。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli doc tag --doc-id <id> --tag <text> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------- | ------ | ---- | ------------------------------------------------------ |
|
||||
| `--doc-id <id>` | array | 是 | 数据中心文件 ID(可重复,最多 20 个/次) |
|
||||
| `--tag <text>` | array | 是 | 标签(可重复),应用到每个 `--doc-id` |
|
||||
| `--mode <mode>` | string | 否 | 更新模式:`append`(默认,追加)或 `overwrite`(覆盖) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--doc-id` 最多 20 个/次
|
||||
- `--tag` 最多 100 个
|
||||
- 每个标签最多 32 字符
|
||||
- 标签总长度最多 700 字符
|
||||
- `--mode` 只能是 `append` 或 `overwrite`
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
tagged: 2 file(s) with [project-a, draft]
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 同一组标签应用到所有 `--doc-id`;不同标签集需多次执行。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 追加标签
|
||||
kscli doc tag --doc-id file-xxx --tag project-a --tag draft --workspace-id ws-xxx
|
||||
|
||||
# 覆盖标签
|
||||
kscli doc tag --doc-id file-a --doc-id file-b --tag final --mode overwrite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc import-oss`
|
||||
|
||||
从已授权的 OSS bucket 批量导入文件到数据中心。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli doc import-oss --bucket <name> --region <id> --oss-key <key> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| -------------------- | ------ | ---- | ------------------------------------- |
|
||||
| `--bucket <name>` | string | 是 | 已授权的 OSS bucket 名称 |
|
||||
| `--region <id>` | string | 是 | OSS region ID(如 `cn-beijing`) |
|
||||
| `--oss-key <key>` | array | 是 | OSS 对象 key(可重复,最多 10 个/次) |
|
||||
| `--category-id <id>` | string | 否 | 目标数据中心分类(默认:默认分类) |
|
||||
| `--tag <text>` | array | 否 | 文件标签(可重复,最多 10 个) |
|
||||
| `--overwrite` | switch | 否 | 覆盖之前从相同 OSS key 导入的文件 |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--oss-key` 最多 10 个/次
|
||||
- `--tag` 最多 10 个
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
imported: 2 file(s)
|
||||
file-a SUCCESS docs/a.pdf
|
||||
file-b SUCCESS docs/b.docx
|
||||
```
|
||||
|
||||
quiet 模式:每行一个 `fileId`。
|
||||
|
||||
json 模式:返回 API 原始响应,`data.addFileResultList[]` 包含每个文件的 fileId、status 和 ossKey。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- bucket 必须事先授权给平台服务角色(RAM 中的 `AliyunServiceRoleForBailian`)。
|
||||
- 文件名取自 OSS key 的 basename。
|
||||
- `--overwrite` 会替换之前导入的文件并生成**新的 fileId**(旧 fileId 失效)。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 导入单个文件
|
||||
kscli doc import-oss --bucket my-bucket --region cn-beijing --oss-key docs/a.pdf --workspace-id ws-xxx
|
||||
|
||||
# 导入多个文件并覆盖
|
||||
kscli doc import-oss --bucket my-bucket --region cn-beijing --oss-key docs/a.pdf --oss-key docs/b.docx --overwrite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
← [返回总览](./kscli-cli-guide.md)
|
||||
@@ -0,0 +1,157 @@
|
||||
# 数据中心文件管理命令手册
|
||||
|
||||
数据中心是知识库文件的存储层。文件通过 `doc upload` 或 `doc import-oss` 进入数据中心,再导入到知识库。数据中心文件可被多个知识库引用。
|
||||
|
||||
> **通用约定**(鉴权、Workspace ID、全局参数、输出格式、危险操作确认、Dry-run 模式)请参阅 [总览文档](./kscli-cli-guide.md#通用约定)。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli file list`
|
||||
|
||||
列出数据中心分类下的文件。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli file list --category-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------------- | ------ | ---- | -------------------------------------------------- |
|
||||
| `--category-id <id>` | string | 是 | 分类 ID(通过 `category list` 或 `file get` 获取) |
|
||||
| `--name <text>` | string | 否 | 按文件名过滤 |
|
||||
| `--file-id <id>` | array | 否 | 按文件 ID 过滤(可重复) |
|
||||
| `--next-token <token>` | string | 否 | 游标分页令牌(从上次输出获取) |
|
||||
| `--max-result <n>` | number | 否 | 每页条数 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
file-xxx SUCCESS intro.md 1024
|
||||
next: --next-token eyJ...
|
||||
```
|
||||
|
||||
quiet 模式:每行一个 `fileId`。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- `--category-id` 必须是真实的分类 ID。与上传 API 不同,字面量 `default` 在此不被解析,传入会返回空列表。通过 `file get` 的 category 字段或 `category list` 获取真实 ID。
|
||||
- 分页是游标方式:使用输出的 `next: --next-token <token>` 继续翻页。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 列出分类下文件
|
||||
kscli file list --category-id cate-xxx --workspace-id ws-xxx
|
||||
|
||||
# 按名称过滤
|
||||
kscli file list --category-id cate-xxx --name report
|
||||
|
||||
# 翻页
|
||||
kscli file list --category-id cate-xxx --next-token eyJ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli file get`
|
||||
|
||||
查看数据中心文件详情。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli file get --file-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | ------ | ---- | --------------- |
|
||||
| `--file-id <id>` | string | 是 | 数据中心文件 ID |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
id: file-xxx
|
||||
name: intro.md
|
||||
type: md
|
||||
size: 1024
|
||||
status: SUCCESS
|
||||
parser: AUTO_SELECT
|
||||
category: cate-xxx
|
||||
uploaded: 2026-01-01T00:00:00Z
|
||||
tags: project-a, draft
|
||||
```
|
||||
|
||||
quiet 模式:输出 JSON 格式。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 无特殊注意事项。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 查看文件详情
|
||||
kscli file get --file-id file-xxx --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli file delete`
|
||||
|
||||
从数据中心永久删除文件。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli file delete --file-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | ------ | ---- | --------------- |
|
||||
| `--file-id <id>` | string | 是 | 数据中心文件 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
deleted: file-xxx
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- **不可逆操作**:如果知识库引用了此文件,相关文档索引会失效。
|
||||
- 与 `doc delete` 的区别:`doc delete` 只从单个知识库索引中移除文档,数据中心源文件保留;`file delete` 删除源文件本身,影响所有引用它的知识库。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 删除文件(交互确认)
|
||||
kscli file delete --file-id file-xxx --workspace-id ws-xxx
|
||||
|
||||
# 跳过确认
|
||||
kscli file delete --file-id file-xxx --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
← [返回总览](./kscli-cli-guide.md)
|
||||
@@ -0,0 +1,342 @@
|
||||
# 知识库管理命令手册
|
||||
|
||||
知识库(Knowledge Base / pipeline / index)是 RAG 的核心载体,存储文档解析后的向量索引。本组命令覆盖知识库的创建、查看、更新、删除和监控。
|
||||
|
||||
> **通用约定**(鉴权、Workspace ID、全局参数、输出格式、危险操作确认、Dry-run 模式)请参阅 [总览文档](./kscli-cli-guide.md#通用约定)。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb list`
|
||||
|
||||
列出工作区中的知识库。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli kb list [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------------- | ------ | ---- | --------------------------------- |
|
||||
| `--name <text>` | string | 否 | 按知识库名称模糊过滤(1-20 字符) |
|
||||
| `--page-number <n>` | number | 否 | 页码(默认:1) |
|
||||
| `--page-size <n>` | number | 否 | 每页条数(默认:20,最大 100) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--name` 长度 1-20 字符
|
||||
- `--page-size` 范围 1-100
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:每行一个知识库,字段以双空格分隔,末尾显示总数。
|
||||
|
||||
```
|
||||
idx-xxx my-kb text-embedding-v4 600 product docs
|
||||
total: 1
|
||||
```
|
||||
|
||||
quiet 模式:每行一个知识库 ID。
|
||||
|
||||
json 模式:返回 API 原始响应,`data.rows[]` 包含完整知识库信息。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 返回的 `id` 字段作为后续命令的 `--index-id` 使用。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 列出所有知识库
|
||||
kscli kb list --workspace-id ws-xxx
|
||||
|
||||
# 按名称过滤,第二页
|
||||
kscli kb list --name demo --page-number 2 --page-size 50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb info`
|
||||
|
||||
查看知识库配置详情。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli kb info --index-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | --------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:按诊断维度分组展示。
|
||||
|
||||
```
|
||||
Basic:
|
||||
id: idx-xxx
|
||||
name: my-kb
|
||||
description: product docs
|
||||
dataType: ...
|
||||
Indexing: [immutable — recreate required to change]
|
||||
embeddingModelName: text-embedding-v4
|
||||
embeddingDimension: 1024
|
||||
chunkSize: 600
|
||||
overlapSize: ...
|
||||
chunkMode: ...
|
||||
separator: ...
|
||||
Retrieval:
|
||||
rerankModelName: ...
|
||||
rerankMinScore: ...
|
||||
rerankTopN: ...
|
||||
rerankMode: ...
|
||||
enableRewrite: ...
|
||||
denseSimilarityTopK: ...
|
||||
sparseSimilarityTopK: ...
|
||||
Data:
|
||||
sourceType: ...
|
||||
connectorId: ...
|
||||
```
|
||||
|
||||
quiet 模式:输出知识库 ID。
|
||||
|
||||
json 模式:返回知识库完整配置 JSON。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 索引设置(向量模型、切片大小等)不可变,修改需重建知识库。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 查看知识库详情
|
||||
kscli kb info --index-id idx-xxx --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb create`
|
||||
|
||||
创建知识库并导入数据中心文件或分类。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli kb create --name <text> --description <text> (--doc-id <id> | --category-id <id>) [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------------------- | ------ | ---- | -------------------------------------------------------- |
|
||||
| `--name <text>` | string | 是 | 知识库名称(1-20 字符,工作区内唯一) |
|
||||
| `--description <text>` | string | 是 | 知识库装了什么内容、给谁用(1-500 字符) |
|
||||
| `--doc-id <id>` | array | 否¹ | 数据中心文件 ID(可重复);与 `--category-id` 互斥 |
|
||||
| `--category-id <id>` | array | 否¹ | 按分类导入该分类下所有文件(可重复);与 `--doc-id` 互斥 |
|
||||
| `--embedding-model <name>` | string | 否 | 向量模型名称(默认:`text-embedding-v4`) |
|
||||
| `--chunk-size <n>` | number | 否 | 切片大小,字符数(默认:600,建议 300-800) |
|
||||
| `--wait` | switch | 否 | 轮询初始导入任务直到终态 |
|
||||
| `--poll-interval <seconds>` | number | 否 | 轮询间隔秒数(默认:5) |
|
||||
|
||||
> ¹ `--doc-id` 和 `--category-id` 二选一,必须提供其一。
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--name` 长度 1-20 字符
|
||||
- `--description` 长度 1-500 字符,缺失或超长会在本地被拦截
|
||||
- `--doc-id` 和 `--category-id` 互斥,必须提供其一
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
index_id: idx-xxx
|
||||
ingestion_id: job-xxx
|
||||
status: COMPLETED
|
||||
Next: check the import job status, then search against this knowledge base.
|
||||
```
|
||||
|
||||
quiet 模式:只输出知识库 ID。
|
||||
|
||||
json 模式:返回 API 原始响应,包含 `pipelineId`(知识库 ID)和 `ingestionId`(导入任务 ID)。`--wait` 时追加 `final_status` 字段。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 结构/存储类型固定为默认文档知识库(非结构化,BUILT_IN 存储)。
|
||||
- 返回知识库 ID(`pipelineId`)和初始导入任务 ID(`ingestionId`)。
|
||||
- 使用 `doc status` 或 `--wait` 跟踪导入进度。
|
||||
- 如果 `--wait` 后部分文档解析失败,CLI 以非零退出码报错,知识库已创建成功的事实会在 hint 中提示。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 从指定文件创建知识库
|
||||
kscli kb create --name demo --description '产品文档' --doc-id file-xxx --workspace-id ws-xxx
|
||||
|
||||
# 从分类导入并等待导入完成
|
||||
kscli kb create --name demo --description '产品文档' --category-id cate-xxx --wait
|
||||
|
||||
# 指定向量模型和切片大小
|
||||
kscli kb create --name my-kb --description '产品文档 v2' --doc-id file-a --doc-id file-b --embedding-model text-embedding-v4 --chunk-size 400 --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb update`
|
||||
|
||||
更新知识库名称、描述或 rerank 阈值。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli kb update --index-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------------------- | ------ | ---- | -------------------------------------------------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--name <text>` | string | 否 | 新名称(1-20 字符) |
|
||||
| `--description <text>` | string | 否 | 新描述 |
|
||||
| `--rerank-min-score <score>` | number | 否 | rerank 最低分数阈值,范围 0-1(低于此分的 chunk 被过滤) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- 至少提供 `--name`、`--description`、`--rerank-min-score` 之一,否则报错 "Nothing to update"
|
||||
- `--name` 长度 1-20 字符
|
||||
- `--rerank-min-score` 范围 0-1
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
updated: idx-xxx
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 索引设置(向量模型、切片大小等)不可变,修改需重建知识库。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 更新描述
|
||||
kscli kb update --index-id idx-xxx --description "product docs v2" --workspace-id ws-xxx
|
||||
|
||||
# 调整 rerank 阈值
|
||||
kscli kb update --index-id idx-xxx --rerank-min-score 0.3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb delete`
|
||||
|
||||
删除知识库及其所有文档和 chunk。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli kb delete --index-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ------------ |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
deleted: idx-xxx
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- **不可逆操作**:知识库及所有索引内容被永久删除。
|
||||
- 数据中心中的源文件不受影响,仅删除知识库索引。
|
||||
- 不带 `--yes` 时,CLI 会先查询知识库名称和文档数量作为确认摘要。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 删除(交互确认)
|
||||
kscli kb delete --index-id idx-xxx --workspace-id ws-xxx
|
||||
|
||||
# 跳过确认
|
||||
kscli kb delete --index-id idx-xxx --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb stats`
|
||||
|
||||
查看知识库存储和 QPS 监控数据。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli kb stats --index-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ----------------------------------------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--start <time>` | string | 否 | 范围起始:Unix 秒或 ISO 日期(默认:24 小时前) |
|
||||
| `--end <time>` | string | 否 | 范围结束:Unix 秒或 ISO 日期(默认:当前时间) |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
plan: ...
|
||||
storage: 100 / 1000
|
||||
peak qps: 5
|
||||
qps windows: 24 data point(s)
|
||||
```
|
||||
|
||||
quiet 模式:输出 json 格式。
|
||||
|
||||
json 模式:返回 API 原始响应,包含 `storageMonitorData` 和 `qpsMonitorData`。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 默认查询最近 24 小时数据。
|
||||
- 时间戳自动转换为 epoch 秒(API 要求秒级字符串)。13 位毫秒时间戳会自动降为秒。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 查看最近 24 小时监控
|
||||
kscli kb stats --index-id idx-xxx --workspace-id ws-xxx
|
||||
|
||||
# 指定日期范围
|
||||
kscli kb stats --index-id idx-xxx --start 2026-07-30 --end 2026-07-31
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
← [返回总览](./kscli-cli-guide.md)
|
||||
@@ -0,0 +1,929 @@
|
||||
# `kscli` 命令完整用法指南
|
||||
|
||||
> Knowledge Studio CLI(`kscli`)命令总览,覆盖全部 37 个命令:34 个知识库命令 + 3 个配置/维护命令。完整参数与示例请参阅各子域手册。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概述](#概述)
|
||||
2. [核心概念与实体关系](#核心概念与实体关系)
|
||||
3. [通用约定](#通用约定)
|
||||
4. [典型工作流](#典型工作流)
|
||||
5. [命令手册](#命令手册)
|
||||
- [知识库管理](#知识库管理) → [完整手册](kb.md)
|
||||
- [文档管理](#文档管理) → [完整手册](doc.md)
|
||||
- [检索服务管理](#检索服务管理) → [完整手册](service.md)
|
||||
- [Chunk 管理](#chunk-管理) → [完整手册](chunk.md)
|
||||
- [数据中心文件管理](#数据中心文件管理) → [完整手册](file.md)
|
||||
- [数据中心集合与分类](#数据中心集合与分类) → [完整手册](collection-category.md)
|
||||
- [检索与对话](#检索与对话) → [完整手册](search-chat.md)
|
||||
- [配置与维护](#配置与维护)
|
||||
6. [常见错误与排查](#常见错误与排查)
|
||||
7. [附录:命令速查表](#附录命令速查表)
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
`kscli`(`knowledge-studio-cli`)是面向 RAG 开发者的知识库专用 CLI,把知识库能力铺平成一级命令组,覆盖 RAG(检索增强生成)全链路:
|
||||
|
||||
- **知识库全生命周期管理**:创建、查看、更新、删除、监控
|
||||
- **文档管理**:上传本地文件或目录、从 OSS 批量导入、查看解析状态、删除、打标签
|
||||
- **Chunk 级运维**:直接增删改查知识库中的内容切片
|
||||
- **检索服务管理**:创建/部署/复制/删除 Q&A 和检索服务(agent),管理 draft 与发布版本
|
||||
- **数据中心管理**:文件、集合(connector)、分类的增删查
|
||||
- **检索与对话**:语义检索(search)、多轮对话(chat)、兼容旧检索(retrieve)
|
||||
- **配置与维护**:查看/修改本地配置、自更新 CLI
|
||||
|
||||
共 37 个命令:34 个知识库命令(按功能域分为 7 组)+ `config show` / `config set` / `update`。所有知识库命令均使用 DashScope API Key 鉴权。
|
||||
|
||||
> **与 `bl` 的关系**:`kscli` 与 `bl knowledge` 复用同一套命令实现,flag 名、行为逻辑、校验规则完全一致,只有命令路径不同 —— `kscli` 把知识库能力铺平(`kscli kb list`、`kscli file list`),`bl` 则把它们收在 `bl knowledge` 之下。用 `bl` 的读者请参阅 [`bl knowledge` 指南](../knowledge/knowledge-cli-guide.md)。
|
||||
|
||||
安装与运行:
|
||||
|
||||
```bash
|
||||
# 免安装执行(推荐,版本可控)
|
||||
npx knowledge-studio-cli@latest --help
|
||||
|
||||
# 全局安装后使用 kscli
|
||||
npm install -g knowledge-studio-cli
|
||||
kscli --help
|
||||
```
|
||||
|
||||
> 后文示例统一写作 `kscli <command>`;若未全局安装,把 `kscli` 换成 `npx knowledge-studio-cli@latest` 即可。
|
||||
|
||||
---
|
||||
|
||||
## 核心概念与实体关系
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 数据中心 (Data Center) │
|
||||
│ │
|
||||
│ 集合 (Collection) ──┬── 分类 (Category) ── 文件 (File) │
|
||||
│ │ "connector" 可多级嵌套 │
|
||||
│ └── 默认分类 │
|
||||
│ │
|
||||
│ 文件来源:doc upload(本地上传) / doc import-oss(OSS导入) │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
│ 导入 (import job)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 知识库 (Knowledge Base) │
|
||||
│ │
|
||||
│ 知识库 (KB / pipeline / index) │
|
||||
│ ├── 文档 (Doc) ── 解析状态: PENDING/RUNNING/COMPLETED │
|
||||
│ │ └── Chunk ── 内容切片,可增删改查、排除/恢复检索 │
|
||||
│ └── 索引设置 (immutable): 向量模型、切片大小等 │
|
||||
│ │
|
||||
│ 知识库管理命令: kb create / list / info / update / delete / stats │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
│ 绑定 (agent_config.kb_search_configs)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 检索服务 (Service / Agent) │
|
||||
│ │
|
||||
│ Service (agent) │
|
||||
│ ├── scene: chat (Q&A) 或 search (检索) │
|
||||
│ ├── 版本: beta (草稿) → 1, 2, 3... (已发布) │
|
||||
│ ├── 状态: draft → deployed → edited → deleted │
|
||||
│ └── 配置: 模型、温度、策略、rerank 等 │
|
||||
│ │
|
||||
│ 消费方式: search (语义检索) / chat (多轮对话) │
|
||||
│ 管理命令: create / update / deploy / copy / delete / list / get │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**关键关系**:
|
||||
|
||||
- **数据中心文件 → 知识库**:通过 `kscli kb create --doc-id` 或 `kscli doc upload --index-id` 导入,文件解析后自动生成 chunk
|
||||
- **知识库 → 检索服务**:一个服务可绑定多个知识库,服务配置中 `kb_search_configs` 指定关联的知识库 ID
|
||||
- **检索服务 → 检索/对话**:`kscli search` 和 `kscli chat` 通过 `--agent-id` 指定服务来执行检索或对话
|
||||
|
||||
---
|
||||
|
||||
## 通用约定
|
||||
|
||||
### 鉴权
|
||||
|
||||
所有知识库命令均使用 **DashScope API Key**(Bearer token)鉴权。获取方式:百炼控制台 API Key 页面。
|
||||
|
||||
优先级(高 → 低):
|
||||
|
||||
1. `--api-key <key>` 命令行参数
|
||||
2. `DASHSCOPE_API_KEY` 环境变量
|
||||
3. 配置文件中的 `api_key`(`kscli config set --key api_key --value <key>`)
|
||||
|
||||
### Workspace ID
|
||||
|
||||
知识库 API 使用 workspace 级域名(`{workspaceId}.cn-beijing.maas.aliyuncs.com`),因此 **几乎所有知识库命令都需要 workspace ID**。
|
||||
|
||||
优先级(高 → 低):
|
||||
|
||||
1. `--workspace-id <id>` 命令行参数
|
||||
2. `BAILIAN_WORKSPACE_ID` 环境变量
|
||||
3. 配置文件中的 `workspace_id`(`kscli config set --key workspace_id --value <id>`)
|
||||
|
||||
缺失时报错:`Workspace ID is required.`
|
||||
|
||||
### 全局通用参数
|
||||
|
||||
以下参数在所有知识库命令中通用,后续命令手册中不再逐条列出:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| --------------------- | ------ | ----------------------------------------------------------- |
|
||||
| `--output <format>` | string | 输出格式:`text`(默认,人类友好)或 `json`(API 原始响应) |
|
||||
| `--api-key <key>` | string | DashScope API Key |
|
||||
| `--base-url <url>` | string | API 基地址(一般不需要指定) |
|
||||
| `--timeout <seconds>` | number | 请求超时秒数 |
|
||||
| `--quiet` | switch | 静默模式,只输出关键结果(如 ID 列表) |
|
||||
| `--verbose` | switch | 详细模式,打印 HTTP 请求/响应详情到 stderr |
|
||||
| `--dry-run` | switch | 干跑模式,预览将发送的请求结构,不实际调用 API |
|
||||
| `--config <name>` | string | 使用指定配置 profile 执行命令 |
|
||||
|
||||
> **注意**:命令手册中每个命令的参数表只列出该命令**特有**的参数。上述全局参数对所有命令有效。
|
||||
|
||||
### 输出格式约定
|
||||
|
||||
- **text 模式**(默认):人类友好的表格/结构化文本,适合终端查看。不同命令的输出格式见各命令的「输出」部分。
|
||||
- **json 模式**(`--output json`):返回 API 原始 JSON 响应,适合程序化处理和 agent 解析。
|
||||
- **quiet 模式**(`--quiet`):只输出最精简的结果(通常只有 ID),适合管道串联。
|
||||
|
||||
### 危险操作确认
|
||||
|
||||
涉及删除的命令(`kb delete`、`doc delete`、`chunk delete`、`file delete`、`category delete`、`service delete`)以及 `service deploy` 在执行前会弹出二次确认提示。使用 `--yes` 可跳过确认,适用于自动化脚本。
|
||||
|
||||
### Dry-run 模式
|
||||
|
||||
`--dry-run` 模式下,命令会输出将发送的 endpoint 和 request body,但**不实际发起网络请求**。部分命令在 dry-run 下仍会执行本地校验(如文件扩展名检查、参数约束检查)。
|
||||
|
||||
---
|
||||
|
||||
## 典型工作流
|
||||
|
||||
### 场景 A:从零搭建知识库并检索
|
||||
|
||||
```bash
|
||||
# 1. 上传本地文件到数据中心
|
||||
kscli doc upload --file ./docs/intro.md --workspace-id ws-xxx
|
||||
# → 返回 file-id
|
||||
|
||||
# 2. 用文件创建知识库
|
||||
kscli kb create --name my-kb --description '产品文档' --doc-id file-xxx --workspace-id ws-xxx --wait
|
||||
# → 返回 index-id (pipelineId) 和导入任务状态
|
||||
|
||||
# 3. 创建检索服务(search 场景)
|
||||
kscli service create --name my-search --scene search --index-id idx-xxx --workspace-id ws-xxx
|
||||
# → 返回 agent-id
|
||||
|
||||
# 4. 部署服务
|
||||
kscli service deploy --agent-id aid-xxx --workspace-id ws-xxx --yes
|
||||
|
||||
# 5. 执行检索
|
||||
kscli search --query "什么是RAG" --agent-id aid-xxx --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
### 场景 B:上传目录并导入到已有知识库
|
||||
|
||||
```bash
|
||||
# 1. 上传整个目录到数据中心并直接导入到知识库(一步到位)
|
||||
kscli doc upload --file ./docs/ --index-id idx-xxx --workspace-id ws-xxx --wait
|
||||
# → 文件逐个上传到 OSS → 注册到数据中心 → 创建合并导入任务 → 轮询到完成
|
||||
|
||||
# 2. 检查文档状态
|
||||
kscli doc list --index-id idx-xxx --workspace-id ws-xxx
|
||||
# → 查看 doc_id 和解析状态
|
||||
|
||||
# 3. 如果有文档解析失败,查看导入任务详情
|
||||
kscli doc status --index-id idx-xxx --job-id job-xxx --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
### 场景 C:创建并部署 Q&A 服务
|
||||
|
||||
```bash
|
||||
# 1. 创建 chat 场景的检索服务
|
||||
kscli service create --name my-qa --scene chat --index-id idx-xxx --workspace-id ws-xxx
|
||||
# → 初始状态: draft, 版本: beta
|
||||
|
||||
# 2. 调整配置(如修改模型、温度)
|
||||
kscli service update --agent-id aid-xxx --model qwen-max --temperature 0.7 --workspace-id ws-xxx
|
||||
|
||||
# 3. 用 beta 版本测试
|
||||
kscli chat --message "什么是RAG?" --agent-id aid-xxx --agent-version beta --workspace-id ws-xxx
|
||||
|
||||
# 4. 测试通过后发布
|
||||
kscli service deploy --agent-id aid-xxx --version-desc "首版" --workspace-id ws-xxx --yes
|
||||
```
|
||||
|
||||
### 场景 D:知识库内容运维
|
||||
|
||||
```bash
|
||||
# 1. 查看 chunk 列表
|
||||
kscli chunk list --index-id idx-xxx --workspace-id ws-xxx
|
||||
# → 返回 metadata._id (chunk id) 和 metadata.doc_id (document id)
|
||||
|
||||
# 2. 修改 chunk 内容
|
||||
kscli chunk update --index-id idx-xxx --chunk-id chunk-xxx --doc-id doc-xxx --content "修正后的内容" --workspace-id ws-xxx
|
||||
|
||||
# 3. 排除某个 chunk 不参与检索(不删除内容)
|
||||
kscli chunk update --index-id idx-xxx --chunk-id chunk-xxx --doc-id doc-xxx --exclude --workspace-id ws-xxx
|
||||
|
||||
# 4. 手动添加新 chunk
|
||||
kscli chunk add --index-id idx-xxx --content "新增的知识片段" --title "补充说明" --workspace-id ws-xxx
|
||||
|
||||
# 5. 删除 chunk(批量,自动分批每 10 个一组)
|
||||
kscli chunk delete --index-id idx-xxx --chunk-id chunk-a --chunk-id chunk-b --yes --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
### 场景 E:服务迁移/复用
|
||||
|
||||
```bash
|
||||
# 1. 复制现有服务为新草稿
|
||||
kscli service copy --agent-id aid-source --workspace-id ws-xxx
|
||||
# → 返回新的 agent-id,名称加 copy_ 前缀
|
||||
|
||||
# 2. 修改新服务配置
|
||||
kscli service update --agent-id aid-new --name "改进版" --temperature 0.5 --workspace-id ws-xxx
|
||||
|
||||
# 3. 测试并发布
|
||||
kscli chat --message "测试" --agent-id aid-new --agent-version beta --workspace-id ws-xxx
|
||||
kscli service deploy --agent-id aid-new --workspace-id ws-xxx --yes
|
||||
```
|
||||
|
||||
### 场景 F:从 OSS 批量导入文件
|
||||
|
||||
```bash
|
||||
# 1. 从已授权的 OSS bucket 批量导入文件到数据中心
|
||||
kscli doc import-oss \
|
||||
--bucket my-bucket --region cn-beijing \
|
||||
--oss-key docs/a.pdf --oss-key docs/b.docx \
|
||||
--workspace-id ws-xxx
|
||||
# → 返回各文件的 fileId
|
||||
|
||||
# 2. 创建知识库并导入这些文件
|
||||
kscli kb create --name oss-kb --description 'OSS 导入文档' --doc-id file-a --doc-id file-b --workspace-id ws-xxx --wait
|
||||
|
||||
# 3. 检索
|
||||
kscli search --query "相关内容" --agent-id aid-xxx --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 命令手册
|
||||
|
||||
以下按功能域分组,覆盖全部 37 个命令。每个条目包含功能说明、用法签名和详细手册链接。
|
||||
|
||||
> 完整参数表、参数约束、输出说明、注意事项与示例请参阅各子域手册。
|
||||
|
||||
---
|
||||
|
||||
### 知识库管理
|
||||
|
||||
> 📖 [完整手册](kb.md) — 6 个命令
|
||||
|
||||
#### `kscli kb list`
|
||||
|
||||
列出工作区中的知识库。
|
||||
|
||||
```bash
|
||||
kscli kb list [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](kb.md#kscli-kb-list)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb info`
|
||||
|
||||
查看知识库配置详情。
|
||||
|
||||
```bash
|
||||
kscli kb info --index-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](kb.md#kscli-kb-info)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb create`
|
||||
|
||||
创建知识库并导入数据中心文件或分类。
|
||||
|
||||
```bash
|
||||
kscli kb create --name <text> --description <text> (--doc-id <id> | --category-id <id>) [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](kb.md#kscli-kb-create)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb update`
|
||||
|
||||
更新知识库名称、描述或 rerank 阈值。
|
||||
|
||||
```bash
|
||||
kscli kb update --index-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](kb.md#kscli-kb-update)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb delete`
|
||||
|
||||
删除知识库及其所有文档和 chunk。
|
||||
|
||||
```bash
|
||||
kscli kb delete --index-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](kb.md#kscli-kb-delete)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli kb stats`
|
||||
|
||||
查看知识库存储和 QPS 监控数据。
|
||||
|
||||
```bash
|
||||
kscli kb stats --index-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](kb.md#kscli-kb-stats)
|
||||
|
||||
---
|
||||
|
||||
### 文档管理
|
||||
|
||||
> 📖 [完整手册](doc.md) — 6 个命令
|
||||
|
||||
#### `kscli doc list`
|
||||
|
||||
列出知识库中的文档及其解析/索引状态。
|
||||
|
||||
```bash
|
||||
kscli doc list --index-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](doc.md#kscli-doc-list)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc status`
|
||||
|
||||
查看知识库导入任务状态。
|
||||
|
||||
```bash
|
||||
kscli doc status --index-id <id> --job-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](doc.md#kscli-doc-status)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc upload`
|
||||
|
||||
上传本地文件或目录到数据中心,可选导入到知识库。
|
||||
|
||||
```bash
|
||||
kscli doc upload --file <path> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](doc.md#kscli-doc-upload)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc delete`
|
||||
|
||||
从知识库中删除文档及其 chunk。
|
||||
|
||||
```bash
|
||||
kscli doc delete --index-id <id> --doc-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](doc.md#kscli-doc-delete)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc tag`
|
||||
|
||||
批量更新数据中心文件的标签。
|
||||
|
||||
```bash
|
||||
kscli doc tag --doc-id <id> --tag <text> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](doc.md#kscli-doc-tag)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli doc import-oss`
|
||||
|
||||
从已授权的 OSS bucket 批量导入文件到数据中心。
|
||||
|
||||
```bash
|
||||
kscli doc import-oss --bucket <name> --region <id> --oss-key <key> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](doc.md#kscli-doc-import-oss)
|
||||
|
||||
---
|
||||
|
||||
### 检索服务管理
|
||||
|
||||
> 📖 [完整手册](service.md) — 7 个命令
|
||||
|
||||
#### `kscli service list`
|
||||
|
||||
列出工作区中的检索/Q&A 服务。
|
||||
|
||||
```bash
|
||||
kscli service list --scene <chat|search> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](service.md#kscli-service-list)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service get`
|
||||
|
||||
查看服务详情,含各版本配置。
|
||||
|
||||
```bash
|
||||
kscli service get --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](service.md#kscli-service-get)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service create`
|
||||
|
||||
创建检索/Q&A 服务,初始状态为 draft,版本为 beta。
|
||||
|
||||
```bash
|
||||
kscli service create --name <text> --scene <chat|search> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](service.md#kscli-service-create)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service update`
|
||||
|
||||
更新服务名称、描述或草稿配置。
|
||||
|
||||
```bash
|
||||
kscli service update --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](service.md#kscli-service-update)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service deploy`
|
||||
|
||||
发布 beta 草稿为新版本。
|
||||
|
||||
```bash
|
||||
kscli service deploy --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](service.md#kscli-service-deploy)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service delete`
|
||||
|
||||
删除检索/Q&A 服务(软删除,幂等)。
|
||||
|
||||
```bash
|
||||
kscli service delete --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](service.md#kscli-service-delete)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service copy`
|
||||
|
||||
复制服务为新草稿(名称自动加 `copy_` 前缀)。
|
||||
|
||||
```bash
|
||||
kscli service copy --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](service.md#kscli-service-copy)
|
||||
|
||||
---
|
||||
|
||||
### Chunk 管理
|
||||
|
||||
> 📖 [完整手册](chunk.md) — 4 个命令
|
||||
|
||||
#### `kscli chunk add`
|
||||
|
||||
直接向知识库添加 chunk。
|
||||
|
||||
```bash
|
||||
kscli chunk add --index-id <id> (--content <text> | --field <k=v>) [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](chunk.md#kscli-chunk-add)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chunk list`
|
||||
|
||||
列出知识库中的 chunk,含内容和状态。
|
||||
|
||||
```bash
|
||||
kscli chunk list --index-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](chunk.md#kscli-chunk-list)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chunk update`
|
||||
|
||||
更新 chunk 内容或切换其检索可见性。
|
||||
|
||||
```bash
|
||||
kscli chunk update --index-id <id> --chunk-id <id> --doc-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](chunk.md#kscli-chunk-update)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chunk delete`
|
||||
|
||||
从知识库中删除 chunk(不可逆)。
|
||||
|
||||
```bash
|
||||
kscli chunk delete --index-id <id> --chunk-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](chunk.md#kscli-chunk-delete)
|
||||
|
||||
---
|
||||
|
||||
### 数据中心文件管理
|
||||
|
||||
> 📖 [完整手册](file.md) — 3 个命令
|
||||
|
||||
#### `kscli file list`
|
||||
|
||||
列出数据中心分类下的文件。
|
||||
|
||||
```bash
|
||||
kscli file list --category-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](file.md#kscli-file-list)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli file get`
|
||||
|
||||
查看数据中心文件详情。
|
||||
|
||||
```bash
|
||||
kscli file get --file-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](file.md#kscli-file-get)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli file delete`
|
||||
|
||||
从数据中心永久删除文件。
|
||||
|
||||
```bash
|
||||
kscli file delete --file-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](file.md#kscli-file-delete)
|
||||
|
||||
---
|
||||
|
||||
### 数据中心集合与分类
|
||||
|
||||
> 📖 [完整手册](collection-category.md) — 5 个命令
|
||||
|
||||
#### `kscli collection create`
|
||||
|
||||
创建 FILE 数据集合。
|
||||
|
||||
```bash
|
||||
kscli collection create --name <text> --description <text> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](collection-category.md#kscli-collection-create)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli collection get`
|
||||
|
||||
查看数据集合详情。
|
||||
|
||||
```bash
|
||||
kscli collection get (--collection-id <id> | --name <text>) [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](collection-category.md#kscli-collection-get)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli category list`
|
||||
|
||||
列出数据中心分类。
|
||||
|
||||
```bash
|
||||
kscli category list [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](collection-category.md#kscli-category-list)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli category add`
|
||||
|
||||
创建数据中心分类。
|
||||
|
||||
```bash
|
||||
kscli category add --name <text> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](collection-category.md#kscli-category-add)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli category delete`
|
||||
|
||||
删除数据中心分类。
|
||||
|
||||
```bash
|
||||
kscli category delete --category-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](collection-category.md#kscli-category-delete)
|
||||
|
||||
---
|
||||
|
||||
### 检索与对话
|
||||
|
||||
> 📖 [完整手册](search-chat.md) — 3 个命令
|
||||
|
||||
#### `kscli retrieve`
|
||||
|
||||
从知识库检索(已废弃,请用 `search` 替代)。
|
||||
|
||||
```bash
|
||||
kscli retrieve --index-id <id> --query <text> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](search-chat.md#kscli-retrieve)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli search`
|
||||
|
||||
对知识库执行语义检索(RAG 检索)。
|
||||
|
||||
```bash
|
||||
kscli search --query <text> --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](search-chat.md#kscli-search)
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chat`
|
||||
|
||||
与知识库进行 RAG 对话(流式输出)。
|
||||
|
||||
```bash
|
||||
kscli chat --message <text> --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
→ [完整参数与示例](search-chat.md#kscli-chat)
|
||||
|
||||
---
|
||||
|
||||
### 配置与维护
|
||||
|
||||
这 3 个命令不调用知识库 API,用于管理本地配置与 CLI 自身版本。配置文件默认位于 `~/.bailian/config.json`(可用 `BAILIAN_CONFIG_DIR` 改写目录)。
|
||||
|
||||
#### `kscli config show`
|
||||
|
||||
显示当前生效配置(含 base_url、output、timeout、profile 名和配置文件路径;密钥类字段自动脱敏)。
|
||||
|
||||
```bash
|
||||
kscli config show [--output json]
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 查看当前配置
|
||||
kscli config show
|
||||
|
||||
# JSON 输出,便于脚本解析
|
||||
kscli config show --output json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli config set`
|
||||
|
||||
写入一个配置项到配置文件。
|
||||
|
||||
```bash
|
||||
kscli config set --key <key> --value <value>
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--key <key>` | string | 是 | 配置项名称:`language`、`base_url`、`output`、`output_dir`、`timeout`、`api_key`、`access_token`、`access_key_id`、`access_key_secret`、`security_token`、`default_*_model`、`workspace_id` |
|
||||
| `--value <value>` | string | 是 | 要写入的值(按 key 类型校验并转换) |
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 持久化 API Key
|
||||
kscli config set --key api_key --value sk-xxx
|
||||
|
||||
# 持久化 workspace,省去每次传 --workspace-id
|
||||
kscli config set --key workspace_id --value ws-xxx
|
||||
|
||||
# 默认输出 JSON
|
||||
kscli config set --key output --value json
|
||||
```
|
||||
|
||||
**注意事项**
|
||||
|
||||
- `--dry-run` 只打印将写入的键值和配置文件路径,不落盘。
|
||||
- 密钥类字段(`api_key`、`access_token` 等)在回显时被掩码。
|
||||
- 配合 `--config <name>` 可写入指定 profile。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli update`
|
||||
|
||||
将 CLI 自更新到最新版本,或用 `--to` 指定目标版本。
|
||||
|
||||
```bash
|
||||
kscli update [--to <version>]
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | ------ | ---- | ------------------------------------------------------------------------- |
|
||||
| `--to <version>` | string | 否 | 目标版本(semver,如 `1.13.0` / `v1.13.0` / `0.0.0-beta-<sha>-<时间戳>`) |
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# 更新到最新版
|
||||
kscli update
|
||||
|
||||
# 回滚/固定到指定版本
|
||||
kscli update --to 1.13.0
|
||||
```
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 更新方式按安装来源自动选择(npm 全局安装或二进制安装)。
|
||||
- `--to` 传入非法 semver 会在本地被拦截并报错。
|
||||
|
||||
---
|
||||
|
||||
## 常见错误与排查
|
||||
|
||||
### Workspace ID 缺失
|
||||
|
||||
**报错**:`Workspace ID is required.`
|
||||
|
||||
**原因**:所有知识库管理命令都需要 workspace ID 来构造 API 端点(`{workspaceId}.cn-beijing.maas.aliyuncs.com`)。
|
||||
|
||||
**解决**:
|
||||
|
||||
```bash
|
||||
# 方式1:命令行参数
|
||||
kscli kb list --workspace-id ws-xxx
|
||||
|
||||
# 方式2:环境变量
|
||||
export BAILIAN_WORKSPACE_ID=ws-xxx
|
||||
|
||||
# 方式3:配置文件
|
||||
kscli config set --key workspace_id --value ws-xxx
|
||||
```
|
||||
|
||||
### 知识库 ID 不存在
|
||||
|
||||
**报错**:`Knowledge base not found: idx-xxx`
|
||||
|
||||
**原因**:`--index-id` 指定的知识库在当前 workspace 中不存在。
|
||||
|
||||
**解决**:先 `kscli kb list` 确认知识库 ID。
|
||||
|
||||
### 导入任务 SystemError
|
||||
|
||||
**报错**:服务端返回 `SystemError`
|
||||
|
||||
**原因**:`doc status` 传入了不存在的 job ID,或知识库空闲无任务。
|
||||
|
||||
**解决**:检查 `doc list` 输出中的 `ingestionId`,或从 `doc upload` / `kb create` 的返回值获取。
|
||||
|
||||
### doc_id 与 fileId 混淆
|
||||
|
||||
**问题**:`doc delete` 时用了 `doc upload` 返回的 `fileId` 而非 `doc list` 返回的 `doc_id`。
|
||||
|
||||
**原因**:通过 `kb create --doc-id` 导入的文档,`doc_id` 等于 `fileId`;但通过 `doc upload --index-id` 导入的,`doc_id` 可能含 workspace 后缀。
|
||||
|
||||
**解决**:始终用 `kscli doc list --quiet` 获取 `doc_id`。
|
||||
|
||||
### retrieve 已废弃
|
||||
|
||||
**问题**:`retrieve` 命令输出废弃警告。
|
||||
|
||||
**解决**:改用 `search` 命令。`search` 通过 `--agent-id` 驱动检索策略,支持多知识库、路由、rerank 等高级特性。`retrieve` 直接操作 `--index-id`,功能受限且不再迭代。
|
||||
|
||||
### OSS 导入权限错误
|
||||
|
||||
**报错**:服务端返回权限相关错误。
|
||||
|
||||
**原因**:OSS bucket 未授权给平台服务角色。
|
||||
|
||||
**解决**:检查 RAM 控制台中的 `AliyunServiceRoleForBailian` 角色是否已正确授权。
|
||||
|
||||
### Chat SSE error
|
||||
|
||||
**报错**:`Chat API error` + API error code。
|
||||
|
||||
**原因**:流式对话过程中服务端返回 error 事件。
|
||||
|
||||
**解决**:检查 `--agent-id` 是否存在、服务是否已部署、API Key 是否有效。错误消息和 code 原样透传,不二次包装。
|
||||
|
||||
### file list 返回空
|
||||
|
||||
**问题**:`file list --category-id default` 返回空列表。
|
||||
|
||||
**原因**:与上传 API 不同,`file list` 不解析字面量 `default`,需要真实分类 ID。
|
||||
|
||||
**解决**:通过 `file get` 的 category 字段或 `category list` 获取真实分类 ID。
|
||||
|
||||
### 集合无法删除
|
||||
|
||||
**问题**:没有 `collection delete` 命令。
|
||||
|
||||
**原因**:暂不支持通过 CLI 删除。
|
||||
|
||||
**解决**:创建集合需谨慎。如需隔离,创建新集合并迁移文件。
|
||||
|
||||
---
|
||||
|
||||
## 附录:命令速查表
|
||||
|
||||
| 命令 | 功能 | 关键参数 |
|
||||
| ------------------------- | ------------ | ----------------------------------------------------------- |
|
||||
| `kscli kb list` | 列出知识库 | `--name` |
|
||||
| `kscli kb info` | 知识库详情 | `--index-id` |
|
||||
| `kscli kb create` | 创建知识库 | `--name`, `--description`, `--doc-id`/`--category-id` |
|
||||
| `kscli kb update` | 更新知识库 | `--index-id`, `--name`/`--description`/`--rerank-min-score` |
|
||||
| `kscli kb delete` | 删除知识库 | `--index-id`, `--yes` |
|
||||
| `kscli kb stats` | 监控数据 | `--index-id`, `--start`/`--end` |
|
||||
| `kscli doc list` | 文档列表 | `--index-id` |
|
||||
| `kscli doc status` | 导入任务状态 | `--index-id`, `--job-id`, `--wait` |
|
||||
| `kscli doc upload` | 上传文件 | `--file`, `--index-id`, `--wait` |
|
||||
| `kscli doc delete` | 删除文档 | `--index-id`, `--doc-id` |
|
||||
| `kscli doc tag` | 文件打标签 | `--doc-id`, `--tag`, `--mode` |
|
||||
| `kscli doc import-oss` | OSS 导入 | `--bucket`, `--region`, `--oss-key` |
|
||||
| `kscli service list` | 服务列表 | `--scene` |
|
||||
| `kscli service get` | 服务详情 | `--agent-id` |
|
||||
| `kscli service create` | 创建服务 | `--name`, `--scene`, `--index-id` |
|
||||
| `kscli service update` | 更新服务 | `--agent-id`, 配置参数 |
|
||||
| `kscli service deploy` | 发布服务 | `--agent-id`, `--yes` |
|
||||
| `kscli service delete` | 删除服务 | `--agent-id`, `--yes` |
|
||||
| `kscli service copy` | 复制服务 | `--agent-id` |
|
||||
| `kscli chunk add` | 添加 chunk | `--index-id`, `--content`/`--field` |
|
||||
| `kscli chunk list` | chunk 列表 | `--index-id`, `--doc-id` |
|
||||
| `kscli chunk update` | 更新 chunk | `--index-id`, `--chunk-id`, `--doc-id` |
|
||||
| `kscli chunk delete` | 删除 chunk | `--index-id`, `--chunk-id`, `--yes` |
|
||||
| `kscli file list` | 文件列表 | `--category-id` |
|
||||
| `kscli file get` | 文件详情 | `--file-id` |
|
||||
| `kscli file delete` | 删除文件 | `--file-id`, `--yes` |
|
||||
| `kscli collection create` | 创建集合 | `--name`, `--description` |
|
||||
| `kscli collection get` | 集合详情 | `--collection-id`/`--name` |
|
||||
| `kscli category list` | 分类列表 | `--collection-id`, `--parent-id` |
|
||||
| `kscli category add` | 创建分类 | `--name`, `--parent-id` |
|
||||
| `kscli category delete` | 删除分类 | `--category-id`, `--yes` |
|
||||
| `kscli retrieve` | 检索(废弃) | `--index-id`, `--query` |
|
||||
| `kscli search` | 语义检索 | `--query`, `--agent-id` |
|
||||
| `kscli chat` | RAG 对话 | `--message`, `--agent-id` |
|
||||
| `kscli config show` | 查看配置 | `--output` |
|
||||
| `kscli config set` | 写入配置 | `--key`, `--value` |
|
||||
| `kscli update` | 自更新 CLI | `--to` |
|
||||
@@ -0,0 +1,218 @@
|
||||
# 检索与对话命令手册
|
||||
|
||||
以下命令通过检索服务(agent)消费知识库。`search` 用于语义检索,`chat` 用于多轮对话。`retrieve` 已废弃。
|
||||
|
||||
> **通用约定**(鉴权、Workspace ID、全局参数、输出格式、危险操作确认、Dry-run 模式)请参阅 [总览文档](./kscli-cli-guide.md#通用约定)。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli retrieve`
|
||||
|
||||
从知识库检索(已废弃,请用 `search` 替代)。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli retrieve --index-id <id> --query <text> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------------------------- | ------ | ---- | --------------------------------------------------- |
|
||||
| `--index-id <id>` | string | 是 | 知识库 ID |
|
||||
| `--query <text>` | string | 是 | 检索查询文本 |
|
||||
| `--dense-similarity-top-k <n>` | number | 否 | 稠密检索 top K |
|
||||
| `--sparse-similarity-top-k <n>` | number | 否 | 稀疏检索 top K |
|
||||
| `--rerank` | switch | 否 | 启用 rerank |
|
||||
| `--rerank-top-n <n>` | number | 否 | rerank 返回 top N 结果 |
|
||||
| `--rerank-model <name>` | string | 否 | rerank 模型名,如 `qwen3-rerank-hybrid` |
|
||||
| `--rerank-mode <mode>` | string | 否 | rerank 模式:`qa`、`similar` 或 `custom` |
|
||||
| `--rerank-instruct <text>` | string | 否 | 自定义 rerank 指令(`--rerank-mode custom` 时使用) |
|
||||
| `--top-k <n>` | number | 否 | 返回结果数(已废弃,用 `--rerank-top-n` 替代) |
|
||||
|
||||
**输出**
|
||||
|
||||
text/quiet 模式:
|
||||
|
||||
```
|
||||
[1] (score: 0.9512)
|
||||
检索到的文本内容...
|
||||
|
||||
[2] (score: 0.8734)
|
||||
另一段文本内容...
|
||||
```
|
||||
|
||||
> 无结果时输出 `No results found.`
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- **已废弃**,推荐使用 `search` 命令。`search` 通过 agent_id 驱动检索策略,支持更多高级特性。
|
||||
- `--top-k` 已废弃,使用 `--rerank-top-n` 替代,传入 `--top-k` 会输出 stderr 警告。
|
||||
- 此命令直接用 `--index-id` 检索,不需要创建检索服务。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 基础检索
|
||||
kscli retrieve --index-id idx-xxx --query "How to use Alibaba Cloud Bailian" --workspace-id ws-xxx
|
||||
|
||||
# 启用 rerank
|
||||
kscli retrieve --index-id idx-xxx --query "RAG retrieval" --rerank --rerank-model qwen3-rerank-hybrid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli search`
|
||||
|
||||
对知识库执行语义检索(RAG 检索)。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli search --query <text> --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------------------- | ------ | ---- | ------------------------------------------------------------------- |
|
||||
| `--query <text>` | string | 是 | 检索查询文本(不可为空) |
|
||||
| `--agent-id <id>` | string | 是 | 检索服务 ID(在控制台知识检索页面获取,或通过 `service list` 查看) |
|
||||
| `--agent-version <version>` | string | 否 | 服务版本:`beta`(调试草稿)或已发布版本号;默认调用最新已发布版本 |
|
||||
| `--image <url>` | array | 否 | 图片 URL(可重复),用于多模态检索 |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--query` 不可为空(API 要求 `minLength: 1`)
|
||||
|
||||
**输出**
|
||||
|
||||
text/quiet 模式:
|
||||
|
||||
```
|
||||
[1] (score: 0.9512)
|
||||
检索到的文本内容...
|
||||
|
||||
[2] (score: 0.8734)
|
||||
另一段文本内容...
|
||||
```
|
||||
|
||||
> 无结果时输出 `No results found.`
|
||||
|
||||
json 模式:返回 API 原始响应,`data.nodes[]` 包含检索结果。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 检索范围和策略(多知识库加权、路由、rerank 等)由 `--agent-id` 对应的服务配置驱动。只需 `--query` 和 `--agent-id` 即可调用。
|
||||
- `--agent-version beta` 调试草稿配置进行调试,部署前验证效果。
|
||||
- 与 `retrieve` 的区别:`search` 通过 agent_id 间接驱动检索策略(支持多知识库、路由、rerank 等),`retrieve` 直接操作 index_id 且功能较少。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 基础检索
|
||||
kscli search --query "What is RAG?" --agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
# 多模态检索(带图片)
|
||||
kscli search --query "describe this image" --agent-id aid-xxx --workspace-id ws-xxx --image https://example.com/img.jpg
|
||||
|
||||
# 调试草稿版本
|
||||
kscli search --query "test" --agent-id aid-xxx --agent-version beta --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli chat`
|
||||
|
||||
与知识库进行 RAG 对话(流式输出)。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli chat --message <text> --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------------------- | ------ | ---- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `--message <text>` | array | 是¹ | 消息文本(可重复)。支持 `role:content` 前缀设置角色(如 `user:hello`),默认角色为 `user`。也支持完整 JSON 对象传递结构化消息 |
|
||||
| `--agent-id <id>` | string | 是 | Q&A 服务 ID(在控制台知识问答页面获取,或通过 `service list --scene chat` 查看) |
|
||||
| `--agent-version <version>` | string | 否 | 服务版本:`beta`(调试草稿)或已发布版本号;默认调用最新已发布版本 |
|
||||
| `--image <url>` | array | 否 | 图片 URL(可重复)。附加到最后一条 user 消息作为多模态内容 |
|
||||
|
||||
> ¹ `--message` 或 `--image` 至少提供其一。纯图片查询可以只传 `--image`(CLI 会自动创建空 user 消息承载图片)。
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--message` 或 `--image` 至少提供一个
|
||||
- `--image` 不能与已包含 `image_url` 内容部分的消息同时使用
|
||||
|
||||
**输出**
|
||||
|
||||
**TTY text 模式**(实时流式):
|
||||
|
||||
```
|
||||
🔍 Retrieving...
|
||||
✍️ Generating...
|
||||
这是AI生成的回答内容,逐字流式输出...
|
||||
```
|
||||
|
||||
> 进度标签由 SSE `step_change` 事件驱动:`tool_calling`(检索中)→ `plan_start`(规划中)→ `generation_start`(生成中)。
|
||||
|
||||
**非 TTY text 模式**(缓冲输出):
|
||||
|
||||
```
|
||||
完整的回答文本...
|
||||
```
|
||||
|
||||
**json 模式**(`--output json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"answer": "完整的回答文本...",
|
||||
"request_id": "xxx"
|
||||
}
|
||||
```
|
||||
|
||||
quiet 模式:输出完整的回答文本。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- API 仅支持 SSE 流式响应。TTY 环境下实时打印 token;非 TTY 环境缓冲后输出完整文本。
|
||||
- SSE 事件生命周期:`tool_calling` → `tool_return` → `plan_start` → `planning` → `plan_end` → `generation_start` → `generating` → `generation_end`。`tool_calling` → `tool_return` 可能循环多次。
|
||||
- 多轮对话:用 `--message "user:..."` 和 `--message "assistant:..."` 传递对话历史。
|
||||
- `--agent-version beta` 调用草稿配置进行调试。
|
||||
- `--image` 附加到最后一条 user 消息上。如果消息中已包含 `image_url` 内容部分,则不能再用 `--image`。
|
||||
- `--verbose` 模式下,所有 SSE 事件详情会输出到 stderr。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 单轮对话
|
||||
kscli chat --message "What is RAG?" --agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
# 多轮对话(带历史)
|
||||
kscli chat \
|
||||
--message "user:What is RAG?" \
|
||||
--message "assistant:RAG is retrieval-augmented generation..." \
|
||||
--message "How does it work?" \
|
||||
--agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
# 多模态对话(带图片)
|
||||
kscli chat \
|
||||
--message "Describe these images" \
|
||||
--image https://example.com/a.png \
|
||||
--image https://example.com/b.png \
|
||||
--agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
# 调试草稿版本
|
||||
kscli chat --message "test" --agent-id aid-xxx --agent-version beta --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
← [返回总览](./kscli-cli-guide.md)
|
||||
@@ -0,0 +1,401 @@
|
||||
# 检索服务管理命令手册
|
||||
|
||||
检索服务(也称 agent)是知识库的检索入口。通过 `--agent-id` 在 search/chat 命令中使用。服务有 `chat`(问答)和 `search`(检索)两种场景。
|
||||
|
||||
> **通用约定**(鉴权、Workspace ID、全局参数、输出格式、危险操作确认、Dry-run 模式)请参阅 [总览文档](./kscli-cli-guide.md#通用约定)。
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service list`
|
||||
|
||||
列出工作区中的检索/Q&A 服务。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli service list --scene <chat|search> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------------------ | ------ | ---- | ------------------------------------------------------- |
|
||||
| `--scene <chat\|search>` | string | 是 | 服务场景:`chat`(Q&A)或 `search`(检索) |
|
||||
| `--status <status>` | string | 否 | 按状态过滤:`draft`、`deployed`(含 edited)、`deleted` |
|
||||
| `--name <text>` | string | 否 | 按服务名称模糊过滤 |
|
||||
| `--agent-id <id>` | string | 否 | 按精确 agent ID 过滤 |
|
||||
| `--index-id <id>` | string | 否 | 按关联知识库 ID 过滤 |
|
||||
| `--page-number <n>` | number | 否 | 页码(默认:1) |
|
||||
| `--page-size <n>` | number | 否 | 每页条数(默认:10,最大 100) |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--scene` 只能是 `chat` 或 `search`
|
||||
- `--status` 只能是 `draft`、`deployed`、`deleted`
|
||||
- `--page-size` 范围 1-100
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
aid-xxx deployed 2 my-qa (kb: my-kb)
|
||||
total: 1
|
||||
Use an agent_id above with the chat command.
|
||||
```
|
||||
|
||||
> 最后一行根据 scene 自动提示用 `search` 还是 `chat` 命令消费。
|
||||
|
||||
quiet 模式:每行一个 `agent_id`。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 服务端要求 `--scene` 必填,要查看两种场景的服务需分别执行。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 列出 chat 服务
|
||||
kscli service list --scene chat --workspace-id ws-xxx
|
||||
|
||||
# 只看已部署的检索服务
|
||||
kscli service list --scene search --status deployed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service get`
|
||||
|
||||
查看服务详情,含各版本配置。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli service get --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| --------------------------- | ------ | ---- | --------------------------------------------------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--agent-version <version>` | string | 否 | 指定版本查看(`beta` 或已发布版本号);不传则返回所有版本 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
Basic:
|
||||
id: aid-xxx
|
||||
name: my-qa
|
||||
desc: product Q&A
|
||||
scene: chat
|
||||
status: deployed
|
||||
Version beta:
|
||||
desc: draft
|
||||
policy: turbo
|
||||
model: qwen-max
|
||||
temperature: 0.7
|
||||
kb: idx-xxx (my-kb)
|
||||
Version 1:
|
||||
published: 2026-01-01
|
||||
...
|
||||
```
|
||||
|
||||
quiet 模式:输出 JSON 格式。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 不传 `--agent-version` 时返回所有版本(beta 草稿 + 已发布版本号)。
|
||||
- 版本值原样传递,有效值集合由服务端维护。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 查看服务完整详情
|
||||
kscli service get --agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
# 只看 beta 草稿配置
|
||||
kscli service get --agent-id aid-xxx --agent-version beta
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service create`
|
||||
|
||||
创建检索/Q&A 服务,初始状态为 draft,版本为 beta。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli service create --name <text> --scene <chat|search> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------------------ | ------ | ---- | ------------------------------------------------- |
|
||||
| `--name <text>` | string | 是 | 服务名称(最多 200 字符,同一场景下工作区内唯一) |
|
||||
| `--scene <chat\|search>` | string | 是 | 服务场景:`chat`(Q&A)或 `search`(检索) |
|
||||
| `--description <text>` | string | 建议 | 这个服务能回答什么、给谁用(最多 1000 字符) |
|
||||
| `--index-id <id>` | string | 否 | 绑定此知识库;其他配置使用服务端默认值 |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- `--name` 最多 200 字符
|
||||
- `--scene` 只能是 `chat` 或 `search`
|
||||
- `--description` 最多 1000 字符;建议填写 —— agent 靠它判断该调用哪个服务
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
created: aid-xxx (status: draft, version: beta)
|
||||
Test the draft with --agent-version beta on search/chat, then deploy it to publish.
|
||||
```
|
||||
|
||||
quiet 模式:输出 agent ID。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 不指定 `--index-id` 时,服务端使用默认 agent 配置。
|
||||
- beta 草稿可通过 search/chat 的 `--agent-version beta` 测试,部署后才生效。
|
||||
- 需要工作区的知识库创建权限。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 创建 Q&A 服务
|
||||
kscli service create --name my-qa --scene chat --workspace-id ws-xxx
|
||||
|
||||
# 创建检索服务并绑定知识库
|
||||
kscli service create --name my-search --scene search --index-id idx-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service update`
|
||||
|
||||
更新服务名称、描述或草稿配置。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli service update --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------------------------ | ------ | ---- | ---------------------------------------------------------------------------------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--name <text>` | string | 否 | 新名称(最多 200 字符) |
|
||||
| `--description <text>` | string | 否 | 新描述(最多 1000 字符) |
|
||||
| `--agent-version <version>` | string | 否 | 目标版本(默认:beta 草稿。已发布版本只接受 `--version-desc`) |
|
||||
| `--version-desc <text>` | string | 否 | 版本描述 |
|
||||
| `--policy <policy>` | string | 否 | Agent 策略:`turbo`(快速)或 `agentic`(多轮) |
|
||||
| `--model <name>` | string | 否 | 生成模型代码(须在平台白名单中) |
|
||||
| `--temperature <n>` | number | 否 | 采样温度,范围 0-2 |
|
||||
| `--max-llm-calls <n>` | number | 否 | 单次请求最大 LLM 调用次数,范围 1-30 |
|
||||
| `--enable-session-file <bool>` | string | 否 | 启用会话文件:`true` 或 `false` |
|
||||
| `--enable-refusal <bool>` | string | 否 | 启用拒答:`true` 或 `false` |
|
||||
| `--enable-anti-leak <bool>` | string | 否 | 启用防泄漏:`true` 或 `false` |
|
||||
| `--enable-rich-text <bool>` | string | 否 | 启用富文本输出:`true` 或 `false` |
|
||||
| `--enable-citation <bool>` | string | 否 | 启用引用标注:`true` 或 `false` |
|
||||
| `--config-file <path>` | string | 否 | JSON 文件替换整个 `agent_config`(含嵌套设置如 `kb_search_configs`);与标量配置参数互斥 |
|
||||
|
||||
**参数约束**
|
||||
|
||||
- 至少提供一个更新项(`--name`/`--description`/`--version-desc`/`--config-file`/标量配置参数),否则报错 "Nothing to update"
|
||||
- `--config-file` 与标量配置参数(`--policy`/`--model`/`--temperature` 等)互斥
|
||||
- 已发布版本 + 配置变更 → 报错(已发布版本只接受 `--version-desc`)
|
||||
- `--name` 最多 200 字符;`--description` 最多 1000 字符
|
||||
- `--policy` 只能是 `turbo` 或 `agentic`
|
||||
- `--temperature` 范围 0-2
|
||||
- `--max-llm-calls` 范围 1-30
|
||||
- 布尔参数(`--enable-*`)只能是 `true` 或 `false`
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
updated: aid-xxx
|
||||
Draft config changed — verify with --agent-version beta, then deploy.
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 配置变更只作用于 beta 草稿;已发布版本只接受 `--version-desc`。
|
||||
- 标量配置参数采用 read-merge-write:CLI 先读取当前 beta 配置,再合并变更后整体提交(API 是整替换语义)。
|
||||
- `--config-file` 替换整个配置,适合设置嵌套字段(如 `kb_search_configs`)。
|
||||
- 修改草稿后用 `--agent-version beta` 在 search/chat 上测试,通过后 `service deploy` 发布。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 调整温度
|
||||
kscli service update --agent-id aid-xxx --temperature 0.7 --workspace-id ws-xxx
|
||||
|
||||
# 用 JSON 文件替换整个配置
|
||||
kscli service update --agent-id aid-xxx --config-file ./agent-config.json
|
||||
|
||||
# 给已发布版本 1 加描述
|
||||
kscli service update --agent-id aid-xxx --agent-version 1 --version-desc "first stable release"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service deploy`
|
||||
|
||||
发布 beta 草稿为新版本。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli service deploy --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ------ | ---- | ---------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--version-desc <text>` | string | 否 | 新版本的描述说明 |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
deployed: aid-xxx version 2
|
||||
```
|
||||
|
||||
quiet 模式:输出新版本号。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 版本号自动递增,状态变为 `deployed`。
|
||||
- 发布影响线上调用方,确认提示会警告。
|
||||
- 如果当前状态为 `edited`(已发布后又改了草稿),确认提示会额外警告「发布会覆盖线上行为」。
|
||||
- 需要工作区的知识库修改权限。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 发布(交互确认)
|
||||
kscli service deploy --agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
# 带描述并跳过确认
|
||||
kscli service deploy --agent-id aid-xxx --version-desc "tuned rerank params" --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service delete`
|
||||
|
||||
删除检索/Q&A 服务(软删除,幂等)。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli service delete --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | --------------- |
|
||||
| `--agent-id <id>` | string | 是 | 服务(agent)ID |
|
||||
| `--yes` | switch | 否 | 跳过确认提示 |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
deleted: aid-xxx (status: deleted)
|
||||
```
|
||||
|
||||
quiet 模式:无输出。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 删除不可撤销,`agent_id` 不再可用于 search/chat 调用。
|
||||
- API 是幂等的:删除已删除的服务不会报错。
|
||||
- 如果服务状态为 `deployed` 或 `edited`,确认提示会额外警告「此服务正在线上运行」。
|
||||
- 需要工作区的知识库删除权限。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 删除(交互确认)
|
||||
kscli service delete --agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
# 跳过确认
|
||||
kscli service delete --agent-id aid-xxx --yes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `kscli service copy`
|
||||
|
||||
复制服务为新草稿(名称自动加 `copy_` 前缀)。
|
||||
|
||||
**用法**
|
||||
|
||||
```bash
|
||||
kscli service copy --agent-id <id> [flags]
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ----------------- | ------ | ---- | ----------------- |
|
||||
| `--agent-id <id>` | string | 是 | 源服务(agent)ID |
|
||||
|
||||
**输出**
|
||||
|
||||
text 模式:
|
||||
|
||||
```
|
||||
new agent_id: aid-new (name: copy_my-qa, status: draft)
|
||||
Test the draft with --agent-version beta on search/chat, then deploy it to publish.
|
||||
```
|
||||
|
||||
quiet 模式:输出新 agent ID。
|
||||
|
||||
json 模式:返回 API 原始响应。
|
||||
|
||||
**注意事项**
|
||||
|
||||
- 副本初始为 beta 草稿,测试后需 deploy 发布。
|
||||
- 需要工作区的知识库创建权限。
|
||||
|
||||
**示例**
|
||||
|
||||
```bash
|
||||
# 复制服务
|
||||
kscli service copy --agent-id aid-source --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
← [返回总览](./kscli-cli-guide.md)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli",
|
||||
"version": "1.17.0",
|
||||
"version": "1.17.1",
|
||||
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
|
||||
"keywords": [
|
||||
"agent",
|
||||
|
||||
@@ -139,6 +139,14 @@ import {
|
||||
managedAgentPlan,
|
||||
managedAgentApply,
|
||||
managedAgentDestroy,
|
||||
managedAgentWorkbench,
|
||||
managedAgentPlayground,
|
||||
managedAgentVersionEnable,
|
||||
managedAgentVersionDisable,
|
||||
managedAgentVersionStatus,
|
||||
managedAgentVersionList,
|
||||
managedAgentVersionPreview,
|
||||
managedAgentVersionRestore,
|
||||
managedAgentStateList,
|
||||
managedAgentStateShow,
|
||||
managedAgentStateRm,
|
||||
@@ -300,6 +308,14 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"managed-agent plan": managedAgentPlan,
|
||||
"managed-agent apply": managedAgentApply,
|
||||
"managed-agent destroy": managedAgentDestroy,
|
||||
"managed-agent workbench": managedAgentWorkbench,
|
||||
"managed-agent playground": managedAgentPlayground,
|
||||
"managed-agent version enable": managedAgentVersionEnable,
|
||||
"managed-agent version disable": managedAgentVersionDisable,
|
||||
"managed-agent version status": managedAgentVersionStatus,
|
||||
"managed-agent version list": managedAgentVersionList,
|
||||
"managed-agent version preview": managedAgentVersionPreview,
|
||||
"managed-agent version restore": managedAgentVersionRestore,
|
||||
"managed-agent state list": managedAgentStateList,
|
||||
"managed-agent state show": managedAgentStateShow,
|
||||
"managed-agent state rm": managedAgentStateRm,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-commands",
|
||||
"version": "1.17.0",
|
||||
"version": "1.17.1",
|
||||
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
@@ -40,7 +40,8 @@
|
||||
"check": "vp check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openagentpack/sdk": "0.3.2",
|
||||
"@openagentpack/local-git": "0.4.0",
|
||||
"@openagentpack/sdk": "0.4.0",
|
||||
"bailian-cli-core": "workspace:*",
|
||||
"bailian-cli-runtime": "workspace:*",
|
||||
"boxen": "catalog:",
|
||||
|
||||
@@ -20,8 +20,9 @@ const COLLECTION_CREATE_FLAGS = {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: {
|
||||
"en-US": "Collection description (required by the server)",
|
||||
"zh-CN": "数据集合描述(服务端必填)",
|
||||
"en-US":
|
||||
"What this collection holds and what it is for — tells collections apart in the list",
|
||||
"zh-CN": "数据集合装了什么内容、给谁用,用于在列表中区分同类集合",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
||||
@@ -29,6 +29,16 @@ const KB_CREATE_FLAGS = {
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: {
|
||||
"en-US":
|
||||
"What this knowledge base holds and what it is for — tells bases apart in the workspace list (1-500 chars)",
|
||||
"zh-CN": "知识库装了什么内容、给谁用,用于在 Workspace 列表中区分同类知识库(1–500 个字符)",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
docId: {
|
||||
type: "array",
|
||||
valueHint: "<id>",
|
||||
@@ -107,7 +117,7 @@ export default defineCommand({
|
||||
"zh-CN": "创建知识库并导入数据中心文件或类目",
|
||||
},
|
||||
auth: "apiKey",
|
||||
usageArgs: "--name <text> (--doc-id <id> | --category-id <id>) [flags]",
|
||||
usageArgs: "--name <text> --description <text> (--doc-id <id> | --category-id <id>) [flags]",
|
||||
flags: KB_CREATE_FLAGS,
|
||||
notes: [
|
||||
{
|
||||
@@ -126,11 +136,20 @@ export default defineCommand({
|
||||
},
|
||||
],
|
||||
exampleArgs: [
|
||||
"--name demo --doc-id file-xxx --workspace-id ws-xxx",
|
||||
"--name demo --category-id cate-xxx --wait",
|
||||
{
|
||||
"en-US": "--name demo --description 'product docs' --doc-id file-xxx --workspace-id ws-xxx",
|
||||
"zh-CN": "--name demo --description '产品文档' --doc-id file-xxx --workspace-id ws-xxx",
|
||||
},
|
||||
{
|
||||
"en-US": "--name demo --description 'product docs' --category-id cate-xxx --wait",
|
||||
"zh-CN": "--name demo --description '产品文档' --category-id cate-xxx --wait",
|
||||
},
|
||||
],
|
||||
validate(flags) {
|
||||
if (flags.name.length < 1 || flags.name.length > 20) return "--name must be 1-20 characters";
|
||||
if (flags.description.length < 1 || flags.description.length > 500) {
|
||||
return "--description must be 1-500 characters";
|
||||
}
|
||||
const hasDocIds = !!flags.docId?.length;
|
||||
const hasCategoryIds = !!flags.categoryId?.length;
|
||||
if (hasDocIds && hasCategoryIds) return "Use either --doc-id or --category-id, not both";
|
||||
@@ -147,6 +166,9 @@ export default defineCommand({
|
||||
// Note: the public docs' example uses sinkType DEFAULT, but BUILT_IN is what works against the live API.
|
||||
const body = {
|
||||
name: flags.name,
|
||||
// description is a required field; length limit is 1-500 (the public API docs
|
||||
// still list it as absent from CreateIndexV2Request.required).
|
||||
description: flags.description,
|
||||
structureType: "unstructured",
|
||||
sinkType: "BUILT_IN",
|
||||
embeddingModelName: flags.embeddingModel ?? "text-embedding-v4",
|
||||
|
||||
@@ -82,6 +82,13 @@ export default defineCommand({
|
||||
auth: "apiKey",
|
||||
usageArgs: "--index-id <id> --query <text> [flags]",
|
||||
flags: RETRIEVE_FLAGS,
|
||||
notes: [
|
||||
{
|
||||
"en-US":
|
||||
"--rerank-model requires the target knowledge base to already have a rerank model configured; otherwise every value is rejected.",
|
||||
"zh-CN": "--rerank-model 要求目标知识库已配置重排序模型,否则任何取值都会被拒绝。",
|
||||
},
|
||||
],
|
||||
exampleArgs: [
|
||||
{
|
||||
"en-US": '--index-id idx_xxx --query "How to use Alibaba Cloud Bailian"',
|
||||
|
||||
@@ -32,8 +32,10 @@ const SERVICE_CREATE_FLAGS = {
|
||||
type: "string",
|
||||
valueHint: "<text>",
|
||||
description: {
|
||||
"en-US": "Service description (up to 1000 chars)",
|
||||
"zh-CN": "服务描述(最多 1000 个字符)",
|
||||
"en-US":
|
||||
"What this service answers and who it serves — recommended: agents read it to pick the right service (up to 1000 chars)",
|
||||
"zh-CN":
|
||||
"这个服务能回答什么、给谁用 —— 建议填写:agent 靠它判断该调用哪个服务(最多 1000 个字符)",
|
||||
},
|
||||
},
|
||||
indexId: {
|
||||
@@ -71,7 +73,11 @@ export default defineCommand({
|
||||
},
|
||||
],
|
||||
exampleArgs: [
|
||||
"--name my-qa --scene chat --workspace-id ws-xxx",
|
||||
{
|
||||
"en-US":
|
||||
"--name my-qa --scene chat --description 'answers product FAQs' --workspace-id ws-xxx",
|
||||
"zh-CN": "--name my-qa --scene chat --description '回答产品常见问题' --workspace-id ws-xxx",
|
||||
},
|
||||
"--name my-search --scene search --index-id idx-xxx",
|
||||
],
|
||||
validate(flags) {
|
||||
|
||||
@@ -14,8 +14,8 @@ const SERVICE_LIST_FLAGS = {
|
||||
type: "string",
|
||||
valueHint: "<scene>",
|
||||
description: {
|
||||
"en-US": "Service scene: chat (Q&A) or search (retrieval). Required by the server",
|
||||
"zh-CN": "服务场景:chat(问答)或 search(检索),服务端必填",
|
||||
"en-US": "Service scene: chat (Q&A) or search (retrieval)",
|
||||
"zh-CN": "服务场景:chat(问答)或 search(检索)",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
||||
@@ -201,6 +201,15 @@ const KNOWN_CONFIG_KEYS = new Set([
|
||||
"session_file_max_parse_length",
|
||||
"enable_kb_router",
|
||||
"kb_router_model",
|
||||
"user_system_prompt",
|
||||
"anti_leak_prompt",
|
||||
"refusal_prompt",
|
||||
"credibility_prompt",
|
||||
"enable_thinking",
|
||||
"enable_temperature",
|
||||
"enable_credibility",
|
||||
"enable_max_completion_tokens",
|
||||
"session_file_parse_mode",
|
||||
"rerank_top_n",
|
||||
"hybrid_rerank",
|
||||
"kb_search_configs",
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
||||
import { basename, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { BailianError, ExitCode } from "bailian-cli-core";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
const REPOSITORY_GITIGNORE = `# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Bailian CLI local runs
|
||||
.openagentpack/state/
|
||||
.openagentpack/runs/
|
||||
|
||||
# Local credentials
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
`;
|
||||
|
||||
const REPOSITORY_GITIGNORE_PATTERNS = [
|
||||
"node_modules/",
|
||||
".openagentpack/state/",
|
||||
".openagentpack/runs/",
|
||||
".env",
|
||||
".env.*",
|
||||
"!.env.example",
|
||||
] as const;
|
||||
|
||||
const PROJECT_SCRIPTS = {
|
||||
"agents:validate": "bl managed-agent validate --file agents.yaml",
|
||||
"agents:plan": "bl managed-agent plan --file agents.yaml",
|
||||
"agents:plan:ci": "bl managed-agent plan --file agents.yaml --output json",
|
||||
"agents:apply:ci": "bl managed-agent apply --file agents.yaml --ci",
|
||||
"agents:workbench": "bl managed-agent workbench --file agents.yaml",
|
||||
} as const;
|
||||
|
||||
const INITIAL_STATE = `${JSON.stringify({ resources: [] }, null, 2)}\n`;
|
||||
|
||||
export interface GitProjectResult {
|
||||
targetDirectory: string;
|
||||
mode: "created" | "upgraded";
|
||||
initializedGit: boolean;
|
||||
createdFiles: string[];
|
||||
updatedFiles: string[];
|
||||
preservedFiles: string[];
|
||||
}
|
||||
|
||||
interface CreateGitProjectOptions {
|
||||
config: string;
|
||||
cliVersion: string;
|
||||
}
|
||||
|
||||
type ProjectTargetMode = "new" | "existing";
|
||||
|
||||
export async function inspectGitProjectTarget(targetDirectory: string): Promise<ProjectTargetMode> {
|
||||
if (!existsSync(targetDirectory)) return "new";
|
||||
const targetStat = await stat(targetDirectory);
|
||||
if (!targetStat.isDirectory()) {
|
||||
throw new BailianError(`Target '${targetDirectory}' is not a directory.`, ExitCode.USAGE);
|
||||
}
|
||||
const entries = await readdir(targetDirectory);
|
||||
if (entries.length === 0) return "new";
|
||||
if (existsSync(resolve(targetDirectory, "agents.yaml"))) return "existing";
|
||||
throw new BailianError(
|
||||
`Target directory '${targetDirectory}' is not empty and does not contain agents.yaml.`,
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
|
||||
export async function createGitProject(
|
||||
directory: string,
|
||||
options: CreateGitProjectOptions,
|
||||
): Promise<GitProjectResult> {
|
||||
const targetDirectory = resolve(directory);
|
||||
const targetMode = await inspectGitProjectTarget(targetDirectory);
|
||||
const shouldInitializeGit = !existsSync(resolve(targetDirectory, ".git"));
|
||||
if (shouldInitializeGit) await assertGitAvailable();
|
||||
|
||||
const createdFiles: string[] = [];
|
||||
const updatedFiles: string[] = [];
|
||||
const preservedFiles: string[] = [];
|
||||
await mkdir(resolve(targetDirectory, ".aoneci"), { recursive: true });
|
||||
|
||||
const config =
|
||||
targetMode === "new"
|
||||
? options.config
|
||||
: await readFile(resolve(targetDirectory, "agents.yaml"), "utf8");
|
||||
if (targetMode === "new") {
|
||||
await writeFile(resolve(targetDirectory, "agents.yaml"), config, "utf8");
|
||||
createdFiles.push("agents.yaml");
|
||||
} else {
|
||||
preservedFiles.push("agents.yaml");
|
||||
}
|
||||
|
||||
await mergeOrCreateTextFile(
|
||||
resolve(targetDirectory, ".gitignore"),
|
||||
REPOSITORY_GITIGNORE,
|
||||
mergeGitignore,
|
||||
".gitignore",
|
||||
createdFiles,
|
||||
updatedFiles,
|
||||
);
|
||||
await mergeOrCreateTextFile(
|
||||
resolve(targetDirectory, ".env.example"),
|
||||
environmentExample(config),
|
||||
(current) => mergeEnvironmentExample(current, config),
|
||||
".env.example",
|
||||
createdFiles,
|
||||
updatedFiles,
|
||||
);
|
||||
|
||||
const packagePath = resolve(targetDirectory, "package.json");
|
||||
if (existsSync(packagePath)) {
|
||||
const current = await readFile(packagePath, "utf8");
|
||||
const merged = mergePackageJson(current, basename(targetDirectory), options.cliVersion);
|
||||
if (merged.content !== current) {
|
||||
await writeFile(packagePath, merged.content, "utf8");
|
||||
updatedFiles.push("package.json");
|
||||
}
|
||||
preservedFiles.push(...merged.preservedSettings);
|
||||
} else {
|
||||
await writeFile(
|
||||
packagePath,
|
||||
buildPackageJson(basename(targetDirectory), options.cliVersion),
|
||||
"utf8",
|
||||
);
|
||||
createdFiles.push("package.json");
|
||||
}
|
||||
|
||||
await createIfMissing(
|
||||
resolve(targetDirectory, "agents.state.json"),
|
||||
INITIAL_STATE,
|
||||
"agents.state.json",
|
||||
createdFiles,
|
||||
preservedFiles,
|
||||
);
|
||||
await createIfMissing(
|
||||
resolve(targetDirectory, ".aoneci/bailian-cli.yml"),
|
||||
buildAoneWorkflow(config),
|
||||
".aoneci/bailian-cli.yml",
|
||||
createdFiles,
|
||||
preservedFiles,
|
||||
);
|
||||
await createIfMissing(
|
||||
resolve(targetDirectory, ".aoneci/bailian-cli-check.yml"),
|
||||
buildAoneCheckWorkflow(config),
|
||||
".aoneci/bailian-cli-check.yml",
|
||||
createdFiles,
|
||||
preservedFiles,
|
||||
);
|
||||
await createIfMissing(
|
||||
resolve(targetDirectory, "README.md"),
|
||||
buildReadme(basename(targetDirectory), config),
|
||||
"README.md",
|
||||
createdFiles,
|
||||
preservedFiles,
|
||||
);
|
||||
|
||||
if (shouldInitializeGit) await initializeGitRepository(targetDirectory);
|
||||
return {
|
||||
targetDirectory,
|
||||
mode: targetMode === "new" ? "created" : "upgraded",
|
||||
initializedGit: shouldInitializeGit,
|
||||
createdFiles,
|
||||
updatedFiles,
|
||||
preservedFiles,
|
||||
};
|
||||
}
|
||||
|
||||
async function assertGitAvailable(): Promise<void> {
|
||||
try {
|
||||
await execFileAsync("git", ["--version"]);
|
||||
} catch {
|
||||
throw new BailianError(
|
||||
"Git is required to initialize a repository.",
|
||||
ExitCode.USAGE,
|
||||
"Install Git and retry.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeGitRepository(targetDirectory: string): Promise<void> {
|
||||
try {
|
||||
await execFileAsync("git", ["init", "--initial-branch", "main"], {
|
||||
cwd: targetDirectory,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new BailianError(
|
||||
`Could not initialize the local Git repository: ${message}`,
|
||||
ExitCode.GENERAL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function mergeOrCreateTextFile(
|
||||
path: string,
|
||||
initialContent: string,
|
||||
merge: (current: string) => string,
|
||||
label: string,
|
||||
createdFiles: string[],
|
||||
updatedFiles: string[],
|
||||
): Promise<void> {
|
||||
if (!existsSync(path)) {
|
||||
await writeFile(path, initialContent, "utf8");
|
||||
createdFiles.push(label);
|
||||
return;
|
||||
}
|
||||
const current = await readFile(path, "utf8");
|
||||
const next = merge(current);
|
||||
if (next !== current) {
|
||||
await writeFile(path, next, "utf8");
|
||||
updatedFiles.push(label);
|
||||
}
|
||||
}
|
||||
|
||||
async function createIfMissing(
|
||||
path: string,
|
||||
content: string,
|
||||
label: string,
|
||||
createdFiles: string[],
|
||||
preservedFiles: string[],
|
||||
): Promise<void> {
|
||||
if (existsSync(path)) {
|
||||
preservedFiles.push(label);
|
||||
return;
|
||||
}
|
||||
await writeFile(path, content, "utf8");
|
||||
createdFiles.push(label);
|
||||
}
|
||||
|
||||
function mergeGitignore(content: string): string {
|
||||
const repositoryContent = content
|
||||
.split(/\r?\n/)
|
||||
.filter((line) => line.trim() !== "agents.state.json")
|
||||
.join("\n");
|
||||
const existingPatterns = new Set(
|
||||
repositoryContent
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
const missingPatterns = REPOSITORY_GITIGNORE_PATTERNS.filter(
|
||||
(pattern) => !existingPatterns.has(pattern),
|
||||
);
|
||||
if (missingPatterns.length === 0) return repositoryContent;
|
||||
return appendBlock(
|
||||
repositoryContent,
|
||||
`# Bailian CLI local files\n${missingPatterns.join("\n")}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
function environmentExample(config: string): string {
|
||||
return `${extractEnvironmentVariables(config)
|
||||
.map((variable) => `${variable}=replace-me`)
|
||||
.join("\n")}\n`;
|
||||
}
|
||||
|
||||
function mergeEnvironmentExample(content: string, config: string): string {
|
||||
const existingVariables = new Set<string>();
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
||||
if (match?.[1]) existingVariables.add(match[1]);
|
||||
}
|
||||
const missingVariables = extractEnvironmentVariables(config).filter(
|
||||
(variable) => !existingVariables.has(variable),
|
||||
);
|
||||
if (missingVariables.length === 0) return content;
|
||||
return appendBlock(
|
||||
content,
|
||||
`${missingVariables.map((variable) => `${variable}=replace-me`).join("\n")}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
function extractEnvironmentVariables(config: string): string[] {
|
||||
const variables = new Set<string>();
|
||||
for (const match of config.matchAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}/g)) {
|
||||
if (match[1]) variables.add(match[1]);
|
||||
}
|
||||
return [...variables];
|
||||
}
|
||||
|
||||
function appendBlock(content: string, block: string): string {
|
||||
if (!content) return block;
|
||||
if (content.endsWith("\n\n")) return `${content}${block}`;
|
||||
if (content.endsWith("\n")) return `${content}\n${block}`;
|
||||
return `${content}\n\n${block}`;
|
||||
}
|
||||
|
||||
function buildPackageJson(projectName: string, cliVersion: string): string {
|
||||
return `${JSON.stringify(
|
||||
{
|
||||
name: npmPackageName(projectName),
|
||||
private: true,
|
||||
version: "0.0.0",
|
||||
type: "module",
|
||||
scripts: PROJECT_SCRIPTS,
|
||||
devDependencies: { "bailian-cli": cliVersion },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`;
|
||||
}
|
||||
|
||||
function mergePackageJson(
|
||||
content: string,
|
||||
projectName: string,
|
||||
cliVersion: string,
|
||||
): { content: string; preservedSettings: string[] } {
|
||||
let manifest: unknown;
|
||||
try {
|
||||
manifest = JSON.parse(content);
|
||||
} catch {
|
||||
throw new BailianError(
|
||||
"Cannot upgrade package.json because it is not valid JSON.",
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
if (!isRecord(manifest)) {
|
||||
throw new BailianError(
|
||||
"Cannot upgrade package.json because its root is not an object.",
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
const preservedSettings: string[] = [];
|
||||
if (manifest.name === undefined) manifest.name = npmPackageName(projectName);
|
||||
if (manifest.private === undefined) manifest.private = true;
|
||||
|
||||
const scripts = manifest.scripts === undefined ? {} : manifest.scripts;
|
||||
if (!isRecord(scripts)) {
|
||||
throw new BailianError(
|
||||
"Cannot upgrade package.json because 'scripts' is not an object.",
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
manifest.scripts = scripts;
|
||||
for (const [name, command] of Object.entries(PROJECT_SCRIPTS)) {
|
||||
if (scripts[name] === undefined) scripts[name] = command;
|
||||
else if (scripts[name] !== command) preservedSettings.push(`package.json scripts.${name}`);
|
||||
}
|
||||
|
||||
const developmentDependencies =
|
||||
manifest.devDependencies === undefined ? {} : manifest.devDependencies;
|
||||
if (!isRecord(developmentDependencies)) {
|
||||
throw new BailianError(
|
||||
"Cannot upgrade package.json because 'devDependencies' is not an object.",
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
manifest.devDependencies = developmentDependencies;
|
||||
if (developmentDependencies["bailian-cli"] === undefined) {
|
||||
developmentDependencies["bailian-cli"] = cliVersion;
|
||||
} else if (developmentDependencies["bailian-cli"] !== cliVersion) {
|
||||
preservedSettings.push("package.json bailian-cli version");
|
||||
}
|
||||
return { content: `${JSON.stringify(manifest, null, 2)}\n`, preservedSettings };
|
||||
}
|
||||
|
||||
function buildAoneEnvironmentBlock(config: string): string {
|
||||
const variables = extractEnvironmentVariables(config);
|
||||
if (variables.length === 0) {
|
||||
return " # Add provider variables referenced by agents.yaml in Aone Flow.";
|
||||
}
|
||||
return variables.map((variable) => ` ${variable}: \${{secrets.${variable}}}`).join("\n");
|
||||
}
|
||||
|
||||
function buildAoneWorkflow(config: string): string {
|
||||
const environmentBlock = buildAoneEnvironmentBlock(config);
|
||||
return `name: Bailian CLI Managed Agent
|
||||
|
||||
triggers:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
apply:
|
||||
name: Validate, plan, and apply Agent resources
|
||||
image: alios-8u
|
||||
timeout: 30m
|
||||
steps:
|
||||
- id: checkout
|
||||
uses: checkout
|
||||
- id: setup-env
|
||||
uses: setup-env
|
||||
inputs:
|
||||
node-version: 22
|
||||
tnpm-version: 10
|
||||
tnpm-cache: true
|
||||
- id: install
|
||||
run: npm install --ignore-scripts --no-audit --no-fund
|
||||
- id: validate-and-plan
|
||||
envs:
|
||||
${environmentBlock}
|
||||
run: |
|
||||
npm run agents:validate
|
||||
npm run agents:plan:ci > bailian-cli-plan.json
|
||||
- id: upload-plan
|
||||
uses: upload-artifact
|
||||
inputs:
|
||||
name: bailian-cli-plan
|
||||
path: bailian-cli-plan.json
|
||||
- id: apply-and-persist-state
|
||||
envs:
|
||||
${environmentBlock}
|
||||
run: |
|
||||
set +e
|
||||
npm run agents:apply:ci
|
||||
apply_status=$?
|
||||
set -e
|
||||
if ! git diff --quiet -- agents.state.json; then
|
||||
git config user.name "Bailian CLI CI"
|
||||
git config user.email "bailian-cli-ci@alibaba-inc.com"
|
||||
git add -- agents.state.json
|
||||
git commit -m "chore: update Bailian CLI Agent state [skip ci]"
|
||||
git push origin HEAD:main
|
||||
fi
|
||||
exit "$apply_status"
|
||||
`;
|
||||
}
|
||||
|
||||
function buildAoneCheckWorkflow(config: string): string {
|
||||
const environmentBlock = buildAoneEnvironmentBlock(config);
|
||||
return `name: Bailian CLI Managed Agent Check
|
||||
|
||||
# Bind this pipeline to Codeup merge-request new/update events in Aone Flow.
|
||||
jobs:
|
||||
check:
|
||||
name: Validate and plan Agent resources
|
||||
image: alios-8u
|
||||
timeout: 20m
|
||||
steps:
|
||||
- id: checkout
|
||||
uses: checkout
|
||||
- id: setup-env
|
||||
uses: setup-env
|
||||
inputs:
|
||||
node-version: 22
|
||||
tnpm-version: 10
|
||||
tnpm-cache: true
|
||||
- id: install
|
||||
run: npm install --ignore-scripts --no-audit --no-fund
|
||||
- id: validate-and-plan
|
||||
envs:
|
||||
${environmentBlock}
|
||||
run: |
|
||||
npm run agents:validate
|
||||
npm run agents:plan:ci > bailian-cli-plan.json
|
||||
- id: upload-plan
|
||||
uses: upload-artifact
|
||||
inputs:
|
||||
name: bailian-cli-plan
|
||||
path: bailian-cli-plan.json
|
||||
`;
|
||||
}
|
||||
|
||||
function buildReadme(projectName: string, config: string): string {
|
||||
const variableList = extractEnvironmentVariables(config)
|
||||
.map((variable) => `- \`${variable}\``)
|
||||
.join("\n");
|
||||
return `# ${projectName}
|
||||
|
||||
This repository declares cloud Agent resources with Bailian CLI.
|
||||
|
||||
## Local Workbench
|
||||
|
||||
1. Copy \`.env.example\` to \`.env\` and replace placeholder credentials.
|
||||
2. Run \`npm install\`.
|
||||
3. Run \`npm run agents:workbench\`.
|
||||
|
||||
## Aone CI
|
||||
|
||||
\`.aoneci/bailian-cli-check.yml\` validates and plans merge requests without applying. \`.aoneci/bailian-cli.yml\` applies non-destructive local changes after a push to \`main\` and commits the resulting \`agents.state.json\` back to \`main\`.
|
||||
|
||||
Configure these values as secret variables in Aone Flow:
|
||||
|
||||
${variableList || "- Add the provider variables referenced by agents.yaml."}
|
||||
|
||||
Set pipeline concurrency to 1, protect the main branch, and require approval where appropriate. Workbench and CI should use isolated credentials, resource namespaces, and State scopes.
|
||||
|
||||
Create the remote repository yourself, then push this local repository:
|
||||
|
||||
\`\`\`bash
|
||||
git add .
|
||||
git commit -m "Initialize Bailian CLI Agent project"
|
||||
git remote add origin <your-codeup-repository-url>
|
||||
git push -u origin main
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
|
||||
function npmPackageName(projectName: string): string {
|
||||
const normalized = projectName
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9._-]+/g, "-")
|
||||
.replace(/^[._-]+|[._-]+$/g, "");
|
||||
return normalized || "bailian-agent-project";
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { BailianError, type Client, ExitCode, type Settings } from "bailian-cli-core";
|
||||
import { emitBare } from "bailian-cli-runtime";
|
||||
|
||||
const PLAYGROUND_PACKAGE = "@openagentpack/playground";
|
||||
const DEFAULT_PORT = 4848;
|
||||
const PLAYGROUND_URL_PATTERN = /running at http:\/\/localhost:(\d+)/i;
|
||||
|
||||
export interface PlaygroundLaunchOptions {
|
||||
port?: number;
|
||||
open: boolean;
|
||||
file: string;
|
||||
agent?: string;
|
||||
surface: "preview" | "workbench";
|
||||
client: Client;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
interface Launcher {
|
||||
command: string;
|
||||
args: string[];
|
||||
version?: string;
|
||||
fetched: boolean;
|
||||
}
|
||||
|
||||
interface ExistingPlayground {
|
||||
version: string;
|
||||
pid: number;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
interface PlaygroundProjectSummary {
|
||||
status?: string;
|
||||
agents?: Array<{ agent?: { id?: string } }>;
|
||||
}
|
||||
|
||||
export interface PlaygroundBrowserTarget {
|
||||
url: string;
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
export async function launchManagedAgentPlayground(
|
||||
options: PlaygroundLaunchOptions,
|
||||
): Promise<void> {
|
||||
assertSupportedNodeVersion();
|
||||
const port = options.port ?? DEFAULT_PORT;
|
||||
if (!Number.isInteger(port) || port <= 0 || port > 65_535) {
|
||||
throw new BailianError(`Invalid --port '${port}'.`, ExitCode.USAGE);
|
||||
}
|
||||
const configPath = resolve(options.file);
|
||||
const projectId = createHash("sha256").update(configPath).digest("hex").slice(0, 16);
|
||||
const launcher = resolveLauncher();
|
||||
const existing = await probeExistingPlayground(port);
|
||||
if (existing) {
|
||||
const reusable =
|
||||
existing.projectId === projectId &&
|
||||
(launcher.version === undefined || existing.version === launcher.version);
|
||||
if (reusable) {
|
||||
emitBare(`Workbench already running at http://localhost:${port} (pid ${existing.pid}).`);
|
||||
await openPlaygroundSurface(port, options.surface, options.agent, options.open);
|
||||
return;
|
||||
}
|
||||
const released = await replaceExistingPlayground(existing, port);
|
||||
if (!released) {
|
||||
throw new BailianError(
|
||||
`Could not stop the existing Workbench process (pid ${existing.pid}) on port ${port}.`,
|
||||
ExitCode.GENERAL,
|
||||
"Stop it manually or choose another --port.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const environment = buildPlaygroundEnvironment(options, port, configPath);
|
||||
if (launcher.fetched) {
|
||||
emitBare(`Fetching ${PLAYGROUND_PACKAGE} (first run may take a moment)...`);
|
||||
}
|
||||
const child = spawn(launcher.command, launcher.args, {
|
||||
env: environment,
|
||||
stdio: ["inherit", "pipe", "inherit"],
|
||||
});
|
||||
const removeSignalForwarding = forwardSignals(child);
|
||||
try {
|
||||
const readyPort = await waitForPlaygroundReady(child, port, 30_000, projectId);
|
||||
if (readyPort === null) {
|
||||
throw new BailianError(
|
||||
`Workbench did not become ready in time. Check the logs above, then open http://localhost:${port}.`,
|
||||
ExitCode.GENERAL,
|
||||
);
|
||||
}
|
||||
emitBare(`Workbench ready at http://localhost:${readyPort}`);
|
||||
await openPlaygroundSurface(readyPort, options.surface, options.agent, options.open);
|
||||
const exitCode = await waitForChildExit(child);
|
||||
if (exitCode !== 0) {
|
||||
throw new BailianError(`Workbench exited with code ${exitCode}.`, ExitCode.GENERAL);
|
||||
}
|
||||
} finally {
|
||||
removeSignalForwarding();
|
||||
}
|
||||
}
|
||||
|
||||
export function playgroundBrowserTargetFromSummary(
|
||||
baseUrl: string,
|
||||
summary: PlaygroundProjectSummary,
|
||||
requestedAgent?: string,
|
||||
): PlaygroundBrowserTarget {
|
||||
if (summary.status !== "valid") return { url: baseUrl };
|
||||
const agentIds = (summary.agents ?? [])
|
||||
.map((entry) => entry.agent?.id?.trim())
|
||||
.filter((agentId): agentId is string => Boolean(agentId));
|
||||
const requested = requestedAgent?.trim();
|
||||
if (requested) {
|
||||
if (agentIds.includes(requested)) {
|
||||
return { url: `${baseUrl}/agents/${encodeURIComponent(requested)}/preview` };
|
||||
}
|
||||
return {
|
||||
url: baseUrl,
|
||||
warning: `Agent '${requested}' was not found. Opening the project Workbench instead.`,
|
||||
};
|
||||
}
|
||||
if (agentIds.length === 1) {
|
||||
return { url: `${baseUrl}/agents/${encodeURIComponent(agentIds[0]!)}/preview` };
|
||||
}
|
||||
if (agentIds.length > 1) {
|
||||
return {
|
||||
url: baseUrl,
|
||||
warning:
|
||||
"This project declares multiple Agents. Opening the Workbench; rerun with --agent <id> for Preview.",
|
||||
};
|
||||
}
|
||||
return { url: baseUrl };
|
||||
}
|
||||
|
||||
function assertSupportedNodeVersion(): void {
|
||||
const majorVersion = Number(process.versions.node.split(".")[0]);
|
||||
if (Number.isFinite(majorVersion) && majorVersion >= 22) return;
|
||||
throw new BailianError(
|
||||
"Managed Agent Workbench requires Node.js 22 or later.",
|
||||
ExitCode.USAGE,
|
||||
"Upgrade Node.js for Workbench; other Bailian CLI commands continue to support Node.js 18.17+.",
|
||||
);
|
||||
}
|
||||
|
||||
function resolveLauncher(): Launcher {
|
||||
const explicit =
|
||||
process.env.BAILIAN_MANAGED_AGENT_PLAYGROUND_BIN?.trim() ||
|
||||
process.env.AGENTS_PLAYGROUND_BIN?.trim();
|
||||
if (explicit) {
|
||||
if (!existsSync(explicit)) {
|
||||
throw new BailianError(
|
||||
`Configured Workbench binary does not exist: ${explicit}`,
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
return { command: process.execPath, args: [explicit], fetched: false };
|
||||
}
|
||||
|
||||
const installed = resolveInstalledPlayground();
|
||||
if (installed) return installed;
|
||||
|
||||
const monorepoBinary = findLocalPlaygroundBin(process.cwd());
|
||||
if (monorepoBinary) {
|
||||
return { command: process.execPath, args: [monorepoBinary], fetched: false };
|
||||
}
|
||||
|
||||
const requestedVersion = process.env.BAILIAN_MANAGED_AGENT_PLAYGROUND_VERSION?.trim() || "latest";
|
||||
return {
|
||||
command: "npx",
|
||||
args: ["-y", `${PLAYGROUND_PACKAGE}@${requestedVersion}`],
|
||||
version: requestedVersion === "latest" ? undefined : requestedVersion,
|
||||
fetched: true,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveInstalledPlayground(): Launcher | undefined {
|
||||
try {
|
||||
const require = createRequire(import.meta.url);
|
||||
const packageJsonPath = require.resolve(`${PLAYGROUND_PACKAGE}/package.json`);
|
||||
const manifest = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
||||
version?: string;
|
||||
bin?: string | Record<string, string>;
|
||||
};
|
||||
const relativeBinary =
|
||||
typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.["agents-playground"];
|
||||
if (!relativeBinary) return undefined;
|
||||
const binaryPath = resolve(dirname(packageJsonPath), relativeBinary);
|
||||
if (!existsSync(binaryPath)) return undefined;
|
||||
return {
|
||||
command: process.execPath,
|
||||
args: [binaryPath],
|
||||
version: manifest.version,
|
||||
fetched: false,
|
||||
};
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function findLocalPlaygroundBin(startDirectory: string): string | undefined {
|
||||
let directory = startDirectory;
|
||||
for (let depth = 0; depth < 10; depth += 1) {
|
||||
const candidate = resolve(directory, "packages/playground/dist/bin/playground.js");
|
||||
if (existsSync(candidate)) return candidate;
|
||||
const parent = dirname(directory);
|
||||
if (parent === directory) break;
|
||||
directory = parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildPlaygroundEnvironment(
|
||||
options: PlaygroundLaunchOptions,
|
||||
port: number,
|
||||
configPath: string,
|
||||
): NodeJS.ProcessEnv {
|
||||
const credential = options.client.exportApiCredential();
|
||||
const environment: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
PORT: String(port),
|
||||
AGENTS_CONFIG_PATH: configPath,
|
||||
AGENTS_PLAYGROUND_TOKEN: randomBytes(32).toString("hex"),
|
||||
};
|
||||
if (credential) environment.DASHSCOPE_API_KEY = credential.token;
|
||||
const baseUrl = options.client.baseUrl.replace(/\/+$/, "");
|
||||
environment.BAILIAN_BASE_URL = baseUrl.endsWith("/api/v1/agentstudio")
|
||||
? baseUrl
|
||||
: `${baseUrl}/api/v1/agentstudio`;
|
||||
if (options.settings.workspaceId) {
|
||||
environment.BAILIAN_WORKSPACE_ID = options.settings.workspaceId;
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
|
||||
async function waitForPlaygroundReady(
|
||||
child: ChildProcess,
|
||||
fallbackPort: number,
|
||||
timeoutMs: number,
|
||||
expectedProjectId: string,
|
||||
): Promise<number | null> {
|
||||
let port = fallbackPort;
|
||||
let outputBuffer = "";
|
||||
child.stdout?.on("data", (chunk: Buffer | string) => {
|
||||
process.stdout.write(chunk);
|
||||
outputBuffer += chunk.toString();
|
||||
const match = outputBuffer.match(PLAYGROUND_URL_PATTERN);
|
||||
if (match?.[1]) port = Number(match[1]);
|
||||
});
|
||||
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() <= deadline) {
|
||||
if (child.exitCode !== null) return null;
|
||||
try {
|
||||
const response = await fetch(`http://localhost:${port}/health`, {
|
||||
signal: AbortSignal.timeout(1_000),
|
||||
});
|
||||
const body = response.ok
|
||||
? ((await response.json()) as { playground?: { project_id?: string } })
|
||||
: undefined;
|
||||
if (body?.playground?.project_id === expectedProjectId) return port;
|
||||
} catch {
|
||||
// Not ready yet.
|
||||
}
|
||||
await new Promise<void>((resolveWait) => setTimeout(resolveWait, 300));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function probeExistingPlayground(port: number): Promise<ExistingPlayground | null> {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:${port}/health`, {
|
||||
signal: AbortSignal.timeout(2_000),
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const body = (await response.json()) as {
|
||||
playground?: { version?: string; pid?: number; project_id?: string };
|
||||
};
|
||||
if (!body.playground?.pid) return null;
|
||||
return {
|
||||
version: body.playground.version ?? "unknown",
|
||||
pid: body.playground.pid,
|
||||
projectId: body.playground.project_id,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function replaceExistingPlayground(
|
||||
existing: ExistingPlayground,
|
||||
port: number,
|
||||
): Promise<boolean> {
|
||||
emitBare(`Replacing Workbench v${existing.version} (pid ${existing.pid}) on port ${port}...`);
|
||||
try {
|
||||
process.kill(existing.pid, "SIGTERM");
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
await new Promise<void>((resolveWait) => setTimeout(resolveWait, 100));
|
||||
if (!(await probeExistingPlayground(port))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function openPlaygroundSurface(
|
||||
port: number,
|
||||
surface: "preview" | "workbench",
|
||||
requestedAgent: string | undefined,
|
||||
shouldOpen: boolean,
|
||||
): Promise<void> {
|
||||
if (!shouldOpen) return;
|
||||
const target =
|
||||
surface === "workbench"
|
||||
? { url: `http://localhost:${port}` }
|
||||
: await resolvePlaygroundBrowserTarget(port, requestedAgent);
|
||||
if (target.warning) emitBare(`Warning: ${target.warning}`);
|
||||
openBrowser(target.url);
|
||||
}
|
||||
|
||||
async function resolvePlaygroundBrowserTarget(
|
||||
port: number,
|
||||
requestedAgent?: string,
|
||||
): Promise<PlaygroundBrowserTarget> {
|
||||
const baseUrl = `http://localhost:${port}`;
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/api/project`, {
|
||||
signal: AbortSignal.timeout(3_000),
|
||||
});
|
||||
if (!response.ok) return { url: baseUrl };
|
||||
return playgroundBrowserTargetFromSummary(
|
||||
baseUrl,
|
||||
(await response.json()) as PlaygroundProjectSummary,
|
||||
requestedAgent,
|
||||
);
|
||||
} catch {
|
||||
return { url: baseUrl };
|
||||
}
|
||||
}
|
||||
|
||||
function openBrowser(url: string): void {
|
||||
const command =
|
||||
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
||||
const args = process.platform === "win32" ? ["", url] : [url];
|
||||
try {
|
||||
spawn(command, args, {
|
||||
stdio: "ignore",
|
||||
detached: true,
|
||||
shell: process.platform === "win32",
|
||||
}).unref();
|
||||
} catch {
|
||||
emitBare(`Could not open a browser automatically. Visit ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
function forwardSignals(child: ChildProcess): () => void {
|
||||
const forwardInterrupt = () => child.kill("SIGINT");
|
||||
const forwardTerminate = () => child.kill("SIGTERM");
|
||||
process.on("SIGINT", forwardInterrupt);
|
||||
process.on("SIGTERM", forwardTerminate);
|
||||
return () => {
|
||||
process.off("SIGINT", forwardInterrupt);
|
||||
process.off("SIGTERM", forwardTerminate);
|
||||
};
|
||||
}
|
||||
|
||||
function waitForChildExit(child: ChildProcess): Promise<number> {
|
||||
if (child.exitCode !== null) return Promise.resolve(child.exitCode);
|
||||
return new Promise((resolveExit, rejectExit) => {
|
||||
child.once("exit", (exitCode) => resolveExit(exitCode ?? 0));
|
||||
child.once("error", rejectExit);
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare, emitResult } from "bailian-cli-runtime";
|
||||
import { executePlannedProject, planProjectContext } from "@openagentpack/sdk";
|
||||
import {
|
||||
executePlannedProject,
|
||||
planProjectContext,
|
||||
type PlannedAction,
|
||||
UserError,
|
||||
} from "@openagentpack/sdk";
|
||||
import { formatResourceLabel } from "./_engine/address-utils.ts";
|
||||
import {
|
||||
assertProviderConfigured,
|
||||
@@ -16,6 +21,12 @@ import {
|
||||
import { withStdoutProtected } from "./_engine/console-capture.ts";
|
||||
import { withAgentErrors } from "./_engine/errors.ts";
|
||||
import { renderAgentFeedback } from "./_engine/feedback.ts";
|
||||
import {
|
||||
commitAutomaticVersion,
|
||||
type PreparedAutomaticVersion,
|
||||
prepareAutomaticVersion,
|
||||
readVersionSource,
|
||||
} from "@openagentpack/local-git";
|
||||
|
||||
const APPLY_FLAGS = {
|
||||
file: {
|
||||
@@ -41,6 +52,13 @@ const APPLY_FLAGS = {
|
||||
"zh-CN": "无需交互提示直接确认并应用(执行变更时必填)",
|
||||
},
|
||||
},
|
||||
ci: {
|
||||
type: "switch",
|
||||
description: {
|
||||
"en-US": "Run non-interactively while blocking deletes and remote drift",
|
||||
"zh-CN": "以非交互模式运行,并阻止删除和远端漂移覆盖",
|
||||
},
|
||||
},
|
||||
noRefresh: {
|
||||
type: "switch",
|
||||
description: {
|
||||
@@ -48,6 +66,13 @@ const APPLY_FLAGS = {
|
||||
"zh-CN": "规划前跳过从远端刷新状态",
|
||||
},
|
||||
},
|
||||
refreshOnly: {
|
||||
type: "switch",
|
||||
description: {
|
||||
"en-US": "Refresh state without mutating remote resources",
|
||||
"zh-CN": "仅刷新 State,不修改远端资源",
|
||||
},
|
||||
},
|
||||
concurrency: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
@@ -64,10 +89,18 @@ export default defineCommand({
|
||||
"zh-CN": "应用规划的变更,创建、更新或删除 Agent 资源",
|
||||
},
|
||||
auth: "apiKey",
|
||||
usageArgs: "[--file <path>] [--provider <name>] [--yes] [--concurrency <n>]",
|
||||
usageArgs:
|
||||
"[--file <path>] [--provider <name>] [--yes | --ci] [--no-refresh] [--refresh-only] [--concurrency <n>]",
|
||||
flags: APPLY_FLAGS,
|
||||
exampleArgs: ["--yes", "--provider bailian --yes"],
|
||||
exampleArgs: ["--yes", "--provider bailian --yes", "--ci"],
|
||||
notes: CREDENTIALS_NOTE,
|
||||
validate(flags) {
|
||||
if (flags.ci && flags.yes) return "--ci cannot be combined with --yes.";
|
||||
if (flags.ci && flags.noRefresh) {
|
||||
return "--ci requires remote state refresh and cannot be combined with --no-refresh.";
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
@@ -80,6 +113,8 @@ export default defineCommand({
|
||||
provider: flags.provider ?? "all",
|
||||
refresh: !flags.noRefresh,
|
||||
concurrency: flags.concurrency,
|
||||
ci: flags.ci,
|
||||
refresh_only: flags.refreshOnly,
|
||||
},
|
||||
config_file: file,
|
||||
hint: "Run `managed-agent plan` to preview the exact resource changes.",
|
||||
@@ -89,16 +124,19 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const planned = await withAgentErrors(() =>
|
||||
const versionSource = await readVersionSource(file);
|
||||
|
||||
const { planned, runtime } = await withAgentErrors(() =>
|
||||
withStdoutProtected(async () => {
|
||||
const runtime = await buildAgentRuntime(ctx, file);
|
||||
assertProviderConfigured(runtime, flags.provider);
|
||||
return planProjectContext(runtime, {
|
||||
const planned = await planProjectContext(runtime, {
|
||||
provider: flags.provider,
|
||||
refresh: !flags.noRefresh,
|
||||
quiet: true,
|
||||
onFeedback: renderAgentFeedback,
|
||||
});
|
||||
return { planned, runtime };
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -118,6 +156,13 @@ export default defineCommand({
|
||||
|
||||
const actionable = plan.actions.filter((action) => action.action !== "no-op");
|
||||
if (actionable.length === 0) {
|
||||
if (!flags.refreshOnly) {
|
||||
const preparedVersion = await prepareAutomaticVersion(
|
||||
runtime.configPath,
|
||||
versionSource.source,
|
||||
);
|
||||
await commitSuccessfulApplyVersion(preparedVersion, format);
|
||||
}
|
||||
if (format === "json")
|
||||
emitResult({ succeeded: 0, failed: 0, skipped: 0, results: [] }, format);
|
||||
else emitBare("No changes. Infrastructure is up-to-date.");
|
||||
@@ -127,13 +172,32 @@ export default defineCommand({
|
||||
const creates = actionable.filter((action) => action.action === "create").length;
|
||||
const updates = actionable.filter((action) => action.action === "update").length;
|
||||
const deletes = planned.destructiveActions;
|
||||
if (flags.ci) assertCiApplyPolicy(actionable);
|
||||
|
||||
for (const action of actionable) {
|
||||
const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-";
|
||||
emitProgress(` ${icon} ${formatResourceLabel(action.address)}`);
|
||||
}
|
||||
|
||||
if (!flags.yes) {
|
||||
if (flags.refreshOnly) {
|
||||
if (format === "json") {
|
||||
emitResult(
|
||||
{
|
||||
refresh_only: true,
|
||||
actions: actionable,
|
||||
succeeded: 0,
|
||||
failed: 0,
|
||||
skipped: actionable.length,
|
||||
},
|
||||
format,
|
||||
);
|
||||
} else {
|
||||
emitBare("Refresh-only mode: no remote mutations were performed.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!flags.yes && !flags.ci) {
|
||||
throw new BailianError(
|
||||
`Refusing to apply ${actionable.length} change(s) (${creates} create, ${updates} update, ${deletes.length} destroy) without confirmation.`,
|
||||
ExitCode.USAGE,
|
||||
@@ -141,6 +205,8 @@ export default defineCommand({
|
||||
);
|
||||
}
|
||||
|
||||
const preparedVersion = await prepareAutomaticVersion(runtime.configPath, versionSource.source);
|
||||
|
||||
const result = await withAgentErrors(() =>
|
||||
withStdoutProtected(() =>
|
||||
executePlannedProject(planned, {
|
||||
@@ -161,6 +227,40 @@ export default defineCommand({
|
||||
emitBare(`\nApply finished: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped.`);
|
||||
}
|
||||
|
||||
if (failed > 0) throw new BailianError("Apply failed.", ExitCode.GENERAL);
|
||||
if (failed > 0 || skipped > 0) {
|
||||
throw new BailianError(
|
||||
failed > 0 ? "Apply failed." : "Apply incomplete: one or more actions were skipped.",
|
||||
ExitCode.GENERAL,
|
||||
);
|
||||
}
|
||||
await commitSuccessfulApplyVersion(preparedVersion, format);
|
||||
},
|
||||
});
|
||||
|
||||
export function assertCiApplyPolicy(actions: PlannedAction[]): void {
|
||||
const deletes = actions.filter((action) => action.action === "delete");
|
||||
if (deletes.length > 0) {
|
||||
throw new UserError(
|
||||
`CI policy blocked ${deletes.length} delete action(s). Review the plan and apply this destructive change through an explicitly approved workflow.`,
|
||||
);
|
||||
}
|
||||
const drifted = actions.filter(
|
||||
(action) => action.driftKind === "remote" || action.driftKind === "both",
|
||||
);
|
||||
if (drifted.length > 0) {
|
||||
throw new UserError(
|
||||
`CI policy blocked ${drifted.length} action(s) with remote drift. Review the remote changes before deciding whether YAML should overwrite them.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function commitSuccessfulApplyVersion(
|
||||
prepared: PreparedAutomaticVersion | null,
|
||||
format: "text" | "json",
|
||||
): Promise<void> {
|
||||
if (!prepared) return;
|
||||
const version = await commitAutomaticVersion(prepared);
|
||||
if (version && format !== "json") {
|
||||
emitBare(`Created local version ${version.short_commit} (${version.message}).`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { emitBare, emitResult } from "bailian-cli-runtime";
|
||||
import { createGitProject, inspectGitProjectTarget } from "./_engine/git-project.ts";
|
||||
|
||||
const GITIGNORE_ADDITIONS = `
|
||||
# agents
|
||||
@@ -100,6 +101,14 @@ const INIT_FLAGS = {
|
||||
"zh-CN": "输出配置路径(默认:agents.yaml)",
|
||||
},
|
||||
},
|
||||
git: {
|
||||
type: "string",
|
||||
valueHint: "<directory>",
|
||||
description: {
|
||||
"en-US": "Create or add CI/Git scaffolding in this project directory",
|
||||
"zh-CN": "在此项目目录中创建或补充 CI/Git 脚手架",
|
||||
},
|
||||
},
|
||||
force: {
|
||||
type: "switch",
|
||||
description: { "en-US": "Overwrite an existing config file", "zh-CN": "覆盖已有配置文件" },
|
||||
@@ -108,13 +117,19 @@ const INIT_FLAGS = {
|
||||
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US": "Create a new agents.yaml template",
|
||||
"zh-CN": "创建新的 agents.yaml 模板",
|
||||
"en-US": "Create an agents.yaml template or a local CI/Git project",
|
||||
"zh-CN": "创建 agents.yaml 模板或本地 CI/Git 项目",
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "[--provider <name>] [--agent-name <name>] [--file <path>] [--force]",
|
||||
usageArgs:
|
||||
"[--provider <name>] [--agent-name <name>] [--file <path>] [--git <directory>] [--force]",
|
||||
flags: INIT_FLAGS,
|
||||
exampleArgs: ["", "--provider bailian --agent-name assistant", "--provider all"],
|
||||
exampleArgs: ["", "--provider bailian --agent-name assistant", "--git ./my-agents", "--git ."],
|
||||
validate(flags) {
|
||||
if (flags.git && flags.file) return "--git cannot be combined with --file.";
|
||||
if (flags.git && flags.force) return "--git cannot be combined with --force.";
|
||||
return undefined;
|
||||
},
|
||||
async run(ctx) {
|
||||
const { settings, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
@@ -122,6 +137,45 @@ export default defineCommand({
|
||||
const agentName = flags.agentName ?? "assistant";
|
||||
const file = flags.file ?? "agents.yaml";
|
||||
|
||||
if (flags.git) {
|
||||
const targetMode = await inspectGitProjectTarget(flags.git);
|
||||
if (settings.dryRun) {
|
||||
emitResult(
|
||||
{
|
||||
would_initialize_git_project: flags.git,
|
||||
mode: targetMode === "new" ? "create" : "upgrade",
|
||||
provider,
|
||||
agent: agentName,
|
||||
},
|
||||
format,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const template = buildTemplate({ provider, agentName });
|
||||
const result = await createGitProject(flags.git, {
|
||||
config: template,
|
||||
cliVersion: ctx.identity.version,
|
||||
});
|
||||
if (format === "json") {
|
||||
emitResult(result, format);
|
||||
} else {
|
||||
const action =
|
||||
result.mode === "created" ? "Created CI/Git project" : "Added CI/Git scaffolding";
|
||||
emitBare(`${action} at ${result.targetDirectory}`);
|
||||
if (result.createdFiles.length > 0) {
|
||||
emitBare(`Created: ${result.createdFiles.join(", ")}`);
|
||||
}
|
||||
if (result.updatedFiles.length > 0) {
|
||||
emitBare(`Updated: ${result.updatedFiles.join(", ")}`);
|
||||
}
|
||||
if (result.preservedFiles.length > 0) {
|
||||
emitBare(`Preserved: ${result.preservedFiles.join(", ")}`);
|
||||
}
|
||||
emitBare("Next: add credentials to .env, install dependencies, and open the Workbench.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (existsSync(file) && !flags.force) {
|
||||
throw new BailianError(
|
||||
`${file} already exists.`,
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
import {
|
||||
BailianError,
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { confirmDangerousAction, emitBare, emitResult } from "bailian-cli-runtime";
|
||||
import chalk from "chalk";
|
||||
import {
|
||||
disableLocalVersioning,
|
||||
enableLocalVersioning,
|
||||
getLocalVersionStatus,
|
||||
type LocalProjectVersion,
|
||||
type LocalVersionPreview,
|
||||
type LocalVersionStatus,
|
||||
listLocalVersions,
|
||||
previewLocalVersion,
|
||||
restoreLocalVersion,
|
||||
} from "@openagentpack/local-git";
|
||||
|
||||
const FILE_FLAG = {
|
||||
file: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: {
|
||||
"en-US": "Config file path (default: agents.yaml)",
|
||||
"zh-CN": "配置文件路径(默认:agents.yaml)",
|
||||
},
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const COMMIT_FLAG = {
|
||||
commit: {
|
||||
type: "string",
|
||||
valueHint: "<full-sha>",
|
||||
required: true,
|
||||
description: {
|
||||
"en-US": "Full commit SHA from the current branch",
|
||||
"zh-CN": "当前分支中的完整 Commit SHA",
|
||||
},
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export const managedAgentVersionEnable = defineCommand({
|
||||
description: {
|
||||
"en-US": "Enable Apply-time Git versioning for agents.yaml",
|
||||
"zh-CN": "为 agents.yaml 启用 Apply 后自动 Git 版本管理",
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "[--file <path>]",
|
||||
flags: FILE_FLAG,
|
||||
exampleArgs: ["", "--file agents.yaml"],
|
||||
async run(ctx) {
|
||||
const file = ctx.flags.file ?? "agents.yaml";
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ would_enable: file, git: await getLocalVersionStatus(file) }, format);
|
||||
return;
|
||||
}
|
||||
const result = await enableLocalVersioning(file, "Enable Bailian CLI versioning");
|
||||
if (format === "json") {
|
||||
emitResult(result, format);
|
||||
return;
|
||||
}
|
||||
if (result.version) {
|
||||
emitBare(`Created baseline version ${result.version.short_commit} ${result.version.message}`);
|
||||
} else {
|
||||
emitBare("Current agents.yaml is already versioned; no commit was created.");
|
||||
}
|
||||
emitBare("Automatic versioning is enabled for this agents.yaml.");
|
||||
renderStatus(result.git);
|
||||
},
|
||||
});
|
||||
|
||||
export const managedAgentVersionDisable = defineCommand({
|
||||
description: {
|
||||
"en-US": "Disable Apply-time Git versioning without removing history",
|
||||
"zh-CN": "关闭 Apply 后自动 Git 版本管理,但保留历史",
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "[--file <path>]",
|
||||
flags: FILE_FLAG,
|
||||
exampleArgs: ["", "--file agents.yaml"],
|
||||
async run(ctx) {
|
||||
const file = ctx.flags.file ?? "agents.yaml";
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ would_disable: file, git: await getLocalVersionStatus(file) }, format);
|
||||
return;
|
||||
}
|
||||
const status = await disableLocalVersioning(file);
|
||||
if (format === "json") {
|
||||
emitResult(status, format);
|
||||
return;
|
||||
}
|
||||
emitBare("Automatic versioning is disabled for this agents.yaml.");
|
||||
renderStatus(status);
|
||||
},
|
||||
});
|
||||
|
||||
export const managedAgentVersionStatus = defineCommand({
|
||||
description: {
|
||||
"en-US": "Show local Git versioning status for agents.yaml",
|
||||
"zh-CN": "显示 agents.yaml 的本地 Git 版本管理状态",
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "[--file <path>]",
|
||||
flags: FILE_FLAG,
|
||||
exampleArgs: ["", "--file agents.yaml --output json"],
|
||||
async run(ctx) {
|
||||
const status = await getLocalVersionStatus(ctx.flags.file ?? "agents.yaml");
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
if (format === "json") emitResult(status, format);
|
||||
else renderStatus(status);
|
||||
},
|
||||
});
|
||||
|
||||
const LIST_FLAGS = {
|
||||
...FILE_FLAG,
|
||||
limit: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: {
|
||||
"en-US": "Maximum versions to return (default: 50, max: 100)",
|
||||
"zh-CN": "最多返回的版本数(默认:50,最大:100)",
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: "string",
|
||||
valueHint: "<cursor>",
|
||||
description: {
|
||||
"en-US": "Pagination cursor returned by the previous page",
|
||||
"zh-CN": "上一页返回的分页游标",
|
||||
},
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export const managedAgentVersionList = defineCommand({
|
||||
description: {
|
||||
"en-US": "List current-branch commits that changed agents.yaml",
|
||||
"zh-CN": "列出当前分支中修改过 agents.yaml 的 Commit",
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "[--file <path>] [--limit <n>] [--cursor <cursor>]",
|
||||
flags: LIST_FLAGS,
|
||||
exampleArgs: ["", "--limit 20 --output json"],
|
||||
async run(ctx) {
|
||||
const page = await listLocalVersions(ctx.flags.file ?? "agents.yaml", {
|
||||
limit: ctx.flags.limit,
|
||||
cursor: ctx.flags.cursor,
|
||||
});
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
if (format === "json") {
|
||||
emitResult(page, format);
|
||||
return;
|
||||
}
|
||||
if (page.versions.length === 0) {
|
||||
emitBare("No versions of agents.yaml exist on the current branch.");
|
||||
return;
|
||||
}
|
||||
for (const version of page.versions) emitBare(formatVersion(version));
|
||||
if (page.next_cursor) emitBare(chalk.dim(`Next cursor: ${page.next_cursor}`));
|
||||
},
|
||||
});
|
||||
|
||||
const PREVIEW_FLAGS = {
|
||||
...FILE_FLAG,
|
||||
...COMMIT_FLAG,
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export const managedAgentVersionPreview = defineCommand({
|
||||
description: {
|
||||
"en-US": "Preview a historical agents.yaml version",
|
||||
"zh-CN": "预览 agents.yaml 的历史版本",
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "--commit <full-sha> [--file <path>]",
|
||||
flags: PREVIEW_FLAGS,
|
||||
exampleArgs: ["--commit <full-sha>", "--commit <full-sha> --output json"],
|
||||
async run(ctx) {
|
||||
const preview = await previewLocalVersion(ctx.flags.file ?? "agents.yaml", ctx.flags.commit);
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
if (format === "json") emitResult(preview, format);
|
||||
else renderPreview(preview);
|
||||
},
|
||||
});
|
||||
|
||||
const RESTORE_FLAGS = {
|
||||
...PREVIEW_FLAGS,
|
||||
yes: {
|
||||
type: "switch",
|
||||
description: {
|
||||
"en-US": "Restore without an interactive confirmation",
|
||||
"zh-CN": "无需交互确认直接恢复",
|
||||
},
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
export const managedAgentVersionRestore = defineCommand({
|
||||
description: {
|
||||
"en-US": "Restore a historical agents.yaml version to the working tree",
|
||||
"zh-CN": "将 agents.yaml 历史版本恢复到工作区",
|
||||
},
|
||||
auth: "none",
|
||||
usageArgs: "--commit <full-sha> [--file <path>] [--yes]",
|
||||
flags: RESTORE_FLAGS,
|
||||
exampleArgs: ["--commit <full-sha>", "--commit <full-sha> --yes --output json"],
|
||||
async run(ctx) {
|
||||
const file = ctx.flags.file ?? "agents.yaml";
|
||||
const preview = await previewLocalVersion(file, ctx.flags.commit);
|
||||
const format = detectOutputFormat(ctx.settings.output);
|
||||
if (format !== "json") renderPreview(preview);
|
||||
if (!preview.can_restore) {
|
||||
throw new BailianError(
|
||||
preview.diagnostics.find((diagnostic) => diagnostic.severity === "error")?.message ??
|
||||
preview.blockers[0] ??
|
||||
"This version cannot be restored.",
|
||||
ExitCode.GENERAL,
|
||||
);
|
||||
}
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult({ would_restore: ctx.flags.commit, preview }, format);
|
||||
return;
|
||||
}
|
||||
await confirmDangerousAction(
|
||||
"Restore this version to the agents.yaml working tree? HEAD and agents.state.json will not change.",
|
||||
ctx.flags.yes,
|
||||
);
|
||||
const restored = await restoreLocalVersion(file, ctx.flags.commit, {
|
||||
head: preview.base_head,
|
||||
sourceRevision: preview.base_source_revision,
|
||||
});
|
||||
if (format === "json") {
|
||||
emitResult({ restored: ctx.flags.commit, preview: restored }, format);
|
||||
} else {
|
||||
emitBare(
|
||||
`Restored ${ctx.flags.commit.slice(0, 12)} to the working tree. HEAD was not changed.`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function renderStatus(status: LocalVersionStatus): void {
|
||||
emitBare(`Git available: ${status.git_available ? "yes" : "no"}`);
|
||||
emitBare(`Automatic versioning: ${status.enabled ? "enabled" : "disabled"}`);
|
||||
emitBare(`Repository: ${status.repository_root ?? "none"}`);
|
||||
emitBare(`Config path: ${status.config_path ?? "none"}`);
|
||||
emitBare(`Branch: ${status.branch ?? "none"}`);
|
||||
emitBare(`HEAD: ${status.head ?? "none"}`);
|
||||
emitBare(
|
||||
`agents.yaml: ${status.config_status}${status.config_versioned ? ", versioned" : ", unversioned"}`,
|
||||
);
|
||||
const blockers = [...new Set([...status.commit_blockers, ...status.restore_blockers])];
|
||||
for (const blocker of blockers) emitBare(chalk.yellow(`Blocker: ${blocker}`));
|
||||
}
|
||||
|
||||
function formatVersion(version: LocalProjectVersion): string {
|
||||
return `${chalk.yellow(version.short_commit)} ${version.authored_at} ${version.message} ${chalk.dim(`(${version.author_name})`)}`;
|
||||
}
|
||||
|
||||
function renderPreview(preview: LocalVersionPreview): void {
|
||||
emitBare(chalk.bold(`Version ${preview.commit}`));
|
||||
emitBare(chalk.red("--- working tree"));
|
||||
emitBare(chalk.green(`+++ ${preview.commit}`));
|
||||
for (const line of buildLineDiff(preview.before_yaml, preview.after_yaml)) {
|
||||
if (line.kind === "deletion") emitBare(chalk.red(`-${line.text}`));
|
||||
else if (line.kind === "addition") emitBare(chalk.green(`+${line.text}`));
|
||||
else emitBare(chalk.dim(` ${line.text}`));
|
||||
}
|
||||
for (const diagnostic of preview.diagnostics) {
|
||||
const color =
|
||||
diagnostic.severity === "error"
|
||||
? chalk.red
|
||||
: diagnostic.severity === "warning"
|
||||
? chalk.yellow
|
||||
: chalk.dim;
|
||||
emitBare(color(`${diagnostic.severity}: ${diagnostic.code}: ${diagnostic.message}`));
|
||||
}
|
||||
for (const blocker of preview.blockers) emitBare(chalk.yellow(`blocker: ${blocker}`));
|
||||
emitBare(`Can restore: ${preview.can_restore ? "yes" : "no"}`);
|
||||
}
|
||||
|
||||
type DiffLine = { kind: "context" | "addition" | "deletion"; text: string };
|
||||
|
||||
function buildLineDiff(beforeSource: string, afterSource: string): DiffLine[] {
|
||||
const beforeLines = yamlLines(beforeSource);
|
||||
const afterLines = yamlLines(afterSource);
|
||||
const maximumDistance = beforeLines.length + afterLines.length;
|
||||
const frontier = new Map<number, number>([[1, 0]]);
|
||||
const traces: Array<Map<number, number>> = [];
|
||||
|
||||
for (let editDistance = 0; editDistance <= maximumDistance; editDistance += 1) {
|
||||
traces.push(new Map(frontier));
|
||||
for (let diagonal = -editDistance; diagonal <= editDistance; diagonal += 2) {
|
||||
const deletionStart = frontier.get(diagonal - 1) ?? Number.NEGATIVE_INFINITY;
|
||||
const additionStart = frontier.get(diagonal + 1) ?? Number.NEGATIVE_INFINITY;
|
||||
const startsWithAddition =
|
||||
diagonal === -editDistance || (diagonal !== editDistance && deletionStart < additionStart);
|
||||
let beforeIndex = startsWithAddition ? (frontier.get(diagonal + 1) ?? 0) : deletionStart + 1;
|
||||
let afterIndex = beforeIndex - diagonal;
|
||||
while (
|
||||
beforeIndex < beforeLines.length &&
|
||||
afterIndex < afterLines.length &&
|
||||
beforeLines[beforeIndex] === afterLines[afterIndex]
|
||||
) {
|
||||
beforeIndex += 1;
|
||||
afterIndex += 1;
|
||||
}
|
||||
frontier.set(diagonal, beforeIndex);
|
||||
if (beforeIndex >= beforeLines.length && afterIndex >= afterLines.length) {
|
||||
return backtrackDiff(beforeLines, afterLines, traces, editDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function backtrackDiff(
|
||||
beforeLines: string[],
|
||||
afterLines: string[],
|
||||
traces: Array<Map<number, number>>,
|
||||
finalDistance: number,
|
||||
): DiffLine[] {
|
||||
let beforeIndex = beforeLines.length;
|
||||
let afterIndex = afterLines.length;
|
||||
const reversedLines: DiffLine[] = [];
|
||||
for (let editDistance = finalDistance; editDistance >= 0; editDistance -= 1) {
|
||||
const frontier = traces[editDistance]!;
|
||||
const diagonal = beforeIndex - afterIndex;
|
||||
const deletionStart = frontier.get(diagonal - 1) ?? Number.NEGATIVE_INFINITY;
|
||||
const additionStart = frontier.get(diagonal + 1) ?? Number.NEGATIVE_INFINITY;
|
||||
const cameFromAddition =
|
||||
diagonal === -editDistance || (diagonal !== editDistance && deletionStart < additionStart);
|
||||
const previousDiagonal = cameFromAddition ? diagonal + 1 : diagonal - 1;
|
||||
const previousBeforeIndex = frontier.get(previousDiagonal) ?? 0;
|
||||
const previousAfterIndex = previousBeforeIndex - previousDiagonal;
|
||||
while (beforeIndex > previousBeforeIndex && afterIndex > previousAfterIndex) {
|
||||
reversedLines.push({ kind: "context", text: beforeLines[beforeIndex - 1]! });
|
||||
beforeIndex -= 1;
|
||||
afterIndex -= 1;
|
||||
}
|
||||
if (editDistance === 0) break;
|
||||
if (beforeIndex === previousBeforeIndex) {
|
||||
reversedLines.push({ kind: "addition", text: afterLines[afterIndex - 1]! });
|
||||
afterIndex -= 1;
|
||||
} else {
|
||||
reversedLines.push({ kind: "deletion", text: beforeLines[beforeIndex - 1]! });
|
||||
beforeIndex -= 1;
|
||||
}
|
||||
}
|
||||
return reversedLines.reverse();
|
||||
}
|
||||
|
||||
function yamlLines(source: string): string[] {
|
||||
const lines = source.split("\n");
|
||||
if (lines[lines.length - 1] === "") lines.pop();
|
||||
return lines;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
|
||||
import { emitResult } from "bailian-cli-runtime";
|
||||
import { CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
|
||||
import { launchManagedAgentPlayground } from "./_engine/playground-launcher.ts";
|
||||
|
||||
const WORKBENCH_FLAGS = {
|
||||
file: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: {
|
||||
"en-US": "Config file path (default: agents.yaml)",
|
||||
"zh-CN": "配置文件路径(默认:agents.yaml)",
|
||||
},
|
||||
},
|
||||
port: {
|
||||
type: "number",
|
||||
valueHint: "<n>",
|
||||
description: {
|
||||
"en-US": "Local port (default: 4848)",
|
||||
"zh-CN": "本地端口(默认:4848)",
|
||||
},
|
||||
},
|
||||
noOpen: {
|
||||
type: "switch",
|
||||
description: {
|
||||
"en-US": "Do not open a browser automatically",
|
||||
"zh-CN": "不自动打开浏览器",
|
||||
},
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const PLAYGROUND_FLAGS = {
|
||||
...WORKBENCH_FLAGS,
|
||||
agent: {
|
||||
type: "string",
|
||||
valueHint: "<id>",
|
||||
description: {
|
||||
"en-US": "Agent to preview (required when the project declares multiple Agents)",
|
||||
"zh-CN": "要预览的 Agent(项目包含多个 Agent 时需要指定)",
|
||||
},
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
const WORKBENCH_NOTES = [
|
||||
...CREDENTIALS_NOTE,
|
||||
{
|
||||
"en-US":
|
||||
"Workbench requires Node.js 22+ and starts the shared @openagentpack/playground package locally. It does not push Git commits or switch branches.",
|
||||
"zh-CN":
|
||||
"Workbench 需要 Node.js 22+,并在本地启动共享的 @openagentpack/playground 包;不会推送 Git Commit 或切换分支。",
|
||||
},
|
||||
];
|
||||
|
||||
export const managedAgentWorkbench = defineCommand({
|
||||
description: {
|
||||
"en-US": "Launch the agents.yaml project Workbench",
|
||||
"zh-CN": "启动 agents.yaml 项目 Workbench",
|
||||
},
|
||||
auth: "apiKey",
|
||||
usageArgs: "[--file <path>] [--port <n>] [--no-open]",
|
||||
flags: WORKBENCH_FLAGS,
|
||||
exampleArgs: ["", "--file agents.yaml --no-open", "--port 4949"],
|
||||
notes: WORKBENCH_NOTES,
|
||||
async run(ctx) {
|
||||
const file = ctx.flags.file ?? "agents.yaml";
|
||||
const port = ctx.flags.port ?? 4848;
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult(
|
||||
{
|
||||
would_launch: "workbench",
|
||||
config_file: file,
|
||||
port,
|
||||
open_browser: !ctx.flags.noOpen,
|
||||
},
|
||||
detectOutputFormat(ctx.settings.output),
|
||||
);
|
||||
return;
|
||||
}
|
||||
await launchManagedAgentPlayground({
|
||||
file,
|
||||
port,
|
||||
open: !ctx.flags.noOpen,
|
||||
surface: "workbench",
|
||||
client: ctx.client,
|
||||
settings: ctx.settings,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const managedAgentPlayground = defineCommand({
|
||||
description: {
|
||||
"en-US": "Launch a Session Preview for an agents.yaml Agent",
|
||||
"zh-CN": "为 agents.yaml 中的 Agent 启动会话预览",
|
||||
},
|
||||
auth: "apiKey",
|
||||
usageArgs: "[--file <path>] [--agent <id>] [--port <n>] [--no-open]",
|
||||
flags: PLAYGROUND_FLAGS,
|
||||
exampleArgs: ["", "--agent assistant", "--file agents.yaml --no-open"],
|
||||
notes: WORKBENCH_NOTES,
|
||||
async run(ctx) {
|
||||
const file = ctx.flags.file ?? "agents.yaml";
|
||||
const port = ctx.flags.port ?? 4848;
|
||||
if (ctx.settings.dryRun) {
|
||||
emitResult(
|
||||
{
|
||||
would_launch: "playground",
|
||||
config_file: file,
|
||||
agent: ctx.flags.agent,
|
||||
port,
|
||||
open_browser: !ctx.flags.noOpen,
|
||||
},
|
||||
detectOutputFormat(ctx.settings.output),
|
||||
);
|
||||
return;
|
||||
}
|
||||
await launchManagedAgentPlayground({
|
||||
file,
|
||||
agent: ctx.flags.agent,
|
||||
port,
|
||||
open: !ctx.flags.noOpen,
|
||||
surface: "preview",
|
||||
client: ctx.client,
|
||||
settings: ctx.settings,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -136,6 +136,18 @@ export { default as managedAgentValidate } from "./commands/managed-agent/valida
|
||||
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";
|
||||
export { default as managedAgentApply } from "./commands/managed-agent/apply.ts";
|
||||
export { default as managedAgentDestroy } from "./commands/managed-agent/destroy.ts";
|
||||
export {
|
||||
managedAgentPlayground,
|
||||
managedAgentWorkbench,
|
||||
} from "./commands/managed-agent/workbench.ts";
|
||||
export {
|
||||
managedAgentVersionDisable,
|
||||
managedAgentVersionEnable,
|
||||
managedAgentVersionList,
|
||||
managedAgentVersionPreview,
|
||||
managedAgentVersionRestore,
|
||||
managedAgentVersionStatus,
|
||||
} from "./commands/managed-agent/version.ts";
|
||||
export { default as managedAgentStateList } from "./commands/managed-agent/state-list.ts";
|
||||
export { default as managedAgentStateShow } from "./commands/managed-agent/state-show.ts";
|
||||
export { default as managedAgentStateRm } from "./commands/managed-agent/state-rm.ts";
|
||||
|
||||
@@ -323,6 +323,8 @@ export async function createKbWithDocs(
|
||||
"create",
|
||||
"--name",
|
||||
kbName,
|
||||
"--description",
|
||||
`journey ${journeyId} fixture knowledge base (safe to delete)`,
|
||||
...fileIds.flatMap((fileId) => ["--doc-id", fileId]),
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
KNOWLEDGE_KB_DELETE_ROUTES,
|
||||
} from "../topic-routes.ts";
|
||||
import { deleteKbWithRetry, pollUntil } from "./journeys/journey-helpers.ts";
|
||||
import { VERIFIED_RERANK_MODEL } from "./verified-models.ts";
|
||||
|
||||
interface DryRunBody {
|
||||
endpoint?: string;
|
||||
@@ -1058,6 +1059,8 @@ describe.skipIf(!isKbAdminE2EReady())(
|
||||
"create",
|
||||
"--name",
|
||||
`e2e-cate-kb-${Date.now() % 100000000}`.slice(0, 20),
|
||||
"--description",
|
||||
"e2e fixture knowledge base (safe to delete)",
|
||||
"--category-id",
|
||||
categoryId,
|
||||
"--workspace-id",
|
||||
@@ -1158,6 +1161,8 @@ describe.skipIf(!isKbAdminE2EReady())(
|
||||
"create",
|
||||
"--name",
|
||||
`e2e-ck-${Date.now() % 100000000}`,
|
||||
"--description",
|
||||
"e2e fixture knowledge base (safe to delete)",
|
||||
"--doc-id",
|
||||
fileId,
|
||||
"--workspace-id",
|
||||
@@ -1319,7 +1324,7 @@ describe.skipIf(!isKbAdminE2EReady())(
|
||||
"chunk chain fixture",
|
||||
"--rerank",
|
||||
"--rerank-model",
|
||||
"qwen3-rerank-hybrid",
|
||||
VERIFIED_RERANK_MODEL,
|
||||
"--rerank-mode",
|
||||
"similar",
|
||||
"--rerank-top-n",
|
||||
@@ -1701,6 +1706,8 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: chunk/category/file 参数补全 (li
|
||||
"create",
|
||||
"--name",
|
||||
`e2e-pk-${Date.now() % 100000000}`.slice(0, 20),
|
||||
"--description",
|
||||
"e2e fixture knowledge base (safe to delete)",
|
||||
"--doc-id",
|
||||
fileId,
|
||||
"--workspace-id",
|
||||
|
||||
@@ -148,6 +148,8 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge doc status (live, 自清
|
||||
"create",
|
||||
"--name",
|
||||
`e2e-st-${Date.now() % 100000000}`,
|
||||
"--description",
|
||||
"e2e fixture knowledge base (safe to delete)",
|
||||
"--doc-id",
|
||||
fileIdA,
|
||||
"--workspace-id",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { KNOWLEDGE_KB_CREATE_ROUTES } from "../topic-routes.ts";
|
||||
interface DryRunBody {
|
||||
endpoint?: string;
|
||||
request?: {
|
||||
description?: string;
|
||||
sourceType?: string;
|
||||
sinkType?: string;
|
||||
docIds?: string[];
|
||||
@@ -23,6 +24,7 @@ describe("e2e: knowledge kb create", () => {
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/--name/i);
|
||||
expect(stderr).toMatch(/--description/i);
|
||||
expect(stderr).toMatch(/--doc-id/i);
|
||||
expect(stderr).toMatch(/--category-id/i);
|
||||
expect(stderr).toMatch(/--embedding-model/i);
|
||||
@@ -33,6 +35,40 @@ describe("e2e: knowledge kb create", () => {
|
||||
const { exitCode } = await runCommandE2e(KNOWLEDGE_KB_CREATE_ROUTES, [
|
||||
"knowledge",
|
||||
"create",
|
||||
"--description",
|
||||
"demo base",
|
||||
"--doc-id",
|
||||
"file_test",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
});
|
||||
|
||||
// The server rejects a missing description with HTTP 400 (Index.InvalidParameter);
|
||||
// the CLI must stop it locally instead.
|
||||
test("缺 --description 报 USAGE (2)", async () => {
|
||||
const { exitCode } = await runCommandE2e(KNOWLEDGE_KB_CREATE_ROUTES, [
|
||||
"knowledge",
|
||||
"create",
|
||||
"--name",
|
||||
"demo",
|
||||
"--doc-id",
|
||||
"file_test",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
});
|
||||
|
||||
test("--description 501 字符报 USAGE (2)", async () => {
|
||||
const { exitCode } = await runCommandE2e(KNOWLEDGE_KB_CREATE_ROUTES, [
|
||||
"knowledge",
|
||||
"create",
|
||||
"--name",
|
||||
"demo",
|
||||
"--description",
|
||||
"x".repeat(501),
|
||||
"--doc-id",
|
||||
"file_test",
|
||||
"--workspace-id",
|
||||
@@ -47,6 +83,8 @@ describe("e2e: knowledge kb create", () => {
|
||||
"create",
|
||||
"--name",
|
||||
"demo",
|
||||
"--description",
|
||||
"demo base",
|
||||
"--workspace-id",
|
||||
"ws_test",
|
||||
]);
|
||||
@@ -59,6 +97,8 @@ describe("e2e: knowledge kb create", () => {
|
||||
"create",
|
||||
"--name",
|
||||
"demo",
|
||||
"--description",
|
||||
"demo base",
|
||||
"--doc-id",
|
||||
"file_test",
|
||||
"--category-id",
|
||||
@@ -75,6 +115,8 @@ describe("e2e: knowledge kb create", () => {
|
||||
"create",
|
||||
"--name",
|
||||
"x".repeat(21),
|
||||
"--description",
|
||||
"demo base",
|
||||
"--doc-id",
|
||||
"file_test",
|
||||
"--workspace-id",
|
||||
@@ -89,6 +131,8 @@ describe("e2e: knowledge kb create", () => {
|
||||
"create",
|
||||
"--name",
|
||||
"demo",
|
||||
"--description",
|
||||
"demo base",
|
||||
"--doc-id",
|
||||
"file_test",
|
||||
"--workspace-id",
|
||||
@@ -103,6 +147,8 @@ describe("e2e: knowledge kb create", () => {
|
||||
expect(data.request?.sourceType).toBe("DATA_CENTER_FILE");
|
||||
expect(data.request?.docIds).toEqual(["file_test"]);
|
||||
expect(data.request?.sinkType).toBe("BUILT_IN");
|
||||
// description is a server-required field — it must reach the request body verbatim
|
||||
expect(data.request?.description).toBe("demo base");
|
||||
// Defaults are part of the contract — the server applies no fallback of its own
|
||||
expect(data.request?.embeddingModelName).toBe("text-embedding-v4");
|
||||
expect(data.request?.chunkSize).toBe(600);
|
||||
@@ -114,6 +160,8 @@ describe("e2e: knowledge kb create", () => {
|
||||
"create",
|
||||
"--name",
|
||||
"demo",
|
||||
"--description",
|
||||
"demo base",
|
||||
"--doc-id",
|
||||
"file_test",
|
||||
"--embedding-model",
|
||||
@@ -138,6 +186,8 @@ describe("e2e: knowledge kb create", () => {
|
||||
"create",
|
||||
"--name",
|
||||
"demo",
|
||||
"--description",
|
||||
"demo base",
|
||||
"--category-id",
|
||||
"cate_test",
|
||||
"--workspace-id",
|
||||
|
||||
@@ -101,6 +101,8 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge kb 写链路 (live, 自清
|
||||
"create",
|
||||
"--name",
|
||||
kbName,
|
||||
"--description",
|
||||
"e2e fixture knowledge base (safe to delete)",
|
||||
"--doc-id",
|
||||
fileId,
|
||||
"--workspace-id",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { join } from "node:path";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { isKbAdminE2EReady, parseStdoutJson, runCommandE2e } from "../helpers.ts";
|
||||
import { KNOWLEDGE_SERVICE_ROUTES } from "../topic-routes.ts";
|
||||
import { pickDifferentAgentModel } from "./verified-models.ts";
|
||||
|
||||
interface DryRunBody {
|
||||
endpoint?: string;
|
||||
@@ -780,6 +781,33 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge service 参数全覆盖 (l
|
||||
expect(listIndexIdRun.exitCode, listIndexIdRun.stderr).toBe(0);
|
||||
|
||||
// ── P0 组1: update --name --version-desc --model → get 读回 3 个 scalar ──
|
||||
// --model 取值自适应:先读回草稿当前模型,再挑一个不同的已验证模型写入,
|
||||
// 断言才能证明「值真的变了」而不是把默认值原样写回
|
||||
const baselineGetRun = await runCommandE2e(KNOWLEDGE_SERVICE_ROUTES, [
|
||||
"knowledge",
|
||||
"service",
|
||||
"get",
|
||||
"--agent-id",
|
||||
agentId,
|
||||
"--agent-version",
|
||||
"beta",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(baselineGetRun.exitCode, baselineGetRun.stderr).toBe(0);
|
||||
const baselineModel = parseStdoutJson<{
|
||||
data?: { agent_details?: Array<{ agent_config?: { agent_model?: string } }> };
|
||||
}>(baselineGetRun.stdout).data?.agent_details?.[0]?.agent_config?.agent_model;
|
||||
const targetModel = pickDifferentAgentModel(baselineModel);
|
||||
if (targetModel === undefined) {
|
||||
// 白名单缩到只剩当前模型 —— 跳过模型断言而不是断言一个空操作
|
||||
process.stderr.write(
|
||||
`skip --model assertion: no verified model differs from ${baselineModel}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
const newName = `${serviceName}-renamed`;
|
||||
const updateScalarRun = await runCommandE2e(KNOWLEDGE_SERVICE_ROUTES, [
|
||||
"knowledge",
|
||||
@@ -791,8 +819,7 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge service 参数全覆盖 (l
|
||||
newName,
|
||||
"--version-desc",
|
||||
"beta-v1",
|
||||
"--model",
|
||||
"qwen-plus",
|
||||
...(targetModel === undefined ? [] : ["--model", targetModel]),
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
@@ -816,14 +843,18 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge service 参数全覆盖 (l
|
||||
data?: {
|
||||
agent_name?: string;
|
||||
agent_details?: Array<{
|
||||
agent_version_desc?: string;
|
||||
agent_version_desc?: string | null;
|
||||
agent_config?: { agent_model?: string };
|
||||
}>;
|
||||
};
|
||||
}>(scalarGetRun.stdout);
|
||||
expect(scalarGetData.data?.agent_name).toBe(newName);
|
||||
expect(scalarGetData.data?.agent_details?.[0]?.agent_config?.agent_model).toBe("qwen-plus");
|
||||
expect(scalarGetData.data?.agent_details?.[0]?.agent_version_desc).toBe("beta-v1");
|
||||
if (targetModel !== undefined) {
|
||||
expect(scalarGetData.data?.agent_details?.[0]?.agent_config?.agent_model).toBe(targetModel);
|
||||
}
|
||||
// --version-desc 在 beta 草稿上是服务端空操作:update 返回 200 但存为 null。
|
||||
// 版本说明只在 deploy 时传入、或对已发布版本 update 才能落库(下方 P0 组4 覆盖)。
|
||||
expect(scalarGetData.data?.agent_details?.[0]?.agent_version_desc).toBeNull();
|
||||
|
||||
// ── P0 组2: update --policy turbo → get 读回 ──
|
||||
const updateTurboRun = await runCommandE2e(KNOWLEDGE_SERVICE_ROUTES, [
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
runCommandE2e,
|
||||
} from "../helpers.ts";
|
||||
import { KNOWLEDGE_ROUTES } from "../topic-routes.ts";
|
||||
import { VERIFIED_RERANK_MODEL } from "./verified-models.ts";
|
||||
|
||||
// ---- Types ----
|
||||
|
||||
@@ -168,7 +169,7 @@ describe("e2e: knowledge retrieve dry-run", () => {
|
||||
"hello",
|
||||
"--rerank",
|
||||
"--rerank-model",
|
||||
"qwen3-rerank-hybrid",
|
||||
VERIFIED_RERANK_MODEL,
|
||||
"--rerank-mode",
|
||||
"custom",
|
||||
"--rerank-instruct",
|
||||
@@ -187,7 +188,7 @@ describe("e2e: knowledge retrieve dry-run", () => {
|
||||
expect(data.request?.enable_reranking).toBe(true);
|
||||
expect(data.request?.dense_similarity_top_k).toBe(100);
|
||||
expect(data.request?.sparse_similarity_top_k).toBe(50);
|
||||
expect(data.request?.rerank?.[0]?.model_name).toBe("qwen3-rerank-hybrid");
|
||||
expect(data.request?.rerank?.[0]?.model_name).toBe(VERIFIED_RERANK_MODEL);
|
||||
expect(data.request?.rerank?.[0]?.rerank_mode).toBe("custom");
|
||||
expect(data.request?.rerank?.[0]?.rerank_instruct).toBe("按相关性排序");
|
||||
});
|
||||
@@ -220,7 +221,7 @@ describe.skipIf(!isKbAdminE2EReady())("e2e: knowledge retrieve 参数补全 (liv
|
||||
"e2e test",
|
||||
"--rerank",
|
||||
"--rerank-model",
|
||||
"qwen3-rerank-hybrid",
|
||||
VERIFIED_RERANK_MODEL,
|
||||
"--rerank-mode",
|
||||
"custom",
|
||||
"--rerank-instruct",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Model values verified against the live API, shared by the knowledge e2e cases so a
|
||||
// server-side model rotation only needs one edit here.
|
||||
//
|
||||
// Deliberately test-only: the command layer must NOT mirror these lists into local
|
||||
// validation. Model catalogs churn with server-side launches/deprecations, so a
|
||||
// hardcoded allowlist in the CLI would block valid models until users upgrade.
|
||||
|
||||
/**
|
||||
* Accepted values for `agent_config.agent_model` (service create/update).
|
||||
* Everything else — including qwen-plus, qwen-max, qwen3-max, qwen3.7-flash,
|
||||
* qwen3.7-max and deepseek-v3 — is rejected with
|
||||
* HTTP 401 InvalidParameter "agent model not allowed: <name>".
|
||||
* The first entry is also the server-side default for a freshly created service.
|
||||
*/
|
||||
export const VERIFIED_AGENT_MODELS = ["qwen3.6-plus", "qwen3.7-plus"] as const;
|
||||
|
||||
/**
|
||||
* Accepted value for `rerank[].model_name` on index/retrieve.
|
||||
* Gotcha: the server only branches on the `-hybrid` suffix — the model prefix is
|
||||
* ignored (`qwen3-rerank` and `gte-rerank` score identically to omitting the field,
|
||||
* `qwen3-rerank-hybrid` and `gte-rerank-hybrid` score identically to each other), so
|
||||
* the effective scoring model is the index's own `rerankModelName`. Unknown names and
|
||||
* indexes without `rerankModelName` both fail with
|
||||
* HTTP 500 Index.IndexRerankError "index rerank config(<name>) error.".
|
||||
*/
|
||||
export const VERIFIED_RERANK_MODEL = "qwen3-rerank-hybrid";
|
||||
|
||||
/**
|
||||
* Pick a verified model that differs from the current one, so a write → read-back
|
||||
* assertion proves the value actually changed instead of re-writing the default.
|
||||
* Returns undefined when the allowlist has shrunk to the model already in use — the
|
||||
* caller must then skip the model assertion instead of asserting a no-op.
|
||||
*/
|
||||
export function pickDifferentAgentModel(currentModel: string | undefined): string | undefined {
|
||||
return VERIFIED_AGENT_MODELS.find((model) => model !== currentModel);
|
||||
}
|
||||
@@ -132,6 +132,27 @@ describe("e2e: managed-agent", () => {
|
||||
expect(stderr).toMatch(/--file|--provider|--yes/i);
|
||||
});
|
||||
|
||||
test("managed-agent version 暴露共享版本管理子命令", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"version",
|
||||
"--help",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toMatch(/enable|disable|status|list|preview|restore/i);
|
||||
});
|
||||
|
||||
test("managed-agent version preview 缺少 --commit 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"version",
|
||||
"preview",
|
||||
"--quiet",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--commit|Missing required/i);
|
||||
});
|
||||
|
||||
test("managed-agent session delete 缺少 --session-id 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
@@ -218,6 +239,47 @@ describe("e2e: managed-agent(--dry-run 短路,不联网不写盘)", () =>
|
||||
expect(data.provider).toBe("bailian");
|
||||
});
|
||||
|
||||
test("init --git --dry-run 仅输出仓库脚手架计划", async () => {
|
||||
const targetDirectory = join(
|
||||
process.cwd(),
|
||||
`.managed-agent-git-dry-run-${process.pid}-${Date.now()}`,
|
||||
);
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"init",
|
||||
"--git",
|
||||
targetDirectory,
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
would_initialize_git_project?: string;
|
||||
mode?: string;
|
||||
}>(stdout);
|
||||
expect(data.would_initialize_git_project).toBe(targetDirectory);
|
||||
expect(data.mode).toBe("create");
|
||||
});
|
||||
|
||||
test("workbench --dry-run 仅输出启动计划", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
"workbench",
|
||||
"--dry-run",
|
||||
"--no-open",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
would_launch?: string;
|
||||
open_browser?: boolean;
|
||||
}>(stdout);
|
||||
expect(data.would_launch).toBe("workbench");
|
||||
expect(data.open_browser).toBe(false);
|
||||
});
|
||||
|
||||
test("apply --dry-run 仅输出计划", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(MANAGED_AGENT_ROUTES, [
|
||||
"managed-agent",
|
||||
|
||||
@@ -186,6 +186,14 @@ export const MANAGED_AGENT_ROUTES: E2eRouteExports = {
|
||||
"managed-agent plan": "managedAgentPlan",
|
||||
"managed-agent apply": "managedAgentApply",
|
||||
"managed-agent destroy": "managedAgentDestroy",
|
||||
"managed-agent workbench": "managedAgentWorkbench",
|
||||
"managed-agent playground": "managedAgentPlayground",
|
||||
"managed-agent version enable": "managedAgentVersionEnable",
|
||||
"managed-agent version disable": "managedAgentVersionDisable",
|
||||
"managed-agent version status": "managedAgentVersionStatus",
|
||||
"managed-agent version list": "managedAgentVersionList",
|
||||
"managed-agent version preview": "managedAgentVersionPreview",
|
||||
"managed-agent version restore": "managedAgentVersionRestore",
|
||||
"managed-agent state list": "managedAgentStateList",
|
||||
"managed-agent state rm": "managedAgentStateRm",
|
||||
"managed-agent state import": "managedAgentStateImport",
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, test } from "vite-plus/test";
|
||||
import { createGitProject } from "../src/commands/managed-agent/_engine/git-project.ts";
|
||||
|
||||
const temporaryDirectories: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
for (const directory of temporaryDirectories.splice(0)) {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe("managed-agent init --git project scaffolding", () => {
|
||||
test("creates a main-branch Git project without committing or configuring a remote", async () => {
|
||||
const parentDirectory = await mkdtemp(join(tmpdir(), "bailian-cli-git-project-"));
|
||||
temporaryDirectories.push(parentDirectory);
|
||||
const targetDirectory = join(parentDirectory, "agent-project");
|
||||
|
||||
const result = await createGitProject(targetDirectory, {
|
||||
config: projectYaml(),
|
||||
cliVersion: "1.17.1",
|
||||
});
|
||||
|
||||
expect(result.mode).toBe("created");
|
||||
expect(result.initializedGit).toBe(true);
|
||||
expect(result.createdFiles).toContain("agents.yaml");
|
||||
expect(await readFile(join(targetDirectory, "agents.yaml"), "utf8")).toContain("assistant:");
|
||||
expect(await readFile(join(targetDirectory, ".aoneci/bailian-cli.yml"), "utf8")).toContain(
|
||||
"agents:apply:ci",
|
||||
);
|
||||
expect(await readFile(join(targetDirectory, "README.md"), "utf8")).toContain(
|
||||
"Create the remote repository yourself",
|
||||
);
|
||||
});
|
||||
|
||||
test("upgrades an initialized config directory without overwriting agents.yaml", async () => {
|
||||
const targetDirectory = await mkdtemp(join(tmpdir(), "bailian-cli-git-upgrade-"));
|
||||
temporaryDirectories.push(targetDirectory);
|
||||
const originalSource = projectYaml().replace("assistant", "reviewer");
|
||||
await writeFile(join(targetDirectory, "agents.yaml"), originalSource);
|
||||
|
||||
const result = await createGitProject(targetDirectory, {
|
||||
config: projectYaml(),
|
||||
cliVersion: "1.17.1",
|
||||
});
|
||||
|
||||
expect(result.mode).toBe("upgraded");
|
||||
expect(result.preservedFiles).toContain("agents.yaml");
|
||||
expect(await readFile(join(targetDirectory, "agents.yaml"), "utf8")).toBe(originalSource);
|
||||
});
|
||||
});
|
||||
|
||||
function projectYaml(): string {
|
||||
return `version: "1"
|
||||
providers:
|
||||
bailian:
|
||||
api_key: \${DASHSCOPE_API_KEY}
|
||||
defaults:
|
||||
provider: bailian
|
||||
agents:
|
||||
assistant:
|
||||
model: qwen3.8-max
|
||||
instructions: You are helpful.
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import type { PlannedAction } from "@openagentpack/sdk";
|
||||
import { afterEach, describe, expect, test } from "vite-plus/test";
|
||||
import {
|
||||
commitAutomaticVersion,
|
||||
disableLocalVersioning,
|
||||
enableLocalVersioning,
|
||||
getLocalVersionStatus,
|
||||
prepareAutomaticVersion,
|
||||
previewLocalVersion,
|
||||
restoreLocalVersion,
|
||||
} from "@openagentpack/local-git";
|
||||
import { playgroundBrowserTargetFromSummary } from "../src/commands/managed-agent/_engine/playground-launcher.ts";
|
||||
import { assertCiApplyPolicy } from "../src/commands/managed-agent/apply.ts";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const temporaryDirectories: string[] = [];
|
||||
const gitIdentity = {
|
||||
GIT_AUTHOR_NAME: "Bailian CLI Test",
|
||||
GIT_AUTHOR_EMAIL: "bailian-cli@example.com",
|
||||
GIT_COMMITTER_NAME: "Bailian CLI Test",
|
||||
GIT_COMMITTER_EMAIL: "bailian-cli@example.com",
|
||||
};
|
||||
|
||||
afterEach(async () => {
|
||||
for (const directory of temporaryDirectories.splice(0)) {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe("managed-agent local Git versions", () => {
|
||||
test("uses the shared path-scoped switch and commits only agents.yaml", async () => {
|
||||
const root = await temporaryDirectory();
|
||||
const configPath = join(root, "agents.yaml");
|
||||
const nestedDirectory = join(root, "nested");
|
||||
const nestedConfigPath = join(nestedDirectory, "agents.yaml");
|
||||
await mkdir(nestedDirectory);
|
||||
await writeFile(configPath, projectYaml("First"));
|
||||
await writeFile(nestedConfigPath, projectYaml("Second"));
|
||||
await git(root, ["init", "--initial-branch", "main"]);
|
||||
await writeFile(join(root, "staged.txt"), "staged\n");
|
||||
await git(root, ["add", "staged.txt"]);
|
||||
const stagedBefore = await git(root, ["status", "--porcelain=v1", "--", "staged.txt"]);
|
||||
|
||||
const enabled = await withGitIdentity(() =>
|
||||
enableLocalVersioning(configPath, "Enable Bailian CLI versioning"),
|
||||
);
|
||||
|
||||
expect(enabled.git.enabled).toBe(true);
|
||||
expect((await getLocalVersionStatus(nestedConfigPath)).enabled).toBe(false);
|
||||
expect((await git(root, ["show", "--pretty=", "--name-only", "HEAD"])).trim()).toBe(
|
||||
"agents.yaml",
|
||||
);
|
||||
expect(await git(root, ["status", "--porcelain=v1", "--", "staged.txt"])).toBe(stagedBefore);
|
||||
expect(
|
||||
await git(root, ["rev-parse", "--git-path", "openagentpack/local-git/versions"]),
|
||||
).toContain("openagentpack/local-git/versions");
|
||||
|
||||
await writeFile(configPath, projectYaml("First updated"));
|
||||
const repeated = await withGitIdentity(() =>
|
||||
enableLocalVersioning(configPath, "Enable Bailian CLI versioning"),
|
||||
);
|
||||
expect(repeated.version?.message).toBe("Enable Bailian CLI versioning");
|
||||
expect(await git(root, ["status", "--porcelain=v1", "--", "staged.txt"])).toBe(stagedBefore);
|
||||
|
||||
const disabled = await disableLocalVersioning(configPath);
|
||||
expect(disabled.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test("auto-commits after success and restores without moving HEAD or changing permissions", async () => {
|
||||
const root = await temporaryDirectory();
|
||||
const configPath = join(root, "agents.yaml");
|
||||
await writeFile(configPath, projectYaml("Version one"));
|
||||
await chmod(configPath, 0o640);
|
||||
const enabled = await withGitIdentity(() =>
|
||||
enableLocalVersioning(configPath, "Enable Bailian CLI versioning"),
|
||||
);
|
||||
const firstCommit = enabled.version!.commit;
|
||||
const secondSource = projectYaml("Version two");
|
||||
await writeFile(configPath, secondSource);
|
||||
|
||||
const prepared = await withGitIdentity(() => prepareAutomaticVersion(configPath, secondSource));
|
||||
const version = await withGitIdentity(() => commitAutomaticVersion(prepared!));
|
||||
const headBeforeRestore = (await git(root, ["rev-parse", "HEAD"])).trim();
|
||||
expect(version?.message).toBe("Apply agents.yaml");
|
||||
|
||||
const preview = await previewLocalVersion(configPath, firstCommit);
|
||||
expect(preview.can_restore).toBe(true);
|
||||
expect(preview.after_yaml).toContain("Version one");
|
||||
await restoreLocalVersion(configPath, firstCommit, {
|
||||
head: preview.base_head,
|
||||
sourceRevision: preview.base_source_revision,
|
||||
});
|
||||
|
||||
expect(await readFile(configPath, "utf8")).toContain("Version one");
|
||||
expect((await git(root, ["rev-parse", "HEAD"])).trim()).toBe(headBeforeRestore);
|
||||
expect((await stat(configPath)).mode & 0o777).toBe(0o640);
|
||||
});
|
||||
|
||||
test("rejects short SHAs and plaintext credentials", async () => {
|
||||
const root = await temporaryDirectory();
|
||||
const configPath = join(root, "agents.yaml");
|
||||
await writeFile(configPath, projectYaml("Safe"));
|
||||
const enabled = await withGitIdentity(() =>
|
||||
enableLocalVersioning(configPath, "Enable Bailian CLI versioning"),
|
||||
);
|
||||
await expect(previewLocalVersion(configPath, enabled.version!.short_commit)).rejects.toThrow(
|
||||
/full hexadecimal commit SHA/i,
|
||||
);
|
||||
|
||||
await disableLocalVersioning(configPath);
|
||||
await writeFile(
|
||||
configPath,
|
||||
projectYaml("Unsafe").replace("qoder: {}", "qoder:\n api_key: plaintext-secret"),
|
||||
);
|
||||
await expect(
|
||||
withGitIdentity(() => enableLocalVersioning(configPath, "Enable Bailian CLI versioning")),
|
||||
).rejects.toThrow(/environment variable reference/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed-agent CI and Workbench policies", () => {
|
||||
test("CI blocks delete actions and remote drift", () => {
|
||||
expect(() => assertCiApplyPolicy([plannedAction("delete")])).toThrow(/blocked.*delete/i);
|
||||
expect(() => assertCiApplyPolicy([plannedAction("update", "remote")])).toThrow(
|
||||
/blocked.*remote drift/i,
|
||||
);
|
||||
expect(() => assertCiApplyPolicy([plannedAction("update", "local")])).not.toThrow();
|
||||
});
|
||||
|
||||
test("Session Preview opens the requested Agent or falls back to Workbench", () => {
|
||||
const summary = {
|
||||
status: "valid",
|
||||
agents: [{ agent: { id: "assistant" } }, { agent: { id: "reviewer" } }],
|
||||
};
|
||||
expect(
|
||||
playgroundBrowserTargetFromSummary("http://localhost:4848", summary, "reviewer"),
|
||||
).toEqual({ url: "http://localhost:4848/agents/reviewer/preview" });
|
||||
expect(playgroundBrowserTargetFromSummary("http://localhost:4848", summary)).toEqual(
|
||||
expect.objectContaining({ url: "http://localhost:4848", warning: expect.any(String) }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function temporaryDirectory(): Promise<string> {
|
||||
const directory = await mkdtemp(join(tmpdir(), "bailian-cli-local-git-"));
|
||||
temporaryDirectories.push(directory);
|
||||
return directory;
|
||||
}
|
||||
|
||||
function projectYaml(instructions: string): string {
|
||||
return `version: "1"
|
||||
providers:
|
||||
qoder: {}
|
||||
defaults:
|
||||
provider: qoder
|
||||
agents:
|
||||
assistant:
|
||||
model: ultimate
|
||||
instructions: ${instructions}
|
||||
`;
|
||||
}
|
||||
|
||||
function plannedAction(
|
||||
action: "create" | "update" | "delete",
|
||||
driftKind: "none" | "local" | "remote" | "both" = "none",
|
||||
): PlannedAction {
|
||||
return {
|
||||
action,
|
||||
driftKind,
|
||||
address: { provider: "bailian", type: "agent", name: "assistant" },
|
||||
} as PlannedAction;
|
||||
}
|
||||
|
||||
async function git(workingDirectory: string, arguments_: string[]): Promise<string> {
|
||||
const result = await execFileAsync("git", arguments_, {
|
||||
cwd: workingDirectory,
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, ...gitIdentity },
|
||||
});
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async function withGitIdentity<Result>(operation: () => Promise<Result>): Promise<Result> {
|
||||
const previousEnvironment = Object.fromEntries(
|
||||
Object.keys(gitIdentity).map((key) => [key, process.env[key]]),
|
||||
);
|
||||
Object.assign(process.env, gitIdentity);
|
||||
try {
|
||||
return await operation();
|
||||
} finally {
|
||||
for (const key of Object.keys(gitIdentity)) {
|
||||
const value = previousEnvironment[key];
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-core",
|
||||
"version": "1.17.0",
|
||||
"version": "1.17.1",
|
||||
"description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -186,6 +186,17 @@ export interface RagAgentConfig {
|
||||
session_file_max_parse_length?: number;
|
||||
enable_kb_router?: string;
|
||||
kb_router_model?: string;
|
||||
// Fields the server returns in the beta draft config; typed so read-merge-write
|
||||
// round-trips them without a passthrough warning. Observed value kinds, not docs.
|
||||
user_system_prompt?: string;
|
||||
anti_leak_prompt?: string;
|
||||
refusal_prompt?: string;
|
||||
credibility_prompt?: string;
|
||||
enable_thinking?: boolean;
|
||||
enable_temperature?: boolean;
|
||||
enable_credibility?: boolean;
|
||||
enable_max_completion_tokens?: boolean;
|
||||
session_file_parse_mode?: string;
|
||||
rerank_top_n?: number;
|
||||
hybrid_rerank?: Record<string, unknown>;
|
||||
kb_search_configs?: Array<Record<string, unknown>>;
|
||||
|
||||
@@ -42,6 +42,7 @@ npm install -g knowledge-studio-cli
|
||||
# 1. Create a knowledge base
|
||||
kscli kb create \
|
||||
--name "my-kb" \
|
||||
--description "my product docs knowledge base" \
|
||||
--embedding-model text-embedding-v3 \
|
||||
--workspace-id <your-workspace-id>
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ npm install -g knowledge-studio-cli
|
||||
# 1. 创建知识库
|
||||
kscli kb create \
|
||||
--name "my-kb" \
|
||||
--description "我的产品文档知识库" \
|
||||
--embedding-model text-embedding-v3 \
|
||||
--workspace-id <your-workspace-id>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "knowledge-studio-cli",
|
||||
"version": "1.17.0",
|
||||
"version": "1.17.1",
|
||||
"description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.",
|
||||
"keywords": [
|
||||
"alibaba-cloud",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-runtime",
|
||||
"version": "1.17.0",
|
||||
"version": "1.17.1",
|
||||
"description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Agent skill for **Alibaba Cloud Model Studio CLI** (`bl`) resource hub — apps, memory, RAG, usage/quota, MCP, and hub `reference/`.
|
||||
|
||||
- Shared protocol: `bailian-protocol` (install via `bl skill init`)
|
||||
- Soft hand-offs (optional skills): `bailian-gen` · `bailian-finetune` · `bailian-managed-agent`
|
||||
- Soft hand-offs (optional skills): `bailian-gen` · `bailian-finetune` · `bailian-managed-agent` · `bailian-web-search`
|
||||
|
||||
```bash
|
||||
bl skill init
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
**阿里云百炼 CLI**(`bl`)的资源管理 Agent 技能 — 应用、记忆、RAG、用量/额度、MCP,以及 hub `reference/`。
|
||||
|
||||
- 共享协议:`bailian-protocol`(通过 `bl skill init` 与整家族同装)
|
||||
- 软 hand-off(可选):`bailian-gen` · `bailian-finetune` · `bailian-managed-agent`
|
||||
- 软 hand-off(可选):`bailian-gen` · `bailian-finetune` · `bailian-managed-agent` · `bailian-web-search`
|
||||
|
||||
```bash
|
||||
bl skill init
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-cli
|
||||
metadata:
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
@@ -12,7 +12,7 @@ description: >-
|
||||
用户点名百炼 / DashScope / `bl`,或继续既有 `bl` 工作流时直接使用。
|
||||
共享协议(consent / 版本预检 / 鉴权 / 错误上报)在 bailian-protocol;官方安装 `bl skill init`。
|
||||
家族路由:生图/生视频/配音/语音合成/转写 → bailian-gen;精调/微调/训练/数据集 → bailian-finetune;
|
||||
agents.yaml 托管 Agent → bailian-managed-agent。
|
||||
agents.yaml 托管 Agent → bailian-managed-agent;联网搜索的模型路由(Token Plan 自带搜索 vs MCP 搜索 + 兜底)→ bailian-web-search。
|
||||
不要用于普通问答、编程、写作、翻译、摘要、泛搜索,或图片理解等宿主自己能做的任务(普通问答、编程、写作、翻译、摘要、泛搜索不触发)。
|
||||
未命名用量/额度问题:先问用户使用哪个产品,再运行 `bl usage` / `bl quota` 查询。
|
||||
---
|
||||
@@ -23,7 +23,7 @@ description: >-
|
||||
|
||||
> **Family hub** — This skill owns Bailian resource commands and the hub `reference/` (apps, knowledge, usage, auth, config, …).
|
||||
> Shared protocol → [`../bailian-protocol/SKILL.md`](../bailian-protocol/SKILL.md) (install the full family with `bl skill init`).
|
||||
> Soft hand-offs by skill name (Read if installed; else `bl … --help` / prompt `bl skill init`): `bailian-gen` (media) · `bailian-finetune` (training) · `bailian-managed-agent` (agents.yaml IaC).
|
||||
> Soft hand-offs by skill name (Read if installed; else `bl … --help` / prompt `bl skill init`): `bailian-gen` (media) · `bailian-finetune` (training) · `bailian-managed-agent` (agents.yaml IaC) · `bailian-web-search` (web search routing).
|
||||
> Do not invoke it for ordinary reasoning, coding, writing, translation, summarization, generic research, or image understanding the host agent can complete directly.
|
||||
>
|
||||
> **Install (supported):** `bl skill init`
|
||||
@@ -40,6 +40,7 @@ Domain skills own their own generated reference trees (soft hand-off — do not
|
||||
- `bailian-gen` → `image` / `video` / `speech` / `omni` / `vision` (fallback: `bl image\|video\|speech\|omni\|vision --help`)
|
||||
- `bailian-finetune` → `dataset` / `finetune` / `deploy` (fallback: `bl dataset\|finetune\|deploy --help`)
|
||||
- `bailian-managed-agent` → `managed-agent` (fallback: `bl managed-agent --help`)
|
||||
- `bailian-web-search` → web search **routing** (hub still owns `reference/search.md` flags; **must** route via this skill before `bl search web`)
|
||||
|
||||
Auto-generated from the CLI source at build time (`pnpm --filter bailian-cli run generate:reference`). Before running an unfamiliar command:
|
||||
|
||||
@@ -58,7 +59,6 @@ Use this table only after the decision table in [`bailian-protocol`](../bailian-
|
||||
| User intent | Command | Notes |
|
||||
| ------------------------------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| Explicit Bailian model chat / text execution | `bl text chat` | Default `qwen3.8-max` |
|
||||
| Search inside a Bailian-scoped workflow | `bl search web` | DashScope MCP search; not for generic web research |
|
||||
| Bailian agent / workflow | `bl app call` | Needs `--app-id` |
|
||||
| Find app by name | `bl app list` then `bl app call` | Console auth |
|
||||
| Bailian app memory CRUD (not host-agent memory) | `bl memory *` | [`reference/memory.md`](reference/memory.md) |
|
||||
@@ -79,6 +79,7 @@ Use this table only after the decision table in [`bailian-protocol`](../bailian-
|
||||
| Image / video / speech / omni / vision | → skill `bailian-gen` | Fallback: `bl image\|video\|speech\|omni\|vision --help` |
|
||||
| Dataset / fine-tune / deploy | → skill `bailian-finetune` | Fallback: `bl dataset\|finetune\|deploy --help` |
|
||||
| agents.yaml IaC / managed-agent sessions | → skill `bailian-managed-agent` | Fallback: `bl managed-agent --help`; `apply`/`destroy` need `--yes` after `plan` |
|
||||
| Web search (model-aware routing) | → skill `bailian-web-search` | Token Plan vs MCP path + fallback; fallback: `bl search web --help` |
|
||||
|
||||
Flags, usage, and examples: see hub [`reference/`](reference/index.md) or `bl <command> --help` — do not guess flags. Domain command details live in the owning skill's `reference/`.
|
||||
|
||||
@@ -117,6 +118,7 @@ schema-export commands.
|
||||
## Routing reminders
|
||||
|
||||
- Image/video/audio generation or editing → skill `bailian-gen` (class 3 consent from `bailian-protocol`). Fine-tuning / datasets / deployments → `bailian-finetune`. agents.yaml IaC → `bailian-managed-agent`. Soft hand-off: Read sibling skill if installed; else `bl … --help` or prompt `bl skill init`. Image understanding the host agent can do → host-first; use `bl vision` / `bl omni` only when the user names a Bailian model or the media (video/audio files) exceeds host capability.
|
||||
- Web search inside a Bailian workflow → skill `bailian-web-search` (model-aware routing: Token Plan → model-native search; default → `bl search web`; MCP failure → fall back once). Generic web research the host can do stays host-first — do not bounce it through `bl`.
|
||||
- Answer ordinary reasoning, coding, writing, translation, summarization, and generic research with the host agent's native capabilities; do not bounce them through `bl text chat` or `bl search web`.
|
||||
- Usage / quota / credits questions that do not name a product → ask which product (Bailian or another AI service) first; run `bl usage` / `bl quota` only after the user picks Bailian or Bailian context is already established.
|
||||
- "Remember this" and memory requests default to the host agent's own memory; `bl memory *` is only for Bailian app memory resources.
|
||||
|
||||
@@ -362,16 +362,16 @@ bl knowledge chunk update --index-id idx-xxx --chunk-id chunk-xxx --doc-id file-
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | --------------------------------------------------------------- |
|
||||
| `--name <text>` | string | yes | Collection name |
|
||||
| `--description <text>` | string | yes | Collection description (required by the server) |
|
||||
| `--store-type <type>` | string | no | Storage: platform (managed) or custom (your own OSS bucket) |
|
||||
| `--oss-region <id>` | string | no | OSS region id (required with --store-type custom) |
|
||||
| `--oss-bucket <name>` | string | no | OSS bucket name (required with --store-type custom) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | ----------------------------------------------------------------------------------- |
|
||||
| `--name <text>` | string | yes | Collection name |
|
||||
| `--description <text>` | string | yes | What this collection holds and what it is for — tells collections apart in the list |
|
||||
| `--store-type <type>` | string | no | Storage: platform (managed) or custom (your own OSS bucket) |
|
||||
| `--oss-region <id>` | string | no | OSS region id (required with --store-type custom) |
|
||||
| `--oss-bucket <name>` | string | no | OSS bucket name (required with --store-type custom) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -420,27 +420,28 @@ bl knowledge collection get --name my-collection
|
||||
|
||||
### `bl knowledge create`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | --------------------------------------------------------------------------------- |
|
||||
| **Name** | `knowledge create` |
|
||||
| **Description** | Create a knowledge base and import data-center files or categories |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl knowledge create --name <text> (--doc-id <id> \| --category-id <id>) [flags]` |
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------ |
|
||||
| **Name** | `knowledge create` |
|
||||
| **Description** | Create a knowledge base and import data-center files or categories |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl knowledge create --name <text> --description <text> (--doc-id <id> \| --category-id <id>) [flags]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------ | -------- | ------------------------------------------------------------------------------------ |
|
||||
| `--name <text>` | string | yes | Knowledge base name (1-20 chars, unique in workspace) |
|
||||
| `--doc-id <id>` | array | no | Data-center file id to import (repeatable); mutually exclusive with --category-id |
|
||||
| `--category-id <id>` | array | no | Import every file under this category (repeatable); mutually exclusive with --doc-id |
|
||||
| `--embedding-model <name>` | string | no | Embedding model name (default: text-embedding-v4) |
|
||||
| `--chunk-size <n>` | number | no | Chunk size in characters (default: 600, recommended 300-800) |
|
||||
| `--wait` | switch | no | Poll the initial import job to a terminal state |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 5) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `--name <text>` | string | yes | Knowledge base name (1-20 chars, unique in workspace) |
|
||||
| `--description <text>` | string | yes | What this knowledge base holds and what it is for — tells bases apart in the workspace list (1-500 chars) |
|
||||
| `--doc-id <id>` | array | no | Data-center file id to import (repeatable); mutually exclusive with --category-id |
|
||||
| `--category-id <id>` | array | no | Import every file under this category (repeatable); mutually exclusive with --doc-id |
|
||||
| `--embedding-model <name>` | string | no | Embedding model name (default: text-embedding-v4) |
|
||||
| `--chunk-size <n>` | number | no | Chunk size in characters (default: 600, recommended 300-800) |
|
||||
| `--wait` | switch | no | Poll the initial import job to a terminal state |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 5) |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -451,11 +452,11 @@ bl knowledge collection get --name my-collection
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl knowledge create --name demo --doc-id file-xxx --workspace-id ws-xxx
|
||||
bl knowledge create --name demo --description 'product docs' --doc-id file-xxx --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
```bash
|
||||
bl knowledge create --name demo --category-id cate-xxx --wait
|
||||
bl knowledge create --name demo --description 'product docs' --category-id cate-xxx --wait
|
||||
```
|
||||
|
||||
### `bl knowledge delete`
|
||||
@@ -911,6 +912,10 @@ bl knowledge list --name demo --page-number 2 --page-size 50
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- --rerank-model requires the target knowledge base to already have a rerank model configured; otherwise every value is rejected.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
@@ -999,15 +1004,15 @@ bl knowledge service copy --agent-id aid-xxx --workspace-id ws-xxx
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | ----------------------------------------------------------------- |
|
||||
| `--name <text>` | string | yes | Service name (up to 200 chars, unique per scene in the workspace) |
|
||||
| `--scene <scene>` | string | yes | Service scene: chat (Q&A) or search (retrieval) |
|
||||
| `--description <text>` | string | no | Service description (up to 1000 chars) |
|
||||
| `--index-id <id>` | string | no | Bind this knowledge base; other settings use server defaults |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| ---------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--name <text>` | string | yes | Service name (up to 200 chars, unique per scene in the workspace) |
|
||||
| `--scene <scene>` | string | yes | Service scene: chat (Q&A) or search (retrieval) |
|
||||
| `--description <text>` | string | no | What this service answers and who it serves — recommended: agents read it to pick the right service (up to 1000 chars) |
|
||||
| `--index-id <id>` | string | no | Bind this knowledge base; other settings use server defaults |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
@@ -1018,7 +1023,7 @@ bl knowledge service copy --agent-id aid-xxx --workspace-id ws-xxx
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl knowledge service create --name my-qa --scene chat --workspace-id ws-xxx
|
||||
bl knowledge service create --name my-qa --scene chat --description 'answers product FAQs' --workspace-id ws-xxx
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -1141,18 +1146,18 @@ bl knowledge service get --agent-id aid-xxx --agent-version beta
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ----------------------------------------------------------------------- |
|
||||
| `--scene <scene>` | string | yes | Service scene: chat (Q&A) or search (retrieval). Required by the server |
|
||||
| `--status <status>` | string | no | Filter by status: draft, deployed (includes edited) or deleted |
|
||||
| `--name <text>` | string | no | Filter by service name (fuzzy match) |
|
||||
| `--agent-id <id>` | string | no | Filter by exact agent ID |
|
||||
| `--index-id <id>` | string | no | Filter by exact linked knowledge base (pipeline) ID |
|
||||
| `--page-number <n>` | number | no | Page number (default: 1) |
|
||||
| `--page-size <n>` | number | no | Page size per request |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | --------------------------------------------------------------- |
|
||||
| `--scene <scene>` | string | yes | Service scene: chat (Q&A) or search (retrieval) |
|
||||
| `--status <status>` | string | no | Filter by status: draft, deployed (includes edited) or deleted |
|
||||
| `--name <text>` | string | no | Filter by service name (fuzzy match) |
|
||||
| `--agent-id <id>` | string | no | Filter by exact agent ID |
|
||||
| `--index-id <id>` | string | no | Filter by exact linked knowledge base (pipeline) ID |
|
||||
| `--page-number <n>` | number | no | Page number (default: 1) |
|
||||
| `--page-size <n>` | number | no | Page size per request |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID for API endpoint URL (or set BAILIAN_WORKSPACE_ID) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
> Auto-generated from `packages/cli/src/commands.ts`. Do not edit by hand.
|
||||
> Regenerate: `pnpm --filter bailian-cli run generate:reference`.
|
||||
|
||||
> **Agent routing (mandatory):** Before running any `bl search *` command, Read skill `bailian-web-search` if installed and follow its identity-based routing (Token Plan → model-native web search; default → MCP; eligible MCP failures → fall back once). Do **not** call `bl search web` directly from this reference alone — Token Plan keys cannot authorize Bailian MCP search. If that skill is missing, run `bl skill init` or fall back to `bl search web --help` / `bl text chat --help` after checking `bl config show --output json`.
|
||||
|
||||
Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-finetune
|
||||
metadata:
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bailian-gen
|
||||
metadata:
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
name: bailian-managed-agent
|
||||
metadata:
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
阿里云百炼托管 Agent 声明式基础设施入口:用户要创建agent、初始化 agents.yaml、校验或预览 agent 配置变更、
|
||||
创建/更新/销毁百炼托管 Agent 或 Deployment、和托管 agent 对话、查会话事件历史、导入或取消跟踪远端资源时使用
|
||||
创建/更新/销毁百炼托管 Agent 或 Deployment、在 Workbench 编辑和调试已有声明、管理 agents.yaml 本地 Git 版本、
|
||||
生成 CI 仓库、和托管 agent 对话、查会话事件历史、导入或取消跟踪远端资源时使用
|
||||
`bl managed-agent`。以 agents.yaml 为唯一事实源做 IaC:init 建脚手架、validate 离线校验、plan 预览 diff、
|
||||
apply / destroy 变更远端资源且必须带 `--yes`,务必先 plan 给用户看 diff 再让其确认。
|
||||
反触发:调用已上线的百炼应用/智能体走 bailian-app-call 或 `bl app`;宿主 agent 自身的记忆、技能、
|
||||
@@ -20,11 +21,12 @@ description: >-
|
||||
|
||||
## Safety guardrail (the most important rule)
|
||||
|
||||
`apply` / `destroy` **mutate remote resources** and only execute when `--yes` is passed:
|
||||
`apply` / `destroy` **mutate remote resources**. Interactive execution requires `--yes`; `apply --ci` is only for an already approved CI workflow:
|
||||
|
||||
1. Always run `bl managed-agent plan` first and show the diff to the user.
|
||||
2. Only after explicit user confirmation, retry `apply` / `destroy` with `--yes`.
|
||||
3. Never add `--yes` on your own initiative before the user has confirmed.
|
||||
4. Never use `--ci` to bypass user confirmation in an interactive task. CI mode blocks deletes and remote drift, but still mutates remote resources.
|
||||
|
||||
## IaC lifecycle
|
||||
|
||||
@@ -36,6 +38,23 @@ description: >-
|
||||
5. Destroy bl managed-agent destroy --yes # only after user confirmation
|
||||
```
|
||||
|
||||
## Workbench, local versions, and CI
|
||||
|
||||
| Intent | Command |
|
||||
| ------------------------------------------- | ---------------------------------------------- |
|
||||
| Launch project resource editing | `bl managed-agent workbench` |
|
||||
| Launch one Agent Session Preview | `bl managed-agent playground --agent <id>` |
|
||||
| Create or upgrade a local Git/CI repository | `bl managed-agent init --git <directory>` |
|
||||
| Enable/disable shared automatic versions | `bl managed-agent version enable` / `disable` |
|
||||
| Inspect local version state and history | `bl managed-agent version status` / `list` |
|
||||
| Preview or restore a historical YAML | `bl managed-agent version preview` / `restore` |
|
||||
|
||||
- Bailian CLI and Workbench use the same repository-local switch for the same Git worktree and `agents.yaml` path. The switch lives in private Git metadata and is not cloned or pushed.
|
||||
- When enabled, a fully successful Apply creates a local commit containing only `agents.yaml`. Failed, partial, cancelled, and `--refresh-only` Apply runs do not commit.
|
||||
- `version restore` writes the historical YAML to the working tree. It does not move `HEAD`, restore `agents.state.json`, create a commit, or Apply remote changes.
|
||||
- Workbench can edit local drafts while Apply is running, but saving/version mutations are blocked until Apply completes. External file edits are detected through revision checks.
|
||||
- `init --git` never creates a remote repository or pushes. The generated Aone CI uses `apply --ci`, which blocks deletes and remote drift; review destructive changes in a separately approved workflow.
|
||||
|
||||
## Deployment as IaC
|
||||
|
||||
Deployment 与 Agent 一样声明在 `agents.yaml` 中,并复用同一条 `validate → plan → apply → destroy` IaC 链路;
|
||||
|
||||
@@ -9,31 +9,39 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
|
||||
## Quick index
|
||||
|
||||
| Command | Authentication | Description | Detail |
|
||||
| --------------------------------- | -------------- | ------------------------------------------------------------- | ------------------------------------ |
|
||||
| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent init` | No Auth | Create a new agents.yaml template | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session create` | API Key | Create a new session for an agent | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session delete` | API Key | Delete a session | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session events` | API Key | List event history for a session | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session get` | API Key | Get details of a session | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session list` | API Key | List sessions from the provider | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent skill-list` | API Key | List skills from the provider's skill catalog | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state list` | No Auth | List resources tracked in agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | [managed-agent.md](managed-agent.md) |
|
||||
| Command | Authentication | Description | Detail |
|
||||
| ---------------------------------- | -------------- | ------------------------------------------------------------- | ------------------------------------ |
|
||||
| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent init` | No Auth | Create an agents.yaml template or a local CI/Git project | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent playground` | API Key | Launch a Session Preview for an agents.yaml Agent | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session create` | API Key | Create a new session for an agent | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session delete` | API Key | Delete a session | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session events` | API Key | List event history for a session | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session get` | API Key | Get details of a session | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session list` | API Key | List sessions from the provider | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent skill-list` | API Key | List skills from the provider's skill catalog | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state list` | No Auth | List resources tracked in agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent state show` | No Auth | Show details of a resource in agents state | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent version disable` | No Auth | Disable Apply-time Git versioning without removing history | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent version enable` | No Auth | Enable Apply-time Git versioning for agents.yaml | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent version list` | No Auth | List current-branch commits that changed agents.yaml | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent version preview` | No Auth | Preview a historical agents.yaml version | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent version restore` | No Auth | Restore a historical agents.yaml version to the working tree | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent version status` | No Auth | Show local Git versioning status for agents.yaml | [managed-agent.md](managed-agent.md) |
|
||||
| `bl managed-agent workbench` | API Key | Launch the agents.yaml project Workbench | [managed-agent.md](managed-agent.md) |
|
||||
|
||||
## By group
|
||||
|
||||
| Group | Commands | Reference |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| `managed-agent` | `apply`, `destroy`, `init`, `plan`, `session create`, `session delete`, `session events`, `session get`, `session list`, `session run`, `session send`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate` | [managed-agent.md](managed-agent.md) |
|
||||
| Group | Commands | Reference |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| `managed-agent` | `apply`, `destroy`, `init`, `plan`, `playground`, `session create`, `session delete`, `session events`, `session get`, `session list`, `session run`, `session send`, `skill-list`, `state import`, `state list`, `state rm`, `state show`, `validate`, `version disable`, `version enable`, `version list`, `version preview`, `version restore`, `version status`, `workbench` | [managed-agent.md](managed-agent.md) |
|
||||
|
||||
## Global flags
|
||||
|
||||
|
||||
@@ -7,36 +7,44 @@ Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Authentication | Description |
|
||||
| --------------------------------- | -------------- | ------------------------------------------------------------- |
|
||||
| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources |
|
||||
| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state |
|
||||
| `bl managed-agent init` | No Auth | Create a new agents.yaml template |
|
||||
| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure |
|
||||
| `bl managed-agent session create` | API Key | Create a new session for an agent |
|
||||
| `bl managed-agent session delete` | API Key | Delete a session |
|
||||
| `bl managed-agent session events` | API Key | List event history for a session |
|
||||
| `bl managed-agent session get` | API Key | Get details of a session |
|
||||
| `bl managed-agent session list` | API Key | List sessions from the provider |
|
||||
| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response |
|
||||
| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response |
|
||||
| `bl managed-agent skill-list` | API Key | List skills from the provider's skill catalog |
|
||||
| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state |
|
||||
| `bl managed-agent state list` | No Auth | List resources tracked in agents state |
|
||||
| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely |
|
||||
| `bl managed-agent state show` | No Auth | Show details of a resource in agents state |
|
||||
| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) |
|
||||
| Command | Authentication | Description |
|
||||
| ---------------------------------- | -------------- | ------------------------------------------------------------- |
|
||||
| `bl managed-agent apply` | API Key | Apply planned changes to create/update/delete agent resources |
|
||||
| `bl managed-agent destroy` | API Key | Destroy all managed agent resources tracked in state |
|
||||
| `bl managed-agent init` | No Auth | Create an agents.yaml template or a local CI/Git project |
|
||||
| `bl managed-agent plan` | API Key | Show what changes would be applied to agent infrastructure |
|
||||
| `bl managed-agent playground` | API Key | Launch a Session Preview for an agents.yaml Agent |
|
||||
| `bl managed-agent session create` | API Key | Create a new session for an agent |
|
||||
| `bl managed-agent session delete` | API Key | Delete a session |
|
||||
| `bl managed-agent session events` | API Key | List event history for a session |
|
||||
| `bl managed-agent session get` | API Key | Get details of a session |
|
||||
| `bl managed-agent session list` | API Key | List sessions from the provider |
|
||||
| `bl managed-agent session run` | API Key | Create a session, send a message, and stream the response |
|
||||
| `bl managed-agent session send` | API Key | Send a message to an existing session and stream the response |
|
||||
| `bl managed-agent skill-list` | API Key | List skills from the provider's skill catalog |
|
||||
| `bl managed-agent state import` | API Key | Import an existing remote resource into agents state |
|
||||
| `bl managed-agent state list` | No Auth | List resources tracked in agents state |
|
||||
| `bl managed-agent state rm` | No Auth | Remove a resource from state without destroying it remotely |
|
||||
| `bl managed-agent state show` | No Auth | Show details of a resource in agents state |
|
||||
| `bl managed-agent validate` | No Auth | Validate an agents.yaml configuration (offline) |
|
||||
| `bl managed-agent version disable` | No Auth | Disable Apply-time Git versioning without removing history |
|
||||
| `bl managed-agent version enable` | No Auth | Enable Apply-time Git versioning for agents.yaml |
|
||||
| `bl managed-agent version list` | No Auth | List current-branch commits that changed agents.yaml |
|
||||
| `bl managed-agent version preview` | No Auth | Preview a historical agents.yaml version |
|
||||
| `bl managed-agent version restore` | No Auth | Restore a historical agents.yaml version to the working tree |
|
||||
| `bl managed-agent version status` | No Auth | Show local Git versioning status for agents.yaml |
|
||||
| `bl managed-agent workbench` | API Key | Launch the agents.yaml project Workbench |
|
||||
|
||||
## Command details
|
||||
|
||||
### `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 <path>] [--provider <name>] [--yes] [--concurrency <n>]` |
|
||||
| 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 <path>] [--provider <name>] [--yes \| --ci] [--no-refresh] [--refresh-only] [--concurrency <n>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
@@ -45,7 +53,9 @@ Index: [index.md](index.md)
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
| `--provider <name>` | string | no | Target provider (default: all configured) |
|
||||
| `--yes` | switch | no | Confirm and apply without an interactive prompt (required to mutate) |
|
||||
| `--ci` | switch | no | Run non-interactively while blocking deletes and remote drift |
|
||||
| `--no-refresh` | switch | no | Skip refreshing state from remote before planning |
|
||||
| `--refresh-only` | switch | no | Refresh state without mutating remote resources |
|
||||
| `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
@@ -66,6 +76,10 @@ bl managed-agent apply --yes
|
||||
bl managed-agent apply --provider bailian --yes
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent apply --ci
|
||||
```
|
||||
|
||||
### `bl managed-agent destroy`
|
||||
|
||||
| Field | Value |
|
||||
@@ -103,12 +117,12 @@ bl managed-agent destroy --yes --cascade
|
||||
|
||||
### `bl managed-agent init`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent init` |
|
||||
| **Description** | Create a new agents.yaml template |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent init [--provider <name>] [--agent-name <name>] [--file <path>] [--force]` |
|
||||
| Field | Value |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent init` |
|
||||
| **Description** | Create an agents.yaml template or a local CI/Git project |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent init [--provider <name>] [--agent-name <name>] [--file <path>] [--git <directory>] [--force]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
@@ -117,6 +131,7 @@ bl managed-agent destroy --yes --cascade
|
||||
| `--provider <bailian\|claude\|qoder\|ark\|all>` | string | no | Provider: bailian, claude, qoder, ark, all (default: bailian) |
|
||||
| `--agent-name <name>` | string | no | Name of the first agent (default: assistant) |
|
||||
| `--file <path>` | string | no | Output config path (default: agents.yaml) |
|
||||
| `--git <directory>` | string | no | Create or add CI/Git scaffolding in this project directory |
|
||||
| `--force` | switch | no | Overwrite an existing config file |
|
||||
|
||||
#### Examples
|
||||
@@ -130,7 +145,11 @@ bl managed-agent init --provider bailian --agent-name assistant
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent init --provider all
|
||||
bl managed-agent init --git ./my-agents
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent init --git .
|
||||
```
|
||||
|
||||
### `bl managed-agent plan`
|
||||
@@ -174,6 +193,47 @@ bl managed-agent plan --provider bailian
|
||||
bl managed-agent plan --no-refresh
|
||||
```
|
||||
|
||||
### `bl managed-agent playground`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent playground` |
|
||||
| **Description** | Launch a Session Preview for an agents.yaml Agent |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl managed-agent playground [--file <path>] [--agent <id>] [--port <n>] [--no-open]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | --------------------------------------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
| `--port <n>` | number | no | Local port (default: 4848) |
|
||||
| `--no-open` | switch | no | Do not open a browser automatically |
|
||||
| `--agent <id>` | string | no | Agent to preview (required when the project declares multiple Agents) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
|
||||
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
|
||||
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
|
||||
- Workbench requires Node.js 22+ and starts the shared @openagentpack/playground package locally. It does not push Git commits or switch branches.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent playground
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent playground --agent assistant
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent playground --file agents.yaml --no-open
|
||||
```
|
||||
|
||||
### `bl managed-agent session create`
|
||||
|
||||
| Field | Value |
|
||||
@@ -618,3 +678,198 @@ bl managed-agent validate
|
||||
```bash
|
||||
bl managed-agent validate --file agents.yaml
|
||||
```
|
||||
|
||||
### `bl managed-agent version disable`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ---------------------------------------------------------- |
|
||||
| **Name** | `managed-agent version disable` |
|
||||
| **Description** | Disable Apply-time Git versioning without removing history |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent version disable [--file <path>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------- | ------ | -------- | --------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent version disable
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent version disable --file agents.yaml
|
||||
```
|
||||
|
||||
### `bl managed-agent version enable`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------- |
|
||||
| **Name** | `managed-agent version enable` |
|
||||
| **Description** | Enable Apply-time Git versioning for agents.yaml |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent version enable [--file <path>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------- | ------ | -------- | --------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent version enable
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent version enable --file agents.yaml
|
||||
```
|
||||
|
||||
### `bl managed-agent version list`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | --------------------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent version list` |
|
||||
| **Description** | List current-branch commits that changed agents.yaml |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent version list [--file <path>] [--limit <n>] [--cursor <cursor>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------- | ------ | -------- | -------------------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
| `--limit <n>` | number | no | Maximum versions to return (default: 50, max: 100) |
|
||||
| `--cursor <cursor>` | string | no | Pagination cursor returned by the previous page |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent version list
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent version list --limit 20 --output json
|
||||
```
|
||||
|
||||
### `bl managed-agent version preview`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ---------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent version preview` |
|
||||
| **Description** | Preview a historical agents.yaml version |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent version preview --commit <full-sha> [--file <path>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | --------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
| `--commit <full-sha>` | string | yes | Full commit SHA from the current branch |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent version preview --commit <full-sha>
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent version preview --commit <full-sha> --output json
|
||||
```
|
||||
|
||||
### `bl managed-agent version restore`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------ |
|
||||
| **Name** | `managed-agent version restore` |
|
||||
| **Description** | Restore a historical agents.yaml version to the working tree |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent version restore --commit <full-sha> [--file <path>] [--yes]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------- | ------ | -------- | ------------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
| `--commit <full-sha>` | string | yes | Full commit SHA from the current branch |
|
||||
| `--yes` | switch | no | Restore without an interactive confirmation |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent version restore --commit <full-sha>
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent version restore --commit <full-sha> --yes --output json
|
||||
```
|
||||
|
||||
### `bl managed-agent version status`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------- |
|
||||
| **Name** | `managed-agent version status` |
|
||||
| **Description** | Show local Git versioning status for agents.yaml |
|
||||
| **Authentication** | No Auth |
|
||||
| **Usage** | `bl managed-agent version status [--file <path>]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------- | ------ | -------- | --------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent version status
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent version status --file agents.yaml --output json
|
||||
```
|
||||
|
||||
### `bl managed-agent workbench`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | --------------------------------------------------------------------- |
|
||||
| **Name** | `managed-agent workbench` |
|
||||
| **Description** | Launch the agents.yaml project Workbench |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl managed-agent workbench [--file <path>] [--port <n>] [--no-open]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------ | ------ | -------- | --------------------------------------- |
|
||||
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
|
||||
| `--port <n>` | number | no | Local port (default: 4848) |
|
||||
| `--no-open` | switch | no | Do not open a browser automatically |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Notes
|
||||
|
||||
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
|
||||
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
|
||||
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
|
||||
- Workbench requires Node.js 22+ and starts the shared @openagentpack/playground package locally. It does not push Git commits or switch branches.
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl managed-agent workbench
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent workbench --file agents.yaml --no-open
|
||||
```
|
||||
|
||||
```bash
|
||||
bl managed-agent workbench --port 4949
|
||||
```
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
name: bailian-protocol
|
||||
metadata:
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
阿里云百炼 `bl` 家族共享执行协议(consent 确认、版本预检、鉴权/安装、错误上报、本地文件与输出约定)。
|
||||
不是面向用户意图的业务入口;当任一 bailian-* 业务 skill(bailian-cli / bailian-gen /
|
||||
bailian-finetune / bailian-managed-agent)执行前需要公共上下文,或用户首次安装/鉴权/`bl` 报错需上报时读取本 skill。
|
||||
bailian-finetune / bailian-managed-agent / bailian-web-search)执行前需要公共上下文,或用户首次安装/鉴权/`bl` 报错需上报时读取本 skill。
|
||||
官方安装为整包:`bl skill init`(与业务 skill 同装)。
|
||||
---
|
||||
|
||||
@@ -51,6 +51,7 @@ After approval, treat Bailian as selected for the current task. Do not ask again
|
||||
| 生图 / 生视频 / 语音 / 图片理解 / 视频理解 / omni / vision | `bailian-gen` | `bl image\|video\|speech\|omni\|vision --help` |
|
||||
| 精调 / 数据集 / 部署 | `bailian-finetune` | `bl dataset\|finetune\|deploy --help` |
|
||||
| agents.yaml IaC | `bailian-managed-agent` | `bl managed-agent --help` |
|
||||
| 联网搜索 / web search(模型路由 + 兜底) | `bailian-web-search` | `bl search web --help` |
|
||||
| 应用 / 知识库 / 用量 / 鉴权配置等资源 | `bailian-cli` | `bl app\|knowledge\|usage\|auth\|config --help` |
|
||||
|
||||
**共享协议** vs **软 hand-off**:
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: bailian-web-search
|
||||
metadata:
|
||||
version: "1.17.1"
|
||||
requires:
|
||||
bins: ["bl"]
|
||||
description: >-
|
||||
阿里云百炼联网搜索(web search)入口:为联网搜索 / 网页搜索 / 查最新资讯做路径分发。
|
||||
先识别当前连接身份:Token Plan(profile `token-plan` 或 base_url host 为
|
||||
`token-plan.<region>.maas.aliyuncs.com`)→ 模型自带搜索
|
||||
(`bl text chat --api responses --tool '{"type":"web_search"}'`);其他 / 默认 →
|
||||
Bailian MCP(`bl search web`);仅在 MCP 鉴权失败、未开通或传输失败时兜底一次到模型自带搜索。
|
||||
Token Plan 联网搜索失败、两条路径鉴权混淆的排查也走本技能。
|
||||
反触发:宿主可完成的 普通问答 / 编程 / 写作 → 不触发;知识库 RAG → bailian-cli
|
||||
(`bl knowledge`);生图/生视频/语音 → bailian-gen;精调/训练 → bailian-finetune;
|
||||
agents.yaml → bailian-managed-agent。
|
||||
共享协议(consent / 版本预检 / 鉴权 / 错误上报)在 bailian-protocol;官方安装 `bl skill init`。
|
||||
---
|
||||
|
||||
# Bailian web search routing (`bailian-web-search`)
|
||||
|
||||
**CRITICAL — Before executing, MUST read the shared protocol in [`../bailian-protocol/SKILL.md`](../bailian-protocol/SKILL.md): Provider selection and consent, Version & updates (pre-flight checklist), Setup & auth, and CLI errors: report an issue. If that protocol file is missing, stop and run `bl skill init`; do not guess auth/consent.**
|
||||
|
||||
> **Scope:** model-aware routing for web search inside Agent-driven `bl` workflows — this skill owns the routing decision only.
|
||||
> Command flags / usage / examples: Read skill `bailian-cli` reference if installed; else `bl search web --help` / `bl text chat --help` — do not guess flags.
|
||||
>
|
||||
> **Install (supported):** `bl skill init`
|
||||
|
||||
Token Plan keys do not authorize Bailian MCP search — never route them to `bl search web`.
|
||||
|
||||
## Routing workflow
|
||||
|
||||
### Step 1 — Identify the active model identity
|
||||
|
||||
Run (no auth needed):
|
||||
|
||||
```bash
|
||||
bl config show --output json
|
||||
```
|
||||
|
||||
Treat the connection as **Token Plan** when **either** condition holds — this mirrors the CLI's own endpoint detection (`usesTokenPlanEndpoint`), so routing stays consistent with command behavior:
|
||||
|
||||
- `config` (active profile name) is `token-plan`, **or**
|
||||
- the hostname of `base_url` matches `token-plan.<region>.maas.aliyuncs.com` (e.g. `https://token-plan.cn-beijing.maas.aliyuncs.com`).
|
||||
|
||||
Do not rely on the profile name alone: users can create additional Token Plan profiles under custom names, and only the Base URL host check catches those. Anything else → **default** identity.
|
||||
|
||||
### Step 2 — Route by identity
|
||||
|
||||
| Condition | Route | Notes |
|
||||
| ------------------------- | ------------------------------- | --------------------------------------------------- |
|
||||
| Token Plan model identity | Model-native path | Preferred — the only path Token Plan keys authorize |
|
||||
| Any other model / default | MCP path | Default for regular DashScope API keys |
|
||||
| MCP path (eligible fail) | Model-native path, exactly once | Fallback — only for the failure classes below |
|
||||
|
||||
**User override:** if the user names a specific path or command, follow it **except** under Token Plan identity: do not call `bl search web` even if the user asks for MCP — explain that Token Plan keys cannot authorize Bailian MCP search, then use the model-native path (or ask once whether to switch profile / key).
|
||||
|
||||
### Model-native path (Token Plan preferred / fallback target)
|
||||
|
||||
```bash
|
||||
bl text chat --api responses --tool '{"type":"web_search"}' --message "搜索近期的阿里云新闻"
|
||||
```
|
||||
|
||||
- The Responses API enables native web search via the tool definition `{"type":"web_search"}`.
|
||||
- Requires a model with native web search support (Qwen3.7+). **Do not hardcode `--model`:** omit it so the CLI uses `default_text_model` / built-in default; only pass `--model` when the user named one.
|
||||
- Write `--message` in the user's language; the reply language follows the prompt (see `bailian-protocol` → Respond in the user's language).
|
||||
- Flags / usage: Read skill `bailian-cli` if installed, else `bl text chat --help`.
|
||||
|
||||
### MCP path (default)
|
||||
|
||||
```bash
|
||||
bl search web --query "阿里云百炼最新功能"
|
||||
```
|
||||
|
||||
- Requires the WebSearch MCP to be activated for the current key; on the not-activated error the CLI appends an activation hint with the marketplace URL — relay it to the user.
|
||||
- Flags / usage: Read skill `bailian-cli` if installed, else `bl search web --help`.
|
||||
|
||||
### Fallback (MCP → model-native, exactly once)
|
||||
|
||||
**Do not** fall back on every non-zero exit. Fall back **only** when `bl search web` fails for one of these classes (match stderr / message):
|
||||
|
||||
- **auth / permission** — key not valid for the MCP service (e.g. Token Plan key misrouted by identity detection),
|
||||
- **MCP not activated** — `MCP request failed: 404` with `未开通` / `MCP不存在` / `MCP_IS_INVALID` (CLI may append an activation hint),
|
||||
- **MCP transport** — 405 / Streamable-HTTP unsupported, or clear network / timeout / DNS failures reaching the MCP endpoint.
|
||||
|
||||
**Do not fall back** for: missing `--query` / USAGE errors, rate limits, content-policy / business errors from a successful MCP session, or empty-but-successful result sets. Report those verbatim and stop (or ask the user); do not burn a Responses call.
|
||||
|
||||
Fallback discipline:
|
||||
|
||||
1. Re-issue the same query via the model-native path (omit `--model` unless the user named one).
|
||||
2. If the fallback succeeds, tell the user the MCP path failed and — when the not-activated hint appeared — that activating the WebSearch MCP restores the default path.
|
||||
3. If the fallback also fails, stop and report both errors verbatim; follow the issue-reporting flow in `bailian-protocol` (ask once). Never loop retries.
|
||||
|
||||
## Quick examples
|
||||
|
||||
```bash
|
||||
# Step 1: identify the active model identity
|
||||
bl config show --output json
|
||||
|
||||
# Token Plan identity → model-native web search (no --model unless user named one)
|
||||
bl text chat --api responses --tool '{"type":"web_search"}' --message "搜索近期的阿里云新闻"
|
||||
|
||||
# Default identity → Bailian MCP search
|
||||
bl search web --query "阿里云百炼最新功能"
|
||||
|
||||
# Eligible MCP failure → fall back once (same rule: no hardcoded --model)
|
||||
bl text chat --api responses --tool '{"type":"web_search"}' --message "阿里云百炼最新功能"
|
||||
```
|
||||
|
||||
## Routing reminders
|
||||
|
||||
- Generic web research the host can do, ordinary Q&A, coding, writing → host-first; do not invoke `bl` (class 1 in `bailian-protocol`). Route only when the user names Bailian / DashScope / `bl` or continues an existing `bl` workflow (class 4).
|
||||
- Knowledge-base / RAG over Bailian corpora → hub skill `bailian-cli` (`bl knowledge`), not this skill.
|
||||
- Summarize search results in the user's language; on the model-native path the CLI injects no default language — if a fixed language is required, pass `--system` in that language (do not hardcode 简体中文).
|
||||
- Other Bailian workflows (apps / usage / config) → hub skill `bailian-cli`; media generation → `bailian-gen`; fine-tuning → `bailian-finetune`; agents.yaml → `bailian-managed-agent`. Soft hand-off by skill name: Read if installed, else `bl … --help` or prompt `bl skill init`.
|
||||
|
||||
## references
|
||||
|
||||
- [bailian-protocol](../bailian-protocol/SKILL.md) — shared protocol (install via `bl skill init`)
|
||||
- skill `bailian-cli` — hub command reference for `bl search web` / `bl text chat` (soft hand-off; fallback: `--help`)
|
||||
@@ -60,6 +60,21 @@ const GENERATED_BANNER =
|
||||
"> Auto-generated from `packages/cli/src/commands.ts`. Do not edit by hand.\n" +
|
||||
"> Regenerate: `pnpm --filter bailian-cli run generate:reference`.";
|
||||
|
||||
/**
|
||||
* Optional agent-facing notice injected at the top of a group's reference file.
|
||||
* Used when the hub owns the command docs but a domain skill owns the routing
|
||||
* decision (e.g. web search must go through bailian-web-search first).
|
||||
*/
|
||||
const GROUP_AGENT_NOTICE: Readonly<Record<string, string>> = {
|
||||
search:
|
||||
"> **Agent routing (mandatory):** Before running any `bl search *` command, " +
|
||||
"Read skill `bailian-web-search` if installed and follow its identity-based routing " +
|
||||
"(Token Plan → model-native web search; default → MCP; eligible MCP failures → fall back once). " +
|
||||
"Do **not** call `bl search web` directly from this reference alone — Token Plan keys cannot " +
|
||||
"authorize Bailian MCP search. If that skill is missing, run `bl skill init` or fall back to " +
|
||||
"`bl search web --help` / `bl text chat --help` after checking `bl config show --output json`.",
|
||||
};
|
||||
|
||||
const AUTH_LABELS = {
|
||||
apiKey: "API Key",
|
||||
console: "Console",
|
||||
@@ -177,11 +192,13 @@ function ownerSkillForGroup(group: string): string {
|
||||
}
|
||||
|
||||
function buildGroupFile(group: string, groupEntries: [string, AnyCommand][]): string {
|
||||
const agentNotice = GROUP_AGENT_NOTICE[group];
|
||||
const lines: string[] = [
|
||||
`# \`bl ${group}\` commands`,
|
||||
"",
|
||||
GENERATED_BANNER,
|
||||
"",
|
||||
...(agentNotice ? [agentNotice, ""] : []),
|
||||
`Index: [index.md](index.md)`,
|
||||
"",
|
||||
"## Commands in this group",
|
||||
|
||||
@@ -69,6 +69,7 @@ export async function runCheck(options = {}) {
|
||||
"skills/bailian-gen/SKILL.md",
|
||||
"skills/bailian-finetune/SKILL.md",
|
||||
"skills/bailian-managed-agent/SKILL.md",
|
||||
"skills/bailian-web-search/SKILL.md",
|
||||
]),
|
||||
"skills/bailian-cli/reference/",
|
||||
"skills/bailian-gen/reference/",
|
||||
|
||||
Reference in New Issue
Block a user