Compare commits

..

8 Commits

Author SHA1 Message Date
chenanran555 b93e0d586d feat(managed-agent): add Workbench and Git-backed CI versioning
Add Workbench launch and local Git version commands, enable automatic
agents.yaml versioning after successful applies, introduce CI-safe apply
policies, and reuse the shared @openagentpack/local-git package.
2026-08-25 20:41:47 +08:00
clark-fc e76ebee681 Merge pull request #172 from modelstudioai/feat/iteration1-w1-foundation
Feat/iteration1 w1 foundation
2026-08-22 13:23:54 +08:00
zeyu.fz 78a1c547d3 fix(cli): 修复知识库创建描述必填及配置更新误报警告
- `knowledge create` 命令新增 `--description` 参数,设置为必填并且在本地校验长度限制
- 修正 `knowledge service update` 命令更新配置时,避免对服务返回的已知字段误报未知字段警告
- 更新命令帮助文案,明确描述类参数的具体用途
- `knowledge retrieve --rerank-model` 参数帮助说明补充前置条件,提示需预先配置重排序模型
- 新增 `bailian-web-search` 路由技能,支持智能选择搜索入口
- 添加完整的 Knowledge Studio CLI 命令手册,包含详细示例及覆盖范围
2026-08-22 13:09:53 +08:00
zeyu.fz a95ad7242b docs(cli): 添加完整的 KSCLI 命令手册文档
- 新增知识库管理(kb)命令详解,包含创建、查询、更新、删除和状态监控
- 补充文档(doc)管理命令,包括文件上传、导入状态查询、标签管理等
- 增加数据中心文件(file)管理说明,覆盖文件列表、详情、删除操作
- 完善集合与分类管理(collection/category)命令手册,支持创建、查询、删除等功能
- 详细描述 chunk 管理命令,包括添加、更新、查询和删除操作
- 统一说明通用约定,涵盖鉴权、全局参数、输出格式、确认机制及干跑模式
- 提供丰富参数说明、输出格式及使用示例,提升 CLI 使用体验和易用性
2026-08-22 12:19:55 +08:00
zeyu.fz b830a14e11 docs(knowledge): 扩展知识库描述长度限制到 500 字符
- 修改命令行参数文档,将 --description 长度限制由 200 字符增加到 500 字符
- 更新代码校验逻辑,支持描述长度最大 500 字符
- 调整相关提示信息,反映新的长度限制
- 修改测试用例,支持 501 字符的描述参数触发用法错误
- 更新 CLI 参考文档中描述字段的长度说明
2026-08-22 11:52:39 +08:00
zeyu.fz a6291e00e1 docs(knowledge): 强制添加知识库描述参数
- 升级知识库创建命令,`--description` 参数变为必填,描述知识库内容和用途
- 更新所有相关文档示例,统一加入 `--description` 参数和示例文本
- CLI 校验增强,缺失或超长的描述参数本地报错,避免服务端拒绝
- 优化服务创建命令,推荐填写描述以帮助 agent 选择合适服务
- 多个测试用例添加对描述参数的验证和断言
- 知识库和集合列表中描述信息作为区分同类项目的辅助信息显式展示
- 其他细节调整包括命令帮助及参数说明内容的更新
2026-08-22 11:21:22 +08:00
Gong Shiqi 0a63115aed Merge pull request #170 from modelstudioai/feat/add-websearch-skill
Add bailian-web-search routing skill
2026-08-20 19:07:52 +08:00
clh02467605 abad3a6643 feat(skills): add bailian-web-search routing skill 2026-08-19 17:12:16 +08:00
64 changed files with 5620 additions and 170 deletions
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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)。
约定:
+17
View File
@@ -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
+17
View File
@@ -6,6 +6,23 @@
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
## [1.17.1] - 2026-08-22
### 修复
- **`knowledge create``--description` 更新为必填** —— 对齐服务端对知识库描述的必填校验:新增 `--description` 参数并设为必填,在发出请求前于本地校验 1500 个字符的长度限制。`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
### 新增
+5 -5
View File
@@ -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-clihub
hub 路由表:本职命令 + 领域 hand-off 行
+6 -4
View File
@@ -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
```
---
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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 靠它判断该调用哪个服务
**输出**
+248
View File
@@ -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 IDCLI 自动分批。
- 如果某批失败,操作停止,已删除的批次会在错误 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)
+268
View File
@@ -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)
+344
View File
@@ -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)
+157
View File
@@ -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)
+342
View File
@@ -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)
+929
View File
@@ -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` |
+218
View File
@@ -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)
+401
View File
@@ -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 | 是 | 服务agentID |
| `--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 | 是 | 服务agentID |
| `--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-writeCLI 先读取当前 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 | 是 | 服务agentID |
| `--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 | 是 | 服务agentID |
| `--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 | 是 | 源服务agentID |
**输出**
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 -1
View File
@@ -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",
+16
View File
@@ -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,
+3 -2
View File
@@ -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 列表中区分同类知识库1500 个字符)",
},
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,
});
},
});
+12
View File
@@ -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 -1
View File
@@ -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>>;
+1
View File
@@ -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>
+1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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": {
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+6 -4
View File
@@ -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.
+57 -52
View File
@@ -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
+2
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-finetune
metadata:
version: "1.17.0"
version: "1.17.1"
requires:
bins: ["bl"]
description: >-
+1 -1
View File
@@ -1,7 +1,7 @@
---
name: bailian-gen
metadata:
version: "1.17.0"
version: "1.17.1"
requires:
bins: ["bl"]
description: >-
+22 -3
View File
@@ -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 为唯一事实源做 IaCinit 建脚手架、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 链路;
+30 -22
View File
@@ -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
```
+3 -2
View File
@@ -1,13 +1,13 @@
---
name: bailian-protocol
metadata:
version: "1.17.0"
version: "1.17.1"
requires:
bins: ["bl"]
description: >-
阿里云百炼 `bl` 家族共享执行协议consent 确认、版本预检、鉴权/安装、错误上报、本地文件与输出约定)。
不是面向用户意图的业务入口;当任一 bailian-* 业务 skillbailian-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**
+120
View File
@@ -0,0 +1,120 @@
---
name: bailian-web-search
metadata:
version: "1.17.1"
requires:
bins: ["bl"]
description: >-
阿里云百炼联网搜索web search入口为联网搜索 / 网页搜索 / 查最新资讯做路径分发。
先识别当前连接身份Token Planprofile `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`)
+17
View File
@@ -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",
+1
View File
@@ -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/",