# @bty/customer-service-cli 中文文档

规范地址：https://npmcn.com/package/%40bty/customer-service-cli/

版本：0\.8\.5
npm 元数据更新时间：2026-10-31T16:58:08.501Z
中文译文更新时间：2026-10-31T17:22:40.353Z
软件包许可：未声明

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40bty/customer-service-cli
原文对照：https://npmcn.com/package/%40bty/customer-service-cli/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：774 kB，4 个文件
- 模块格式：ES Module
- TypeScript 类型：未声明
- Node.js 要求：\>=20\.0\.0
- 运行依赖：4 个
- 首次发布：2026-03-22T14:34:32.372Z
- 最新版本发布：2026-10-31T16:58:08.307Z
- 已发布版本数：76（近 12 个月 76 个）

## 中文简介

AI客户服务CLI -座席友好

## 安装

```sh
npm install @bty/customer-service-cli
```

也可以使用：`pnpm add @bty/customer-service-cli`、`yarn add @bty/customer-service-cli`、`bun add @bty/customer-service-cli`

## README

# AI 客服 CLI

供客户、内部人员与 Agent 调用的 AI 客服命令行工具。所有输出默认为 JSON 格式，便于程序解析。

## 安装

### 通过 npm 全局安装（推荐）

```bash
npm install -g @bty/customer-service-cli
```

安装后即可直接使用 `cs-cli` 命令。

### 从源码构建

```bash
pnpm install
pnpm build
```

构建产物位于 `dist/bin.js`，可通过 `cs-cli` 命令调用。

### 源码入口与多行配置

推荐在 `packages/customer-service-cli` 目录用 `pnpm exec tsx src/bin.ts <args>` 跑源码；生产配置写入须先取得明确授权，备份目标字段，写后回读并逐字核对。多行 SA 使用 UTF-8 文件（`--situation-file` / `--action-file`），Agent 补丁使用 `--data @patch.json`，文件读取保留 CRLF、末尾换行和字面反斜杠。

仓库的 `pnpm --filter @bty/customer-service-cli --silent dev <args>` 也保留真实换行。它通过本包启动器解码 pnpm 9 的原始 JSON 参数，并用无 shell 的 Node/tsx 运行 CLI，避免 pnpm 9 把 LF 变成字面 `\n` 或展开 `$()` 等内容；支持 macOS/Linux 的 POSIX shell + pnpm 9（仓库固定 9.15.4）。Windows、npm/yarn、其他 pnpm 大版本或不能解析的生命周期参数会停止执行，请改用上述 `pnpm exec` 入口。不要删掉 `package.json` 中 dev 脚本末尾的 `#`。

## 默认 API 地址

未传 `--env` 或使用 `--env prod` 时可使用以下内置地址；`--env dev` 需要先配置该环境的服务地址：

| 服务 | 默认地址 |
| --- | --- |
| 客服 API | `https://customer-servhub-api.betteryeah.com` |
| Dashboard 只读 API | 客服 API 地址追加 `/observability` |
| AI API | `https://ai-api.betteryeah.com` |
| Customer Agent | `https://customer-agent.bantouyan.com` |
| 任务中心（learning-center） | `https://learning-center.betteryeah.com`；`--env dev` 内置 `https://dev-learning-center.betteryeah.com`，无需配置 |
| Langfuse | `http://192.168.40.10:3000` |

> AI API 同时服务于登录认证（`auth login` / `auth whoami`），不再区分独立的 "认证 API" 字段。

如需覆盖，可通过 `cs-cli config set` 修改：

```bash
cs-cli config set --cs-api https://your-api.example.com
```

Dashboard 命令会自动请求该地址下的 `/observability/v1/dashboard/**`。

### API 地址优先级

未传 `--env` 时（等同线上，见下方「同时登录 dev 与线上」），每个 API 地址的读取顺序为 `环境变量 > config.json > 内置默认值`。环境变量为 `undefined` 或空字符串 `""` 都视为未设置，继续向下 fallback。显式环境只读对应环境的配置；prod 可使用内置地址，dev 缺配置会报错，避免请求落到生产。

| 环境变量 | 覆盖字段 | 默认值 |
| --- | --- | --- |
| `CS_CS_API_URL` | `customerServiceApiUrl` | `https://customer-servhub-api.betteryeah.com` |
| `CS_AI_API_URL` | `aiApiUrl`（同时服务 auth 调用） | `https://ai-api.betteryeah.com` |
| `CS_CUSTOMER_AGENT_URL` | `customerAgentApiUrl` | `https://customer-agent.bantouyan.com` |
| `CS_CS_ADMIN_URL` | `csAdminUrl` | `https://customer-service-admin.betteryeah.com` |
| `CS_LEARNING_CENTER_URL` | `learningCenterUrl` | `https://learning-center.betteryeah.com` |

## 快速开始

```bash
# 登录
cs-cli auth login --phone 13800000000 --password yourpassword

# 查看当前用户
cs-cli auth whoami

# 设置默认工作空间
cs-cli config set-workspace <workspace_id>

# 列出 Agent
cs-cli agent list
```

## 同时登录 dev 与线上

全局 `--env dev|prod` 选择本次命令的 API 地址、登录态和默认工作空间，放在一级命令前。**不传 `--env` 就是线上**，与 `--env prod` 共用同一份配置和登录态；只有 dev 必须写 `--env dev`。每个环境登录一次即可交替执行，无需反复改配置：

```bash
# 一次性配置 dev；替换为部署提供的真实地址
cs-cli --env dev config set --cs-api <dev客服API地址> --ai-api <dev登录及AI地址>
# 使用 Customer Agent / cs-admin 命令时再配置相应服务
cs-cli --env dev config set --agent-api <devCustomerAgent地址> --admin-api <dev后台地址>

cs-cli --env dev auth login --phone 13800000000 --password yourpassword
cs-cli --env prod auth login --phone 13800000000 --password yourpassword
cs-cli --env dev config set-workspace <dev工作空间ID> --global
cs-cli --env prod config set-workspace <线上工作空间ID> --global

cs-cli --env prod agent list
cs-cli --env dev agent list
cs-cli --env dev config get
cs-cli --env prod auth whoami
# 只退出 dev，prod 登录态仍可使用
cs-cli --env dev auth logout
```

-   全局配置和凭据分别存入 `~/.cs-cli/environments/dev/` 与 `~/.cs-cli/environments/prod/`。`CS_CLI_HOME` 只替换根目录；两个环境仍位于其 `environments/` 子目录。凭据文件权限为 `0600`。
-   登出、过期和 401 只清理发起请求的环境；未登录时提示带同一 `--env` 的登录命令。Dashboard 地址仍从该环境的客服 API 派生。
-   未传 `--env` 按整个目录二选一，不拼两处的文件：`environments/prod/` 目录存在（用过 `--env prod` 就有）就整体用 prod；没有 prod 目录、根目录有旧单环境文件时整体用根目录（与升级前一致，不用重登）；两处都没有时用 prod。读写、401 / 过期清理都在选中的那个目录里；清掉 prod 凭据后仍是 prod（提示登录），不会落回根目录。有了 prod 目录后根目录旧 `config.json` 里的自定义地址、默认工作空间不再生效，要在 prod 里重新设置。
-   未传 `--env` 的 `auth logout` 两处都清，根目录旧登录态一并清掉。显式 `--env prod` 只用 prod 目录、不看根目录，dev 永远不借用根目录或 prod。
-   通用 `CS_AUTH`、`CS_WORKSPACE_ID`、`CS_AGENT_ID`、`CS_CS_API_URL`、`CS_AI_API_URL`、`CS_CUSTOMER_AGENT_URL`、`CS_CS_ADMIN_URL`、`CS_LEARNING_CENTER_URL` 没有环境归属，非空时与显式 `--env` 混用会报错，且不发请求。sandbox / CI 继续不传 `--env` 使用原注入方式；多环境调用先清除这些变量并配置各自环境。
-   `config get` 返回当前 `environment`（未传时为 prod）、实际使用的配置目录 `configDir` 及全局、本地配置。

Langfuse 通过 `debug trace --langfuse-env dev|prod` 单独选择；默认跟随全局 API 环境，未选 API 环境时默认 prod。旧的 `debug trace <id> --env dev` 仍只选择 Langfuse，API 环境保持原配置；若要同时选择 API 环境，必须把全局 `--env` 放在一级命令前。旧参数与 `--langfuse-env` 值冲突时拒绝执行。

```bash
cs-cli --env dev debug trace <trace_id>
cs-cli --env prod debug trace <trace_id> --langfuse-env dev
```

## 全局选项

| 选项 | 说明 | 默认值 |
| --- | --- | --- |
| `--table` | 以表格形式输出（默认 JSON） | `false` |
| `--env <dev|prod>` | 本次 API 环境，独立配置与登录态 | `prod`（线上） |
| `--workspace <id>` | 覆盖默认工作空间 ID | 配置文件中的值 |
| `--request-timeout <ms>` | 单个 HTTP 请求超时（毫秒）；`--timeout` 是相关子命令的轮询窗口 | `60000` |

## 本地配置 (`.cs-cli.json`)

支持在项目目录下放置 `.cs-cli.json` 文件，为不同项目配置不同的工作空间。

### 工作空间优先级

| 优先级 | 来源 | 说明 |
| --- | --- | --- |
| 0 | `CS_WORKSPACE_ID` 环境变量 | env lock，最高优先级，override 硬失败（见下方"环境变量锁定"） |
| 1 | `--workspace <id>` 参数 | 命令行临时覆盖 |
| 2 | `.cs-cli.json` (本地配置) | 当前目录或祖先目录 |
| 3 | `~/.cs-cli/environments/<env>/config.json` | 全局默认配置（未传 `--env` 见上方「同时登录 dev 与线上」的目录规则，可能是 prod 或根目录旧文件） |

### 使用方式

```bash
# 在项目目录下初始化本地配置
cs-cli config init

# 设置工作空间（自动写入本地 .cs-cli.json）
cs-cli config set-workspace <workspace_id>

# 强制写入全局配置
cs-cli config set-workspace <workspace_id> --global
```

CLI 从当前目录向上查找 `.cs-cli.json`，找到第一个即使用其中的 `workspaceId`。

显式 `--env` 只读取同一文件的 `environments.dev` 或 `environments.prod`；缺该项则使用该环境的全局默认工作空间，不借用旧顶层 `workspaceId` 或另一环境。本地与全局默认值在查询参数及 HTTP workspace header 中使用同一优先级。`config set-workspace` 写入所选环境，保留另一环境和旧字段。未传 `--env` 仍读写旧顶层字段，与升级前一致。

`.cs-cli.json` 格式：

```json
{
  "workspaceId": "ws-abc123",
  "workspaceName": "某客户项目"
}
```

同一目录保存两环境的格式：

```json
{
  "environments": {
    "dev": { "workspaceId": "ws-dev", "workspaceName": "开发环境" },
    "prod": { "workspaceId": "ws-prod", "workspaceName": "线上环境" }
  }
}
```

### `set-workspace` 写入规则

-   当前目录或祖先目录存在 `.cs-cli.json` → 写入本地配置
-   不存在 `.cs-cli.json` → 写入全局配置
-   使用 `--global` → 强制写入全局配置

每次写入后会提示写入的目标路径。

## 环境变量锁定（sandbox / CI）

当 sandbox、CI 等非交互环境预先注入以下环境变量时，cs-cli 会透明地把 env 作为凭据与上下文来源，并拒绝任何运行时 override（`--workspace` / `--agent` / `auth login` 等），避免下游脚本在上下文被静默切换时产生未定义行为。

| 环境变量 | 作用 |
| --- | --- |
| `CS_AUTH` | 登录凭据（等同 access token）。存在即视为"已锁定" |
| `CS_WORKSPACE_ID` | 锁定工作空间 ID |
| `CS_AGENT_ID` | 锁定默认 agent ID |

处理逻辑：

1.  **env 优先**：只要环境变量有值，cs-cli 就从 env 读取，忽略本地 `credentials.json` / `.cs-cli.json` / 全局配置。
2.  **override 硬失败**：锁定后如果再通过 `--workspace` / `--agent` / `auth login` / `auth logout` / `config set-workspace` 传入不一致的值，cs-cli 立刻报错退出，不会静默覆盖。
3.  **幂等传值**：override 与 env 值完全一致时允许通过（便于脚本显式断言）。

使用示例：

```bash
export CS_AUTH="<your-access-token>"
export CS_WORKSPACE_ID="ws-abc123"
export CS_AGENT_ID="agent-xyz"

cs-cli agent list     # 直接运行，无需 auth login
cs-cli --workspace ws-other agent list   # ❌ 报错：CS_WORKSPACE_ID 已被锁定
```

要解除锁定，清空对应环境变量即可（`unset CS_AUTH CS_WORKSPACE_ID CS_AGENT_ID`）。

## 命令权限

客户与内部人员继续使用各自账号执行 `auth login`，共用登录体系。CLI 仅维护下表的 27 条内部受限命令，按当前账号的服务端 `role_code` 检查权限；未命中清单的命令不新增 CLI 角色校验，沿用原有登录与工作空间权限。

| CLI 权限 | 允许的角色 | 命令范围 |
| --- | --- | --- |
| 普通 | 不增加平台角色要求 | 商品、SA、FAQ、知识、会话、调试、看板等业务命令；包括 `product feedback get/save/accept/restore/revoke/mode/disable-image/enable-image` |
| 内部普通 | `DEVELOPER`、`ADMIN`、`SYSTEM_ADMIN` | `channel-metric query/backfill`、`monitor fields`、`monitor tickets list/delete`、全部 `ops-record` 与 `special-project` 子命令、普通 key 的 `system-config get` |
| 内部高级 | `ADMIN`、`SYSTEM_ADMIN` | `agent fork` |
| 系统管理员 | `SYSTEM_ADMIN` | `agent engine get/set`、`device list`、`token-usage summary`、`monitor agents/workspaces/statistics` 与 `monitor snapshots hourly/daily/daily-range` |

`system-config get` 的以下四个 key 要求 `SYSTEM_ADMIN`：`installer_package_api_keys`、`sop_main_engine_config`、`sop_main_engine_request_config`、`sop_main_engine_flow_service_config`；其他 key 要求内部普通权限。`SHOP_MANAGER`、`ASSISTANT` 及未知角色不能执行内部命令。平台角色通过 CLI 检查后，服务端仍会执行原有业务和资源权限校验。

执行内部命令前，CLI 使用当前环境的登录凭据请求客服 API `GET /v1/user/profile`，不把角色写入凭据文件，也不跨进程缓存。角色无法确认或请求失败时，不执行目标业务命令；仅注入 `CS_AUTH` 而未设置 workspace 时，也能查询角色。`auth`、`config`、帮助和普通命令不增加此请求，`auth whoami` 仍返回原用户中心身份信息。

仅受限命令的 `--help` 增加权限标签。角色不足时输出 JSON 错误并以退出码 `1` 结束；未登录或 Token 失效为 `2`，网络或服务器错误为 `3`。本限制作用于本版 CLI；服务端接口与旧版 CLI 的行为未作调整。

本版移除了测试集与节点模板命令组，相关子命令不再注册；Sol 的 `debug replay` / `debug overlay` 流程保留。

## 命令速查表

### 认证 (`auth`)

| 命令 | 说明 |
| --- | --- |
| `auth login --phone <手机号> --password <密码>` | 登录 |
| `auth logout` | 退出登录 |
| `auth whoami` | 查看当前登录用户 |

### 配置 (`config`)

| 命令 | 说明 |
| --- | --- |
| `config set [--cs-api <url>] [--ai-api <url>] [--agent-api <url>] [--admin-api <url>] [--learning-center <url>]` | 设置当前环境的 API 地址 |
| `config get` | 查看当前配置（含全局和本地） |
| `config init` | 在当前目录初始化本地配置 (`.cs-cli.json`) |
| `config set-workspace <workspace_id> [--global]` | 设置默认工作空间 |

### 工作空间 (`workspace`)

| 命令 | 说明 |
| --- | --- |
| `workspace list` | 列出所有工作空间 |
| `workspace points-consumes-daily [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>]` | 按天查询工作空间积分消耗（默认最近 30 天） |

`workspace points-consumes-daily` 需要可用的工作空间上下文（`--workspace`、本地 `.cs-cli.json` 或全局 `defaultWorkspaceId`）。

```bash
# 查询最近 30 天（默认）
cs-cli workspace points-consumes-daily

# 指定日期范围（含 start/end）
cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
```

### Agent 管理 (`agent`)

| 命令 | 说明 |
| --- | --- |
| `agent list [--keyword <名称>] [--page N] [--page-size N] [--fields <路径,...>] [--raw]` | 列出 Agent 列表；`--keyword` 按名称模糊搜索；`--fields` 裁每一条记录；`--raw` 不遮蔽凭据 |
| `agent get <config_id> [--fields <路径,...>] [--raw]` | 获取 Agent 详情；`--fields` 只输出指定字段；`--raw` 不遮蔽凭据 |
| \`agent update <config\_id> --data <json | @file>\` |
| `agent fork --source-customer-config-id <id> --target-workspace-id <id> [选项]` | 复制 Agent；CLI 要求内部高级权限（ADMIN 或 SYSTEM\_ADMIN）；同空间仍校验当前用户权限，跨空间服务端仍仅允许 SYSTEM\_ADMIN |
| `agent engine get <config_id>` | 查看 Agent 独立层引擎版本（顶层 + 设备定向；`null` 按 classic 跑），需超级管理员（SYSTEM\_ADMIN） |
| `agent engine set <config_id> --to v2|v3|v4 [--device <设备ID>]` / `--device <设备ID> --clear` | 切换引擎版本（写线上）；不带 `--device` 为整体切换并清掉设备定向；返回切换前版本 |

`agent list` 每条都带完整 `customer_config`，单页可达数十万 token；只要 id 和名字时用 `agent list --keyword <店名> --fields config_id,config_name`。`--fields` 逗号分隔，点号取嵌套（如 `customer_config.xxx`），路径途经数组时作用到每个元素，不存在的路径直接省略，输出保持原嵌套结构；`list` 作用于 `.data.items` 每一条，分页字段保留；返回体没有 `items` 数组时往 stderr 打警告并对整个返回体裁剪。不带 `--fields` 时输出除凭据遮蔽外与之前相同。

`agent get` / `agent list` 默认按键名遮蔽凭据字段，值替换为 `[REDACTED len=N]`。规则比 `debug trace` 窄，只看键名、不扫正文：整键 `token` / `session` / `authorization`（仅字符串值）/ `ak` / `sk` / `dsn` / `database_url`，或键名（去掉 `_`、`-` 并转小写后）以 `secret`、`password`、`passwd`、`access_token`、`refresh_token`、`api_token`、`auth_token`、`security_token`、`bearer_token`、`session_token`、`id_token`、`session_key`、`api_key`、`app_key`、`access_key`、`secret_key`、`private_key`、`cookie(s)`、`credential(s)` 结尾；命中的数字值也遮（平台 appkey 常是纯数字），布尔、`null`、空串和空对象 / 数组不遮，值是 JSON 字符串时按同一规则处理其中的键。`max_token`、`keep_session`、`need_authorization`、`cookie_rule`、`password_hint` 这类键以及 prompt 正文里的 `Cookie: 曲奇` 不会被遮。`agent update --data` 里出现 `[REDACTED len=N]` 时会在调用接口前拒绝，所以带遮蔽占位的内容不能回传，只传要改的字段。确实需要真实值时加 `--raw` 关闭遮蔽：**输出含明文凭据**，不要贴进对话、日志或工单。

`agent fork` 必须显式指定来源 Agent 配置 ID 和目标工作空间 ID。`--copy-product-data`、`--copy-activity-data` 默认开启，可分别用 `--no-copy-product-data` / `--no-copy-activity-data` 关闭。`--clone-tools` 会复制来源 Agent 当前已绑定的全部工具（包括系统工具和自定义工作流工具），默认不复制；多数场景建议开启。`--copy-authorized-shop-id` 让克隆的工具沿用来源工具的店铺授权 `authorized_shop_id`，默认清空；仅与 `--clone-tools` 同用生效，只在用户明确要求全量复制工具时开启。`--shop-id` 可显式指定目标店铺 ID。

`agent engine` 读写的是 Agent 独立层的 `version`（v2=classic / v3=Luna / v4=Sol），这是 sop 收到的 `extra.version` 的唯一来源；判断引擎不要用 `/effective` 的合并结果。店铺还要在 `sop_main_engine_config` 的 internal 路由里（`system-config get sop_main_engine_config`），版本才会生效。

`agent engine get/set`、`device list` 和 `token-usage summary` 要求超级管理员（SYSTEM\_ADMIN）账号。`system-config get` 按 key 区分权限，见下表；角色不足时应由具备相应权限的账号执行。

### 系统配置 / 设备（`system-config` / `device`）

| 命令 | 说明 |
| --- | --- |
| `system-config get <key>` | 读一条全局 system\_config；普通 key 要求内部普通权限，四个敏感 key 要求 SYSTEM\_ADMIN（见「命令权限」） |
| `device list [--workspace-id <id>] [--channel <渠道>] [--equipment-id <id>] [--page N] [--page-size N]` | 需 SYSTEM\_ADMIN；分页列出设备，返回的 `version` 是客户端版本，不是引擎版本 |

### 场景动作 (`sa`)

| 命令 | 说明 |
| --- | --- |
| `sa list --agent <id> [--intent <意图>] [--first-label <标签>] [--second-label <标签>]` | 列出场景动作 |
| `sa search --agent <id> --keyword <关键词>` | 搜索场景动作 |
| `sa create --agent <id> --first-label <一级标签> --second-label <二级标签> --situation <场景> --action <动作> [--transfer] [--intent <意图>]` | 创建场景动作 |
| `sa update --agent <id> --id <SA_ID> --first-label <一级标签> --second-label <二级标签> --situation <场景> --action <动作> [--transfer]` | 更新场景动作 |
| `sa delete --agent <id> --id <SA_ID> --first-label <一级标签> --second-label <二级标签>` | 删除场景动作 |
| `sa versions --agent <id> --id <SA_ID>` | 查看修改记录 |

`sa create/update` 的场景必须从 `--situation <文本>` 与 `--situation-file <路径>` 中选一个，动作必须从 `--action <文本>` 与 `--action-file <路径>` 中选一个；两字段均必传，可混用文本与文件。文本参数含字面 `\n` 且没有真实 CR/LF 时会在调用 API 前拒绝，不会自动替换；有意写入字面 `\n` 请放入文件。`--situation/--action @file` 仍是普通文本，不读取文件。`sa update` 仍为全量更新，标签及转人工等字段需按备份回填。

```bash
# 本包目录，示例使用已配置的 dev；仅在确认范围与备份后执行
pnpm exec tsx src/bin.ts --env dev --workspace <workspace_id> sa update \
  --agent <agent_id> --id <SA_ID> --first-label <一级标签> --second-label <二级标签> \
  --situation-file ./situation.md --action-file ./action.md
```

### 工具 (`tool`)

| 命令 | 说明 |
| --- | --- |
| `tool list --agent <id>` | 列出该 Agent 配置已绑定的工具（custom + system） |
| `tool create --agent <id> --name <名称> --key <标识> --source-id <flow_id> [--description <描述>] [--source-type <类型>] [--invocation-mode <模式>] [--input-vars <json>]` | 创建绑定工作流（flow）的自定义工具 |
| `tool update <tool_id> [--name <名称>] [--key <标识>] [--description <描述>] [--source-id <flow_id>] [--invocation-mode <模式>] [--input-vars <json>]` | 更新工具（仅透传实际提供的字段） |
| `tool delete <tool_id>` | 删除（软删除）指定工具 |
| `tool workflow list [--keyword <关键词>]` | 搜索可绑定的工作流（flow），取 `source_id` |
| `tool workflow detail <flow_id>` | 查看工作流入参，辅助填 `--input-vars` |
| `tool workflow create --name <名称> --code <code|@file> [--description <描述>] [--input-vars <json|@file>] [--no-publish] [--code-node-name <名称>] [--version-desc <描述>]` | 创建 START→CODE→END 骨架工作流，CODE 节点承载工具代码（仅 JavaScript），默认自动发布 |
| `tool workflow update <flow_id> --code <code|@file> [--no-publish] [--version-desc <描述>]` | 只更新工作流唯一 CODE 节点的代码（其它节点原样保留），默认自动发布 |
| `tool workflow run <flow_id> [--input <json|@file>]` | 运行已发布的工作流（HTTP 同步执行），返回 `task_id` / `status` / `run_result` |
| `tool workflow delete <flow_id> --yes` | 硬删除工作流（不可恢复）；缺 `--yes` 拒绝执行 |
| `tool workflow describe` | 输出 CODE 节点（JavaScript）使用文档：入口/变量注入/返回值/内置包/SDK，依据官方文档整理 |

`tool` 命令组包装 customer-servhub-api 现有 `/v1/tool` 接口，管理 Agent 的自定义/覆盖工具；CLI 不改变服务端工具表或 sop-engine 的工具执行逻辑。`--invocation-mode` 取值 `must_invoke` / `on_demand`（默认）/ `on_demand_async`，非法值在发请求前本地报错；`tool create --input-vars` 接受 JSON 字符串，形如 `{"order_id":{"description":"订单号","type":"string","required":true}}`，对应工作流入参描述；`workspace_id` 由当前工作空间自动回填。

`tool workflow create/update/run/delete` 直连 BetterYeah 平台（`ai-api`）程序化管理工作流本体：`create` 生成固定 START→CODE→END 三节点骨架并把代码写进 CODE 节点、默认发布，返回 `flow_id` 供 `tool create --source-id` 绑定；`update` 只暴露 CODE 节点代码的修改；`run` 是 HTTP 同步执行（自动经 `GET /v1/workspaces/getKey/{ws}` 用登录态换应用态 Access-Key 再调 `execute_flow`，替代平台画布的 wss 通道），`--input` 的键对应 START `formConfig` 的 `variableName`（默认 `{}`）；`delete` 是硬删除、必须显式 `--yes`。create/update 发布时可用 `--version-desc` 附版本描述（平台版本历史里可见，便于区分 CLI 改动；`--no-publish` 时忽略）。CODE 节点只支持 JavaScript（CommonJS `require`，不支持 ES Module）。`--code` / `--input-vars` 的值以 `@` 开头时按文件路径读取（如 `@./tool.js`）。注意 `tool workflow create --input-vars` 覆盖的是 START 节点的 `formConfig` 入参声明，与 `tool create --input-vars`（工具记录的 `input_var_descriptions`）是不同字段。`tool workflow describe` 输出依据[官方 JavaScript&Python 节点文档](https://ai-docs.betteryeah.com/Flow/%E8%8A%82%E7%82%B9%E4%BB%8B%E7%BB%8D%E4%B8%8E%E9%85%8D%E7%BD%AE/JavaScript&Python%E8%8A%82%E7%82%B9.html)整理的 CODE 节点使用文档（入口 `main()`、变量注入、返回值约定、内置包、BetterYeah SDK），AI 编写工具代码前应先读。

### 商品管理 (`product`)

| 命令 | 说明 |
| --- | --- |
| `product list --agent <id> [--keyword <关键词>] [--status <状态>] [--tag <标签>]` | 列出商品 |
| `product get --agent <id> --product-id <商品ID>` | 获取商品详情（自动获取 db\_id） |
| `product update --agent <id> --product-id <商品ID> --update <json|@file>` | 更新商品信息 |
| `product shelf --agent <id> --status <上架|下架|预售> (--ids <id,id> | --ids-file <path>) [--concurrency <n>] [--yes]` | 批量设置商品状态（上架 / 下架 / 预售，写「商品状态」列，写后回读校验） |
| `product update-sku --agent <id> (--sku <SKU名称> | --sku-id <id>) --update <json|@file>` | 更新 SKU 信息（`--sku-id` 优先于 `--sku`） |
| `product add --agent <id> --data <json|@file> [--source <type>] [--no-identify]` | 直接传入结构化商品数据（JSON 数组）异步学习（manual / identify 默认开启） |
| `product learn --agent <id> --url <URL...> [--source <type>] [--no-identify]` | 通过商品 URL 异步学习（manual / identify 默认开启） |
| `product identify-all --agent <id>` | 一键增强学习当前 Agent 下所有待增强商品 |
| `product sync-taobao --agent <id> [--skip-hash-check] [--sync-type <full|incremental>]` | 触发淘宝店铺商品同步 |
| `product sync-taobao-item --agent <id> --product-id <商品ID>` | 触发单个淘宝商品同步 |
| `product related-knowledge list --agent <id> --product-id <商品ID>` | 列出商品详情绑定的 QA / 扩展知识库文件 |
| `product related-knowledge add --agent <id> --product-id <商品ID> --type <qa|common> (--file <文件名> | --file-id <id> --partition-id <id>)` | 给商品追加关联知识库文件 |
| `product related-knowledge remove --agent <id> --product-id <商品ID> --type <qa|common> --file-id <id>` | 移除商品绑定的指定知识库文件 |
| `product related-knowledge set --agent <id> --product-id <商品ID> --bindings <json|@file>` | 覆盖商品绑定的全部知识库文件 |
| `product supplement list --agent <id> --product-id <商品ID>` | 列出商品补充知识条目（含每条的 question / answer / images / videos） |
| `product supplement add --agent <id> --product-id <商品ID> --question <Q> --answer <A> [--image <path|url>...] [--video <path|url>...] [--dry-run] [--backup <path>]` | 新增一条补充知识（Q/A + 可选图片/视频）；本地媒体自动上传公开 OSS，URL 直写；read-modify-write 保留旧条目 |
| `product supplement append-media --agent <id> --product-id <商品ID> --index <n> [--image ...] [--video ...] [--dry-run] [--backup <path>]` | 向指定下标条目追加图片/视频，其它条目原样保留、去重合并 |

`product update` 支持更新的字段包括：`卖点`、`补充知识`、`tag`、`参数`、`轮播图识别结果`、`商品详情页识别结果` 等。`--update` 接受 JSON 字符串或 `@文件路径`。

`product add` 与 `product learn` 走同一个上传接口（`/v1/knowledge/products/upload`），区别在于载荷：`add` 传结构化商品数据 `product_items`（已抓好的商品详情 JSON 数组），`learn` 传 `product_urls`（让后端去抓）。`--data` 接受 JSON 字符串或 `@文件路径`；传单个对象会自动包成数组，传 JSON 基本类型（数字 / 字符串等）会直接报错；`--source` 默认 `manual`，`--no-identify` 关闭自动识别。

`product learn` 提交商品详情页 URL 进入异步学习队列（`/v1/knowledge/products/upload`），返回 `flow_id` 和 `knowledge_ids`。`--url` 可多值，用空格分隔；`--source` 默认 `manual`；传 `--no-identify` 可关闭自动识别。

`product identify-all` 对接 `POST /v1/knowledge/products/identify/all`，会为当前 Agent 一键提交所有“已可用但尚未增强”的商品进入增强学习队列。它不是全量重学；当没有待增强商品时，服务端会返回“暂无需要识图的商品”。

`product sync-taobao` / `product sync-taobao-item` 调用 customer-servhub-api 的授权店铺同步接口，要求 `--agent` 已唯一绑定一个淘宝授权店铺。前者支持 `--sync-type full|incremental`，默认 `full`；后者按 `product_id` 补同步单个淘宝商品。

注意：这两个命令仅对已开通“高级工具”的店铺 Agent 生效。未开通高级工具时，即使 `--agent` 已绑定淘宝授权店铺，请求也可能在服务端失败。

`product related-knowledge` 管理商品详情的“关联知识库”文件级绑定。`--type qa` 对应 FAQ 知识库，`--type common` 对应扩展知识库。`add` / `remove` 会先读取当前绑定，再通过服务端全量替换接口写回；重复添加或移除不存在的文件保持幂等。`--file <文件名>` 会按当前 Agent 的知识库文件列表做精确匹配；文件名不存在或不唯一时，改用 `--file-id` + `--partition-id` 显式模式。

`product supplement` 维护商品「补充知识」的带图/视频问答（结构为 `{ question, answer, images: [URL], videos: [URL] }`），与前端商品详情页的补充知识上传一致。写入采用 **read-modify-write**：先 `product get` 读取并解析当前补充知识，本地合并后再调既有 `/v1/knowledge/product/update`，绝不用新增项覆盖旧条目。`--image` / `--video` 可重复传多个，值可以是**本地文件**（先经 `/v1/upload_sts` 取 STS 凭证上传公开 OSS，key 为 `/ai/knowledge/supplement/${timestamp}_${filename}`）或**已有 http(s) URL**（直写不重复上传）。图片限 `image/*` ≤ 10MB、视频限 `video/*` ≤ 200MB，超限或类型不符在发起上传请求前直接失败。`--dry-run` 只输出将写入的最终 JSON、不落库、不上传；`--backup <path>` 在写前把当前补充知识快照到本地文件。`append-media` 仅向 `--index` 指定的条目追加媒体，去重合并，其它条目原样保留。

单文件上传也可单独用 `media upload`（见下方「媒体上传」小节），拿到公网 URL 后再作为 `--image` / `--video` 的值传入。

```bash
# 更新卖点
cs-cli product update --agent <id> --product-id <商品ID> --update '{"卖点":"新卖点内容"}'

# 更新标签
cs-cli product update --agent <id> --product-id <商品ID> --update '{"tag":"[\"春季新品\",\"热卖\"]"}'

# 添加商品补充知识
cs-cli product update --agent <id> --product-id <商品ID> --update '{"补充知识":"[{\"question\":\"问题\",\"answer\":\"回答\"}]"}'

# 更新 SKU 补充知识（按 SKU 名称定位）
cs-cli product update-sku --agent <id> --sku "颜色分类:粉红" --update '{"补充知识":"说明内容"}'

# 更新 SKU 补充知识（按 sku_id 定位，推荐）
cs-cli product update-sku --agent <id> --sku-id 6072595054179 --update '{"补充知识":"说明内容"}'

# 给商品绑定一个 FAQ 文件
cs-cli product related-knowledge add --agent <id> --product-id <商品ID> --type qa --file "常见问题.xlsx"

# 显式 ID 模式绑定扩展知识文件
cs-cli product related-knowledge add --agent <id> --product-id <商品ID> --type common --file-id 622474 --partition-id 12345 --file-name "售后规则.md"

# 查看商品当前补充知识条目
cs-cli product supplement list --agent <id> --product-id <商品ID>

# 新增一条带图问答（本地图片自动上传，已有 URL 直写）
cs-cli product supplement add --agent <id> --product-id <商品ID> \
  --question "尺码怎么选？" --answer "参考尺码表，拍下留言身高体重" \
  --image ./size-chart.png --image https://xxx.com/a.png

# 先 dry-run 预览最终写入 JSON，不落库
cs-cli product supplement add --agent <id> --product-id <商品ID> \
  --question "Q" --answer "A" --video ./demo.mp4 --dry-run

# 向第 0 条补充知识追加媒体，写前备份
cs-cli product supplement append-media --agent <id> --product-id <商品ID> \
  --index 0 --image ./extra.png --backup ./supplement-backup.json
```

### 媒体上传 (`media`)

| 命令 | 说明 |
| --- | --- |
| `media upload --file <path> [--type <image|video>] [--path-prefix <prefix>] [--dry-run]` | 上传单个本地图片/视频到公开 OSS，返回可访问 URL |

`media upload` 把单个本地文件上传到公开 OSS（`resource_bucket`），返回公网 URL，供 `product supplement` 的 `--image` / `--video` 复用。凭证走 `GET /v1/upload_sts`（返回 AES-CBC 加密，CLI 内解密），上传 key 为 `<path-prefix>/<timestamp>_<filename>`，`--path-prefix` 默认 `/ai/knowledge/supplement`。`--type` 缺省时按扩展名推断；图片限 `image/*` ≤ 10MB、视频限 `video/*` ≤ 200MB，超限/类型不符在发起上传前直接拒绝。传入的 `--file` 如果本身是 http(s) URL 会被拒绝（无需上传）。`--dry-run` 仅校验并输出将要上传的信息、不实际上传。

```bash
# 上传本地图片，拿到公网 URL
cs-cli media upload --file ./banner.png --type image

# 自定义路径前缀 + 预览（不实际上传）
cs-cli media upload --file ./demo.mp4 --path-prefix /ai/knowledge/supplement --dry-run
```

### 活动管理 (`activity`)

活动 V2（Issue #34）增删改查 + per-tenant V2 总开关。服务端 `customer-servhub-api /v1/activity*`；契约详情参见 [`docs/devkit/specs/2026-05-28-activity-v2-api-reference.md`](https://npmcn.com/package/docs/devkit/specs/2026-05-28-activity-v2-api-reference.md)。

| 命令 | 说明 |
| --- | --- |
| `activity list --agent <id> [--page N] [--page-size N] [--search <text>]` | 列出活动（返回 `total` / `v2_activity_enabled` / `items[]`） |
| `activity by-product --agent <id> --product-id <id>` | 按 `product_id` 反查当前生效活动（仅 enabled、product\_scope='all' 必入） |
| `activity create --data <json|@file>` | 创建活动（payload 见 `IActivityWritePayload`） |
| `activity update <activity_id> --data <json|@file>` | 整体更新活动（products 整组替换，非增量） |
| `activity set-enabled <activity_id> --enabled <true|false>` | 切换单条活动启用状态（仅写 enabled） |
| `activity delete <activity_id>` | 软删活动 + 级联软删关联 |
| `activity v2-enabled --agent <id> --enabled <true|false>` | per-tenant 启停 V2 链路 |

-   `--agent <id>` 对应服务端 `customer_agent_config_id`（与 `sa` / `product` 命令同口径）。
-   `activity create` / `update` payload 必填：`customer_agent_config_id`、`activity_name`、`effective_type`、`activity_rule`；`effective_type=time_range` 要求 `start_time` + `end_time`；`product_scope='specific'`（缺省）要求 `products ≥ 1`。
-   业务失败（参数非法 / 跨 workspace / 不存在）服务端返 HTTP 200 + `code:400`，CLI 转为 `exit 1` 并打印 `INVALID_PAYLOAD: …`。

```bash
# 列出活动
cs-cli activity list --agent <cfg_id> --search 返现

# 按商品反查
cs-cli activity by-product --agent <cfg_id> --product-id 914494989518

# 创建活动（从文件读 payload）
cs-cli activity create --data @activity.json

# 整体更新
cs-cli activity update 6 --data @activity.json

# 列表行 toggle 启用
cs-cli activity set-enabled 6 --enabled false

# 软删活动
cs-cli activity delete 6

# 为某 agent 启用 V2 链路
cs-cli activity v2-enabled --agent <cfg_id> --enabled true
```

### 工单管理 (`issue`)

| 命令 | 说明 |
| --- | --- |
| `issue list [--agent <id>] [--status <状态>] [--priority <优先级>] [--start <日期>] [--end <日期>]` | 列出工单 |
| `issue get <issue_id>` | 获取工单详情 |
| `issue create --title <标题> --content <内容> [--priority <优先级>] [--tags <标签>]` | 单条创建工单 |
| `issue create --file <path> | --stdin [--dry-run] [--concurrency <n>] [--yes]` | **批量**创建工单（JSON 协议见下） |
| \`issue update <issue\_id> --data <json | @file>\` |
| \`issue update --ids <a,b,c> | --ids-file --data <json | @file> \[--dry-run\] \[--concurrency \] \[--yes\]\` |
| `issue update-owner <issue_id> --owner <user_id|花名>` | 单条改派负责人（花名自动解析） |
| `issue update-owner --ids <a,b,c> | --ids-file <path> --owner <user_id|花名> [--dry-run] [--yes]` | **批量**改派负责人（走 cs-admin BFF，原子改派 + 自动 Feishu/DingTalk 通知，单批 ≤ 200） |
| `issue stats [--start <日期>] [--end <日期>]` | 各工作空间 Issue 统计（open/closed/total） |
| `issue comment list <issue_id>` | 列出工单评论 |
| `issue comment add <issue_id> --content <内容>` | 添加评论 |

#### 批量写约定（`update` / `update-owner` / `create --file`）

-   `--ids "a,b,c"` 与 `--ids-file path.txt`（一行一个 id，`#` 注释、空行忽略）互斥；与单条 `<issue_id>` 互斥。
-   `--owner` 同时接受 `user_id`（≤ 7 位纯数字）和工程师花名。花名经 cs-admin `/api/account-roles` 解析（KA + SMB 合并去重，5 分钟内存 cache），同名 / 模糊歧义会报错列 candidates。`--owner` 拒绝 11 位手机号（Issue #49，老 `--owner-phone` 0.5.0 起硬删）。
-   `--dry-run` 在所有批量子命令上都不会调写接口；`create --file --dry-run` 会真跑 `user_name → conversation_id` 反查，以便提前发现 `NOT_FOUND` / `AMBIGUOUS`。
-   N ≥ 20 且 stdout 是 TTY 时弹二次确认（输入 `yes`）；非 TTY 必须显式 `--yes`，否则拒收。
-   末尾报告统一 schema：`{version:"1", total, success_count, failed_count, skipped_count, aborted, success_ids[], failed[{id,code,msg}], skipped[{id,reason}]}`。退出码：`0` 全成功 / `1` 全失败 / `2` 部分失败 / `130` SIGINT。
-   `update-owner --ids` 走 cs-admin BFF `POST /api/workbench/issues/batch-owner` 单次调用（原子），返回 `{updated, skipped:[{issueId, reason}], notify[]}`，与 `update` 的 runBatch 报告 schema 不同（保留 BFF 原样）。
-   双后端单 token：servhub Bearer JWT 同时通过 cs-admin BFF 鉴权（实测验证），无需额外登录。

#### `issue create --file` JSON 协议

文件解析交给上游（LLM/Agent/脚本）整理成下面这份结构化 JSON，CLI 只做校验 + 反查 + 拼装 + 批量创建。

```jsonc
{
  "version": "1",
  "workspace_id": "ws_xxx",          // 顶层，对所有 issue 生效；缺省时落到默认 workspace
  "issues": [
    {
      "title": "客户反馈：A 问题答非所问",   // 必填
      "priority": "high",              // 必填，critical|high|medium|low
      "expected": "应该回答 X",          // 必填，CLI 把它写进 content 的 ## Expected 段
      "description": "上下文描述",       // 可选，拼在 expected 前面
      "conversation_id": "conv_xxx",   // 与 user_name 二选一
      "user_name": "张三",              // 仅给昵称时，CLI 调 /v1/chat/conversations?user_name= 反查
      "tags": ["badcase"],             // 可选
      "source_type": "manual_review",  // 可选
      "source_data": {"reviewer": "..."} // 可选
    }
  ]
}
```

CLI 把 `description` + `expected` 拼成 `content`：

```
{description}（如有）

## Expected
{expected}
```

`user_name` 反查规则（5 分钟内存 cache，key = `${workspaceId}|${userName}`）：

-   1 条命中 → 用其 `conversation_id`
-   0 条 → 该 issue 标记 `NOT_FOUND`，整批拒收（防止半批进创建）
-   多条 → 该 issue 标记 `AMBIGUOUS`，提示改传显式 `conversation_id`

`priority` 不在枚举内、`title`/`expected` 缺失等 → 客户端直接拒收（不打任何接口）。

### 聊天记录 (`chat-history`)

查询客户导入的聊天记录，用于 SA（场景动作）清洗，非 Agent 自身的对话记录。

| 命令 | 说明 |
| --- | --- |
| `chat-history --agent <id> [--page N] [--page-size N]` | 查询客户导入的聊天记录 |

### FAQ 知识库 (`faq`)

| 命令 | 说明 |
| --- | --- |
| `faq list --agent <id>` | 列出 FAQ 文件 |
| `faq content --agent <id> --file <文件名> [--keyword <词>] [--page-size <n>] [--next-group-id <id>]` | 列出文件内答案组（含 `answer_group_id` / 问题 / 答案） |
| `faq add --agent <id> --file <文件名> --questions <问题1,问题2> --answers <答案=xxx>` | 添加 FAQ 内容 |
| `faq update --agent <id> --file <文件名> --group-id <id> [--questions <...>] [--answers <...>] [--delete-chunks <id1,id2>]` | 更新已有 FAQ 答案组（`--questions` / `--answers` / `--delete-chunks` 至少一个） |
| `faq delete --agent <id> --file <文件名> --group-id <id>` | 删除 FAQ 答案组 |
| `faq bind-products list --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>)` | 列出 FAQ 文件绑定的商品 |
| `faq bind-products add --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 给 FAQ 文件追加绑定商品 |
| `faq bind-products remove --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 从 FAQ 文件移除绑定商品 |
| `faq bind-products set --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 覆盖 FAQ 文件绑定的全部商品 |

`faq content` 走 `GET /mebsuta/api/v1/dataset/{knowledge_id}/faq_contents`，列出文件内的答案组及其 `answer_group_id`，是 `faq update` / `faq delete` 拿 `--group-id` 的来源（`faq list` 只给文件，文件内的 group-id 要靠这条命令查）。支持 `--keyword` 搜索、`--page-size` 分页、`--next-group-id` 游标续翻。

`faq update` 按 `answer_group_id` 定位目标组，先读回这组的现值，只覆盖显式传入的字段，再 POST 同一个 `faq_contents/save` 接口完成字段替换、问题替换与 chunk 级删除。`--questions` 整组替换问法，没列出的原问法会被删掉。读不到现值、合并后没有问题或没有非空答案时拒绝保存。`faq delete` 走 `DELETE /faq_contents/{group_id}` 整组删除。

`faq bind-products` 固定写入 `knowledge_type=qa` 的商品绑定关系，适合从 FAQ 文件视角批量维护关联商品。`--file` 为精确文件名匹配；文件名不唯一时请传 `--file-id` 和 `--partition-id`。

### 扩展知识库 (`knowledge`)

Agent 关联的通用规则知识库（`suffix_type=_common`），与 FAQ / 商品知识并列的第三类知识源，支持段落（chunk）级 CRUD，统一走 `/mebsuta/api/v1/dataset/{id}/contents/perform-operations`。

| 命令 | 说明 |
| --- | --- |
| `knowledge list --agent <id> [--page N] [--page-size N]` | 列出 Agent 关联的扩展知识库文件 |
| `knowledge content list --knowledge-id <id> [--page-size N] [--direction up|down]` | 列出段落（chunk） |
| `knowledge content add --knowledge-id <id> --content <text|@file> [--keyword <k...>] [--after <chunk_id>]` | 新增段落 |
| `knowledge content update --knowledge-id <id> --chunk-id <id> [--content <text|@file>] [--keyword <k...>] [--enable | --disable]` | 更新段落（四个变更字段至少一个；未指定 `--enable` / `--disable` 时自动保留当前启停状态，避免后端 enable 必填校验失败） |
| `knowledge content delete --knowledge-id <id> --chunk-id <id>` | 删除段落 |
| `knowledge bind-products list --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>)` | 列出扩展知识文件绑定的商品 |
| `knowledge bind-products add --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 给扩展知识文件追加绑定商品 |
| `knowledge bind-products remove --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 从扩展知识文件移除绑定商品 |
| `knowledge bind-products set --agent <id> (--file <文件名> | --file-id <id> --partition-id <id>) --product-id <商品ID...>` | 覆盖扩展知识文件绑定的全部商品 |

`knowledge content add` / `update` 的 `--content` 支持 `@路径` 前缀从文件读取**纯文本**（不是 JSON）。`--keyword` 为 variadic 选项，空格分隔多个关键词。

`knowledge bind-products` 固定写入 `knowledge_type=common` 的商品绑定关系，适合从扩展知识文件视角批量维护关联商品。`add` / `remove` 会 read-modify-replace，重复添加或移除不存在的商品保持幂等。

### 会话管理 (`conversation`)

| 命令 | 说明 |
| --- | --- |
| `conversation list [--agent <id>] [--equipment-id <设备ID>] [--user <用户名>] [--start <日期>] [--end <日期>] [--query-content <文本>]` | 搜索会话；`--start/--end` 按会话 `updated_at` 过滤（按时段算转人工率用 `transfer-human`）；`--query-content` 按消息内容搜索，必须同时给起止时间 |
| \`conversation records <conversation\_id> \[--page-size N\] \[--direction prev | next\] \[--current-id | --all\]\` |
| `conversation context-search (--query <文本> | --user <用户名>) [--limit <1~500>] [--start <时间>] [--end <时间>]` | 通过上下文内容或买家用户名模糊搜索对话记录（返回匹配消息详情，时间范围最多 3 天） |
| `conversation find-order <订单号> --agent <id> --start <时间> --end <时间>` | 按订单号反查会话与 record\_id；超过 3 天自动按 3 天分段顺序查询，总跨度最多 31 天 |
| `conversation transfer-human --agent <customer_agent_config_id> --start <时间> --end <时间> [--only-abnormal]` | 查询转人工会话及对应转人工消息记录（时间范围最多 7 天，自动 cursor 翻页；`--only-abnormal` 仅保留异常会话） |

### Agent 调试 (`debug`)

| 命令 | 说明 |
| --- | --- |
| `debug ask --agent <id> (--text <消息> | --messages <json|@file>) [--user <用户名>] [--url <图片URL>] [--conversation <会话ID>] [--mock <json|@file>] [--expect <file>] [--data-overlay <json|@file> | --overlay <dir>] [--timeout <秒>] [--poll-interval <毫秒>]` | 向 Agent 发送消息并等待回复 |
| `debug reproduce <record_id> [--agent <id>] [--mock <json|@file>] [--expect <file>] [--data-overlay <json|@file> | --overlay <dir>] [--replace-last <文本>] [--image <URL>] [--timeout <秒>] [--poll-interval <毫秒>] [--dry-run]` | 根据 record\_id 复现 Agent 回复（自动获取上下文）；`--replace-last` / `--image` 换掉最后一条买家消息 |
| `debug data snapshot --agent <id> [--product-id <商品ID...>]` | 生成可传给 `--data-overlay @file` 的 debug 数据覆盖 JSON |
| `debug record <record_id...> [--out <file.ndjson>]` | 获取记录的调试信息（flow\_info）；多个 id 时最多 3 路并发，`--out` 按 NDJSON 每行一条写盘（附 `engine` / `flow_engine`），stdout 只给汇总；返回中包含 `trace_id`；Sol 引擎有存档时另有 `sol_trace_id` 与 `sol_trace`（服务端 `view=diagnosis` 的原样结果） |
| `debug trace <trace_id> [--langfuse-env <dev|prod>]` | 根据 Langfuse Trace ID 获取完整 Trace 详情（经所选环境的服务端代理）；Sol 轮次另附 `sol_trace`（服务端 `view=diagnosis`） |
| `debug trace (<st-…> | --record <record_id>) [--view summary|prompt|lean|full|otel] [--call <node>] [--out <file>]` | Sol 引擎单轮完整 prompt 存档（服务端展开并逐字节校验）；默认摘要 |
| `debug trace --date <YYYY-MM-DD> [--agent <id>] [--out <dir> [--label <l>]... [--node <n>]... [--limit <n>]]` | 某天 Sol 存档按 workspace + agent 合并好的调用文件：列条数 / 下载并筛选 |
| `debug replay export (--trace <id...> | --record <id...> | --conversation <id> [--until <N>]) --out <case.json>` | 导出同一会话的 Sol 轮次上下文及商品快照；`--conversation` 按会话自动收集 Sol trace |
| `debug replay append --case <case.json> --after <N> --text <text...> [--clock <iso>] --out <new-case.json>` | 离线：保留前 N 轮，接一轮自定义买家消息（只能 rollout 跑） |
| `debug replay open --case <case.json> [--mode rollout|original] [--products <snapshot.json>] [--mocks <mocks.json>] [--products-patch <patch.json>] --out <run.json>` | 初始化本地运行文件，默认沿用 dev 状态逐轮执行；可带工具 mock 与商品局部补丁 |
| `debug replay next --run <run.json>` | 执行下一轮并保存完整结果与状态 |
| `debug replay run --run <run.json> [--until <N>]` | 从保存的进度继续，逐轮落盘 |
| `debug replay fork --run <run.json> --at <N> [--products <snapshot.json>] [--mocks <mocks.json>] [--products-patch <patch.json>] --out <new-run.json>` | 保留前 N 轮与对应状态，在新文件继续验证 |
| `debug replay inspect --run <run.json> [--turn <index>] [--view <view> | --path <pointer>] [--offset <N>] [--limit <N>] [--out <file>]` | 只读本地摘要/卡片/工具/原文，按需取证 |
| `debug replay tools --run <run.json...> [--tool <name...>] [--turn <index>] [--limit <N>]` | 只读：多个运行文件的逐工具调用次数、调用率、数据来源与缺录制 |
| `debug replay compare --baseline <run.json> --candidate <run-b.json> [--expect <expected.json>] [--ab] [--out <file>]` | 同输入字段差异与显式断言（含 `called` / `not_called` 工具调用断言），区分执行和验证；两边 case.overlay hash 不同时默认拒绝，`--ab` 声明为配置 A/B |
| `debug mock init --case <case.json> --out <mocks.json>` | 从回放包已录制的工具调用生成 `tool-mock/v1` 骨架（`fill`；回放 / sol-local 用） |
| `debug overlay init (--case <case.json> | --agent <id>) --out <dir> [--base current|incident] [--turn <N>] [--product <id...>]` | 只读：把回放基线拉成可编辑的覆盖目录（Sol 含 `tools.json`；`--product` 预填 `products_patch.json`） |
| `debug overlay build [--case <case.json>] --overlay <dir> [--out <file>] [--format sol|v1|online]` | 离线：把覆盖目录合并进 case 的 `overlay` 段（含 `tools`），并写 `products_patch` / `tool_mocks`（classic 输出 `debug_data_overlay` v1）；`--format online` 输出线上模拟用的 `{version, servhub, sol}`，不需要 `--case`，`--out` 可省（结果直接打印） |

`debug ask` 会自动创建调试会话、发送消息、轮询等待 Agent 回复，默认最大等待 30 秒，默认轮询间隔 2000ms。`--text` 与 `--messages` 二选一：`--text` 发送单条消息，`--url` 只在 `--text` 模式下作为附图 URL；`--messages` 传入完整消息列表（JSON 或 @文件）重放多轮对话，既接受 JSON 数组，也接受 `conversation records` 原始 `{ "msg_list": [...] }` 对象。`--conversation` 可复用已有 debug 会话继续对话；`--mock` 可传工具结果 mock；`--expect` 对本轮线上回复做断言；`--data-overlay` 可传 `debug_data_overlay` JSON 或 @文件；`--overlay <dir>` 直接用覆盖目录现场生成线上模拟覆盖（Sol，与 `--data-overlay` 互斥）。

`debug reproduce` 根据一条回复记录自动复现 Agent 回复：通过 recordId 获取关联会话和 Agent 配置，拉取完整会话记录并截取目标 record 之前的上下文，使用原始用户名创建新 debug 会话并发送。默认最大等待 60 秒，默认轮询间隔 2000ms。`--agent` 可指定另一个 Agent 复现同一段上下文（A/B 对比）；`--mock`、`--expect` 和 `--data-overlay`（或 `--overlay <dir>`，见「Sol 线上模拟」）会在复现发送时透传；`--replace-last` / `--image` 换掉最后一条买家消息；`--dry-run` 仅输出截断后的 `msg_list`（含替换结果）、mock 和 overlay，不发送。

`debug data snapshot` 生成 `debug_data_overlay` v1 JSON，适合把当前 Agent 的 SOP / 商品 / 活动等数据固定成一次 debug 覆盖输入，再通过 `debug ask --data-overlay @file` 或 `debug reproduce --data-overlay @file` 复跑。`--agent <id>` 必填；`--product-id <商品ID...>` 可重复传多个商品 ID，只导出指定商品，不传则分页导出全量商品。命令输出写到 stdout，通常重定向到文件保存。

```bash
# 只导出指定商品相关 overlay
cs-cli debug data snapshot --agent <id> --product-id 586812046546 > overlay.json

# 使用 overlay 跑一次 ask
cs-cli debug ask --agent <id> --text "https://item.taobao.com/item.htm?id=586812046546 这个商品名称是啥" --data-overlay @overlay.json
```

**Sol 逐轮回放**：先用生产环境的 CLI 配置运行 `replay export`，按轮次顺序传入同一会话的 1 到 50 个 `sol_trace_id` 或回复 `record_id`。导出依赖存档中的轮前状态、客户资产和商品数据；缺少可执行上下文的历史存档会由服务端明确报错。导出包顶层附 `export_report`（`sol-replay-export-report/v1`），逐轮标出回放会不会停：`blocked` 表示严格与 lenient 都会停，`warn` 表示严格会停、lenient 可继续（含跳过轮）；有 warn/blocked 时 `export` 在 stderr 打印逐轮摘要（轮号、状态、规则码），stdout 与退出码不变。授权了推荐工具却没捕获 `recommend_route` 的轮由导出自动补（先借同包邻轮，借不到补空路由），来源记入 `export_report.recommend_route_filled`，不用再手工补。`replay open` 只生成本地 `run.json`，内嵌完整 case；消息卡片、未知字段与原时间戳均原样保存。

执行 `next` / `run` 时使用当前 CLI 环境的客服 API 和登录态；服务端仅在 dev 开放执行。预先按上面的多环境步骤分别配置并登录，生产导出使用 `--env prod`，执行使用 `--env dev`。`rollout`（默认）沿用 dev 每轮产生的状态，适合改了商品选择逻辑后验证后续轮次；`original` 每轮恢复对应线上状态，适合独立对照。

```bash
# dev 与 prod 已分别配置 API、工作空间并登录
cs-cli --env prod debug replay export --record <第一轮回复ID> <第二轮回复ID> --out case.json
cs-cli debug replay open --case case.json --out run.json
cs-cli --env dev --request-timeout 180000 debug replay next --run run.json
cs-cli --env dev debug replay run --run run.json --until 2

# 保留完成的第一轮，从其 dev 状态继续新分支；--at 0 从初始上下文开始
cs-cli debug replay fork --run run.json --at 1 --out run-b.json
cs-cli --env dev debug replay run --run run-b.json

# 新逻辑会查询 B：补入包含 B 的原始商品数据，再从同一个 checkpoint 分叉
cs-cli debug replay fork --run run.json --at 1 --products products-B.json --out run-products-B.json
cs-cli --env dev debug replay next --run run-products-B.json
```

`open` / `fork --products snapshot.json` 把原始商品数据对象固定到 `case.products`，显式作为所有后续轮次的数据源；商品 ID/SKU 查询和卡片生成仍由真实 Sol 代码执行。其余消息、客户身份、前 N 轮与 checkpoint 保持原样。快照应覆盖改动后会查到的商品及所需 SKU、库存、摘要；缺范围由服务端报错，不自动补空结果。不传 `--products` 则使用各轮自己的录制数据，库存等时点数据不会跨轮自动合并。

只想改几件商品（改状态 / 补库存或总结 / 补活动与关联知识）时，不必重写整份快照：在 `case.json` 顶层加 `products_patch`（`products-patch/v1`）即可，按商品 id 合并行、整块替换 SKU / 库存 / 总结、覆盖短链，并按商品集合补 enrichment；格式见 `docs/devkit/specs/2026-10-27-590-sol-工具mock.md`「商品局部补丁」。sol-local 对应 `shop.products_patch` 与 `run --products-patch`。不想手改 case 时，`open` / `fork --products-patch patch.json` 把补丁写进 `case.products_patch`，`--mocks mocks.json`（`tool-mock/v1`）写进 `case.tool_mocks`；两者都先按契约校验，非法时不建运行文件。`fork` 只替换给出的那一项，其余沿用原运行文件。

**复现第 N 轮之后的回复**：只知道会话 id 时，`export --conversation <id> [--until N]` 按时间顺序读 AI 回复的 record 诊断、收集去重后的 `sol_trace_id` 再导出（人工回复没有 Sol trace，自动跳过；超过 50 轮必须 `--until`）。要看「第 N 轮之后买家换一种说法 / 追问一句会怎么回」，用 `append` 离线造一轮：

```bash
cs-cli --env prod debug replay export --conversation <conversation_id> --until 3 --out case.json
cs-cli debug replay append --case case.json --after 3 --text "那 1.8 米的有现货吗" --out case.next.json
cs-cli debug replay open --case case.next.json --out run-next.json                # 只能 rollout
cs-cli --env dev --request-timeout 180000 debug replay run --run run-next.json
```

-   合成轮复制第 N 轮的 request / context（资产、识图、商品快照不变），在对话末尾接上买家文本消息（多个 `--text` 即连发），时钟默认第 N 轮 + 60 秒（`--clock` 可指定），去掉只对原轮有效的 QA 预取录制与线上结果，`tool_records` 清空，并标 `synthetic`。第 N 轮之后的原有轮次丢弃。
-   rollout 会丢掉第 N 轮线上 AI 回复、接在 dev 回复之后；第 N 轮到合成消息之间线上真实发生的人工 / 平台消息不会带上。
-   合成轮没有工具录制：模型调了非商品工具会 422 缺录制，用 `--mocks` 给这些工具配 `fill` 规则；商品工具走商品快照。含合成轮的 case 拒绝 `--mode original`。

`--until N` 表示执行到完成 N 轮，包含第 N 轮；不传则跑完。每轮用独立 `request_id` 关联同步终态，并原子保存结果及 checkpoint。`failed` / `skipped` 也作为该轮结果保存；HTTP 错误或返回关联不符时保留上一个已完成 checkpoint，再次执行即可从该处恢复。`export` / `open` / `fork` 不覆盖已有文件。运行文件使用同目录 `.lock` 防止并发推进；进程异常退出后，确认没有回放进程才可删除残留锁。文件包含客户上下文，默认权限为 `0600`。

**供 AI 执行的输出与取证**：`next/run` 默认仅返回进度、最后一轮 `response_status`、回复预览、焦点商品及卡片/工具/告警数量。`execution_status=completed` 表示命令完成，`verification_status=not_evaluated` 表示尚未判断业务预期；即使本轮 Sol 是 `failed/skipped`，仍完整存档并在摘要中明确显示。完整 response、trace、checkpoint 保存在 `saved_to` 指向的 run 文件。

`inspect` 只读本地文件，不需要联网或登录。`--turn` 使用从 0 开始的索引；不传则分页读取已完成轮次摘要。视图为 `summary|response|cards|tools|trace|checkpoint|request|raw`；cards 读取 checkpoint 中的 tool\_result 事实载荷（包含沿用的历史事实），没有记录时提供 run\_record 卡片摘要；原始业务载荷可取 `/response/llm_payload`。tools 来自 trace 的工具执行。`--path` 从本轮完整结果按 JSON Pointer 取字段（覆盖 view），支持 `/`→`~1`、`~`→`~0` 转义。未执行轮次可查 request。数组默认 10 项，`--offset/--limit` 分页，返回 total/next\_offset；超过 12,000 bytes 的视图仅给标记截断的预览，可缩小路径或 `--out` 完整保存所选视图/数组页，未知字段不改写。

```bash
cs-cli debug replay inspect --run run.json
cs-cli debug replay inspect --run run.json --turn 0 --view cards --limit 5
cs-cli debug replay inspect --run run.json --turn 0 --view tools
cs-cli debug replay inspect --run run.json --turn 0 --path /checkpoint/head/focus_skus
cs-cli debug replay inspect --run run.json --turn 0 --view trace --out turn-0-trace.json
cs-cli debug replay compare --baseline run.json --candidate run-b.json --out comparison.json
cs-cli debug replay compare --baseline run.json --candidate run-b.json --expect expected.json --out verified.json
```

`compare` 要求 source、mode、逐轮 trace ID 和 request 相同，允许显式更换数据。比较共同已执行前缀的状态、回复、转人工、标签、事实卡及载荷、工具有效参数/结果、焦点商品/订单/SKU/场景；忽略卡片抓取时刻与工具耗时。`case_equal/data_equal` 和两份运行摘要用于标明输入包及数据是否一致；完成长度不同则 `comparison_status=incomplete`。默认只报差异位置和数量，完整 before/after 与断言证据可用 `--out` 保存，或通过 inspect 按位置读取。无 `--expect` 一律 `not_evaluated`，不把“无差异”判作“正确”。

预期文件只做显式逐轮字段断言（默认相等比较），不执行代码或自动语义评分；每条可加 `op`：`equals`（默认）/ `not_equals` / `contains` / `not_contains` / `length` / `startswith` / `in`，字段缺失一律判失败：

```json
{"schema":"sol-replay-expect/v1","assertions":[
  {"turn_index":0,"path":"/checkpoint/head/focus_product_ids","equals":["B"]},
  {"turn_index":1,"path":"/checkpoint/head/focus_product_ids","equals":["B"]}
]}
```

断言工具调用用 `op: "called"` / `"not_called"`，不写 `path`（或写 `/tool_calls`），`equals` 为 `{tool, input?, include_prefetch?}`：`tool` 接受配置名或 canonical 名（`faq_knowledge` = `fetch_qa`）；`input` 对模型给的入参做子集匹配；默认只认模型调用（取自 `trace.llm_calls` 输出的 `tool_calls`，不含 `read_block`）；引擎自己发起的执行——预取、选品轮按 `must_invoke` 补发的 `product_recommendation` 等——不算，`include_prefetch: true` 时任何执行都算。`debug ask` / `debug reproduce --expect` 同样可用，线上证据取 `run_record.tool_calls`，`input_source=prefetch` 的不算模型调用；补发的执行同样标 `input_source=model`，按本轮告警 `recommendation_forced` 认出来，不算模型调用（`debug replay tools` 里记 `forced`）。

```json
{"schema":"sol-replay-expect/v1","assertions":[
  {"turn_index":2,"op":"called","equals":{"tool":"order_query"}},
  {"turn_index":2,"op":"not_called","equals":{"tool":"faq_knowledge","input":{"query":"运费"}}}
]}
```

`debug replay tools --run run-1.json run-2.json ...` 汇总多个运行文件：每个工具的 `model_calls`、`turns_called`、`call_rate`（调用轮数 / 已执行轮数，同一 case 多跑几次即调用率）、`executions` 按数据来源（`recorded` 录制 / `mock` / `overlay` 覆盖知识词法检索 / `snapshot` 商品快照 / `prefetch` 预取）计数、`missing_recording`（缺录制）与 `failed`，`calls` 给逐轮明细（`--limit` 截断）。回放里预取的非商品工具也按录制记 `recorded`，所以引擎发起（预取、`must_invoke` 补发）的次数要用 `executions − model_calls` 估。

全部声明断言满足才是 `verification_status=passed`；不相等或字段不存在为 `failed`，所需轮次尚未执行为 `incomplete`。这里的通过仅覆盖声明的断言，可用于验证 A→B 以及下一轮继续 B；回复质量仍需业务标准。比较命令成功时外层 success=true；断言失败/未完成额外设置退出码 1。无预期的差异报告和断言通过退出码为 0。

缺数 HTTP/业务 422 会在 CLI `error` 中保留受控的 `reason/retryable/missing/next_action/truncated`，例如 `missing=[{"kind":"catalog","ids":["B"],"fields":["rows","covered_product_ids"]}]`。`snapshot_data_incomplete` 对应 `supply_snapshot`，输入损坏的 `replay_invalid_input` 对应 `repair_case`；两者均 `retryable=false`。AI 应按 fields 补齐原始快照再 open/fork，或修复 case，随后从保存的轮次继续；重复同一请求不会产生缺失数据。诊断只含字段名和受控实体标识，不含凭据、任意上游异常或完整工具参数；非法诊断退回固定失败信息，不更新失败轮的 checkpoint。

所有回放命令默认 JSON 的最终输出信封（包含路径等元数据）按 12,000 bytes 控制。超限时返回 `truncated=true`、`data.artifact` 与 `next_action=read_artifact`，完整输出转存为权限 `0600` 的本地临时证据文件；运行文件和显式 `--out` 仍保留完整数据。AI 应先读取 artifact；需长期保存时将其复制到任务目录。非法或缺失结构化诊断的 422 始终使用固定文案，不回显上游 message。

**回放配置覆盖**（#442）：在本地改 SA、FAQ、扩展知识、customer\_config、工具、商品后重跑，不写线上。

```bash
cs-cli --env prod debug replay export --record <record_id> --out case.json
cs-cli --env prod debug overlay init --case case.json --out ov/ --product <商品id>   # 只读；默认 --base current
# 编辑 ov/sa.json、ov/faq.json、ov/common_knowledge.json、ov/customer_config.json、ov/tools.json、ov/products_patch.json
cs-cli debug mock init --case case.json --out ov/tool_mocks.json        # 可选：新增工具或要替换工具返回值时
cs-cli debug overlay build --case case.json --overlay ov/ --out case.ov.json
cs-cli --env dev debug replay open --case case.ov.json --out run-ov.json && cs-cli --env dev debug replay run --run run-ov.json
cs-cli debug replay compare --baseline run.json --candidate run-ov.json --ab
```

-   `overlay init` 生成 `sa.json`（SA 全量）、`faq.json`（FAQ 当前全集）、`common_knowledge.json`（扩展知识当前全集）、`customer_config.json`（深合并补丁，初始 `{}`）和 `.base.json`（基线元信息）。不写 `request` 及其中凭据。
-   `--base current`：取同一 Agent 最近一轮 Sol 回复的资产，再用 `sa list`、`agent get` 的当前值修正，差异写进 `stale_fields`（只核对 SA 和 flow\_instruction 槽位；所选回复超过 24 小时会告警，其他配置可能已改过）；找不到 Sol record 时退回事故快照，`.base.json` 标 `fallback: "incident"` 并在 stderr 告警。`--base incident`：取 case 第一轮（或 `--turn N`）的资产。
-   FAQ 条目 id 写成 `<file_id>-<answer_group_id>`，知识条目写成 `<file_id>-<chunk_id>`。
-   `overlay build` 只写和基线不同的 SA，定位写 id（内容哈希），不写每轮排序编号 `ref`；`sa.json` 里 ref / id 重复会报错（复制条目当新增时去掉 ref 和 id）。`faq.json` / `common_knowledge.json` 相对 init 有改动（含新增、删除）时整集写入，回放里对应的检索改为本地词法检索（报告标 `lexical_approx`）；没改或删掉文件时不写，仍用录制结果。标准 QA 不覆盖，报告固定写 `qa_assets: "baseline"`。整份商品快照仍用 `replay open/fork --products`，改几件商品用下面的 `products_patch.json`。
-   工具（Sol）：`overlay init` 另写 `tools.json`——回放资产 `tool_entries` 去重后的工具行全量（同一 canonical 名有多行时自定义行优先），基线存进 `.base.json` 的 `tools_baseline`。可改 `description`、`input_var_descriptions`、`invocation_mode`（`must_invoke` / `on_demand` / `on_demand_async`）、`usage_guidance`、`selection_hint` 等字段，也可加一行新工具或删掉一行（或写 `"delete": true`）。`build` 按 canonical 名与基线 diff：改了的只写改动字段，删掉的写 `{tool, delete: true}`，新增整行写入 `overlay.tools`，引擎同时同步请求的 `tools[]`（授权集合）。字段从行里删掉不会清空（浅合并，要清空写空字符串）；Sol 自持工具（`build_apply_link`、`fetch_product_supplement`、改备注 / 改地址等）只读 `usage_guidance`；`usage_guidance` / `selection_hint` 回放能验证但 `tool update` 写不了；这些情况 build 在 stderr 提示。
-   新增工具在回放里没有录制，模型一调就 422 缺录制：用 `debug mock init` 生成 `ov/tool_mocks.json` 并给它加规则（`fill` / `override`），build 原样写进 `case.tool_mocks`；没配 mock 时 build 提示。
-   商品（Sol）：`overlay init --product <id...>` 按商品 id 从 case 商品快照预填 `products_patch.json`（`products-patch/v1`：目录行去掉 `商品id`，可改 参数 / 卖点 / 补充知识 / 标题等列；该商品全部 SKU 行；总结；库存），快照里没有的商品写空行并告警。`build` 对预填基线最小化：目录行只留改动列，SKU / 库存 / 总结整块比较，没改的商品不写；`enrichment`（按商品集合补活动 / 关联知识）与 `short_links` 原样保留；结果写进 `case.products_patch`，全没改则不写。
-   `--format v1` 不支持工具、商品补丁和工具 mock，三个文件存在时列进 `skipped`。
-   执行端 sop-engine 早于 `overlay.tools` 支持时会静默忽略工具覆盖（hash 仍然通过）；`next` / `run` 发现 `overlay_report.applied` 没有 `tools` 时在摘要 `overlay_warnings` 和 stderr 告警，这时工具改动没生效。
-   `hash` 与引擎同口径：去掉 `hash` 后键按码点排序、无空白分隔、非 ASCII 原样、UTF-8，取 sha256。
-   每轮结果带 `overlay_report`（applied / missing / retrieval）。同样的 case 可直接给 `sol-local run --case case.ov.json`。
-   classic 店：`overlay init --agent <id>`，`overlay build` 输出 `debug_data_overlay` v1，交给 `debug reproduce --data-overlay @file`。v1 的 FAQ / 知识只带相对基线改动或新增的条目；v1 表达不了的改动（含删除条目）列在 `skipped`。

**`--data-overlay` 生效检查**：`debug ask` / `debug reproduce` 带 `--data-overlay`（或 `--overlay <dir>`）时，发送前按目标 Agent 的顶层引擎（读不到时 reproduce 看原 record）拦截引擎不读的非空子树，退出码 2、不发送：`sop` 子树只有 classic 读（Sol 店会提示改用 `sol` 子树或 `--overlay`，Luna 的 SA / FAQ / 知识覆盖走 `debug replay`），`sol` 子树只有 Sol 读。发送后逐条读回 `debug_data_overlay` 回报并输出 `debug_data_overlay_report`（`applied` / `missing` / `unused` / `sol` 的 hash、base、检索口径）；`applied`（sop、servhub、sol）全空时退出码 3，引擎回报 `sol.invalid`（子树不合法，本轮按无覆盖执行）时打印原因并退出码 3。

**Sol 线上模拟**（#610）：在本地改 Sol 店的 SA、工具、标准 QA、FAQ、扩展知识、商品、开关、customer\_config、违禁词后，用线上真实服务跑一轮，不写线上配置。覆盖只随这一次 debug 请求生效。

```bash
cs-cli --env prod debug overlay init --agent <config_id> --out ov/      # 只读：拉当前 SA / FAQ / 扩展知识作基线（也可 --case 用回放基线）
# 编辑 ov/sa.json、ov/faq.json、ov/common_knowledge.json、ov/customer_config.json；可选 ov/switches.json、ov/qa_items.json、ov/forbidden_words.json、ov/tools.json、ov/products_patch.json
cs-cli --env prod debug overlay build --overlay ov/ --format online     # 可选：只看生成的 {version, servhub, sol}（加 --out 写文件）
cs-cli --env prod debug ask --agent <config_id> --text "问题" --overlay ov/
cs-cli --env prod debug reproduce <record_id> --replace-last "换个问法" --overlay ov/ [--image <url>] [--dry-run]
```

-   `--overlay <dir>` 等于先 `overlay build --format online` 再 `--data-overlay`；和 `--data-overlay` 互斥（同时传退出码 1）。目录相对 init 基线没有任何改动、文件格式错误时报错、不发送。
-   输出形状：`{"version":1,"servhub":{customer_config?,forbidden_words?},"sol":{version:1,base,hash,sa?,tools?,qa_items?,faq?,common_knowledge?,products_patch?,switches?}}`。没有 sol 内容时整个 `sol` 省略。`sol` 不含 customer\_config 与 assets\_replace；`sol.hash` 与 `--format sol` 同一套规范化 JSON sha256。
-   `sa` / `tools` / `products_patch` 与 `--format sol` 同一套 diff（只发改动；SA 定位写 id）。`faq` / `common_knowledge` 是补丁：只发改动、新增的条目；基线里有而目录里没有的条目（或写了 `"delete": true`）发 `{id, delete:true, match_*}`；改动条目带基线原文 `match_questions`（FAQ 原问法）/ `match_content`（知识原正文），引擎靠它在真实检索结果里定位。原文存在 `.base.json` 的 `knowledge_baseline`，旧版 `init` 生成的目录没有它：补丁照发但不带 `match_*`，`skipped` 里提示重新 `init`。
-   可选文件，缺了就不发对应项，格式错误明确报错：
    -   `switches.json`：`{"full_product_detail":bool,"order_promise_ship_time":bool,"image_rule_transfer":bool}`，只允许这三个布尔开关。
    -   `qa_items.json`：数组，条目 `{ref?|id?, questions?, answer_text?, delete?}`；带 `ref` / `id` 的是改或删，不带的是新增（必须同时给 `questions` 与 `answer_text`）。
    -   `forbidden_words.json`：字符串数组，整体替换线上违禁词表，进 `servhub.forbidden_words`。
-   `init --agent` 只读拉 SA / FAQ / 扩展知识基线（servhub 读口），不拉工具行也不预填商品：没有 case 快照时 `tools.json` / `products_patch.json` 不会生成，要改工具或商品就手写增量文件（工具行只写要改的字段，删除写 `"delete": true`；`build` 提示「没有工具基线，按增量原样发送」）。要拿到工具基线与商品预填，用 `init --case <case.json>`（回放资产快照）。
-   `sol.base=incident` 的覆盖在线上仍套在**当前**线上资产上，不是事故时刻的资产（条目对不上的会在 `missing` 里）；`ask` / `reproduce` 发送前打印一行提示。
-   带 overlay 或 `--mock` 时，输出里始终有本轮 `tool_calls`：`{model, prefetch, forced}` 三组，每项 `{tool, status, source, args}`，stderr 同步打印 `[tool-calls] model=N prefetch=N forced=N`。`prefetch` 是引擎预取（`input_source=prefetch` / `prefetch_mock`），`forced` 是选品轮 `must_invoke` 补发（按本轮告警 `recommendation_forced` 认出），两者都不算模型调用，口径与 `called` / `not_called` 断言一致。统计落库晚于回复，取不到 flow\_info 时 stderr 提示「tool\_calls 为空不代表没调用」。
-   `reproduce --replace-last <文本>` 把上下文里最后一条买家消息的文本换掉，`--image <url>` 换 / 加图片（可同用），其余上下文不变；`--dry-run` 展示替换后的 `msg_list` 与 `replaced_last`，不发送。上下文里没有买家消息时报错。
-   判读顺序：先看 `debug_data_overlay_report.records[].applied`（改动有没有生效）、`missing`（条目对不上）、`unused`（声明了但本轮没读到），再看 `tool_calls`（本轮实际调了什么）和回复。

**工具 mock（Sol）**：`debug ask --mock @mocks.json` / `debug reproduce --mock @mocks.json` 会把工具返回值替换成声明的数据（真实工具 handler、工具录制采集与 flow 点上报都不执行），模型仍自行决定是否、何时调用；买家的真实链路不带这个字段。模型调用本身仍会产生 token 成本。回放（`debug replay` / `sol-local`）与实时共用同一份 `tool-mock/v1` 契约：

```json
{
  "schema": "tool-mock/v1",
  "rules": [
    {"id": "order-signed", "tool": "order_query", "mode": "override", "call": 1,
     "input": {"order_id": "123456"},
     "response": {"status": "success", "result": [ ... ]}}
  ]
}
```

-   `mode=override` 压过回放录制；`mode=fill` 只补缺失（默认）。旧的 `{工具名: {status, result}}` 与 `[{tool_name, result}]` 仍可用，等价 `fill`。
-   `call` 是该工具本轮第几次调用（1-based），`input` 是入参子集（匹配有效输入：模型可见参数 + 被拆到网关侧通道的 `query` / `product_ids`）；多条规则按声明顺序取第一条。
-   命中与未命中都可见：结果带 `tool_mock_report{applied[{tool,call,rule,mode}], unused[{rule,tool,reason}]}`，`reason` 为 `tool_not_called` / `input_mismatch` / `recording_present`。实时请求里规则非法时不打断本轮，按无 mock 执行并在 `warnings` 记 `tool_mock_invalid`；回放 / sol-local 的规则非法则直接判用例非法（422 / 退出 2）。
-   从已有回放包生成可编辑骨架：`cs-cli debug mock init --case case.json --out mocks.json`（按轮次 / 调用序号展开，`response` 取自录制，改完即可用）。
-   批量：`sol-local bench run --tool-mocks mocks.json [--products-patch patch.json] --strict-mocks`，整集未命中的规则会让命令非零退出。

**线上回报与断言**：`debug ask` / `debug reproduce` 带 `--mock` 时，同步响应与返回 `data` 都带 `tool_mock_report`；报告同时写进回复的 `flow_infos`，之后用 `debug record <record_id>` 也能查到（OSS 不可用时 DB slim 版仍保留该字段）。规则非法时没有报告，单独返回 `tool_mock_warnings`（`code=tool_mock_invalid`），stderr 同时打印 applied / unused / invalid 摘要。

`--expect <file>` 复用 `sol-replay-expect/v1`，对本次线上回复做单轮断言；`turn_index` 只支持 `0`，其余 op / 字段缺失判失败的规则与 `debug replay compare --expect` 完全一致。断言路径相对线上证据对象：

| 路径 | 内容 |
| --- | --- |
| `/response/messages` | 本次调试会话里各条助手回复文本 |
| `/response/transfer_to_human` | 回复记录上的转人工标记（未取到时为 `null`） |
| `/response/transfer_decision` | Sol `run_record.transfer_decision`（有存档时） |
| `/tool_mock_report` | `{applied, unused}`；可断言 `/tool_mock_report/unused` 的 `length` 为 0 |
| `/warnings` | 引擎告警列表；`tool_mock_invalid` 也在里面 |
| `/cards` | Sol 本轮卡片（来自 `flow_infos`，没有时为 `[]`） |

```json
{"schema":"sol-replay-expect/v1","assertions":[
  {"turn_index":0,"path":"/response/messages","op":"contains","equals":"已签收"},
  {"turn_index":0,"path":"/response/transfer_to_human","equals":false},
  {"turn_index":0,"path":"/tool_mock_report/unused","op":"length","equals":0}
]}
```

断言失败或 `incomplete` 时设置退出码 `1`；全通过为 `0`。这里只验证声明的断言，回复质量仍按业务标准判断。

```bash
cs-cli debug mock init --case case.json --out mocks.json
cs-cli --env dev debug ask --agent <id> --text "帮我查下订单 123456" --mock @mocks.json
```

**Sol 完整 prompt 存档**（`debug trace` 的第二种 id）：Sol 引擎（路由 v4）每轮把每次 LLM 调用发给模型的原样 messages / tools 存到 OSS（30 天），`debug record` 返回的 `sol_trace_id`（`st-YYYYMMDD-<uuid>`）即存档 id。存档格式与各种视图都由服务端定义，CLI 只传参、写文件（设计见 `docs/devkit/specs/2026-09-23-sol-trace-完整prompt回溯与AI取数.md` §9.20）。只接受登录用户，且需有该存档所属 workspace 的权限。

-   单轮：`--view summary`（默认）给身份 / 结局 / `labels` / `decision` / 每次调用一行。`--view diagnosis` 是给客服助手的更短卡片（无身份 id、无工具入参，不超过 4KB）。要原文再取 `prompt`（`--call` 选，默认第一次 main）、`lean`、`full` 或 `otel`。`verified` 是服务端哈希校验结果。
-   按天：每天北京时间 02:30 合并前一天，**当天的请用单轮**（先在 Langfuse 按结局 tag 筛出轮次，再拿 `sol_trace_id`）。workspace 取全局 `--workspace` / 默认配置。不带 `--out` 只列各 agent 的轮数；`--out <dir>` 下载服务端展开好的调用文件到 `<dir>/<date>/<workspace>/<agent>/calls.jsonl`（一次调用一行，lean 形态，每行带轮次上下文、`labels`、`decision`）。`--label`（可重复，AND）按结局标签筛轮次，如 `transfer`、`repair`、`llm-error`、`status:failed`、`warn:<告警码>`；`--node` 只保留某类调用；`--limit` 限制轮数（跨 agent 累计）。

```bash
# 某条回复为什么这么回：record 里直接带 decision；要原文再看 prompt
cs-cli debug record <record_id>
cs-cli debug trace --record <record_id> --view prompt
cs-cli debug trace st-20260923-<uuid> --view prompt --call repair

# 批量：昨天某 agent 所有转人工轮次的主调用
cs-cli --workspace <workspace_id> debug trace --date 2026-09-23 --agent <agent_id> --out ./sol-trace --label transfer --node main
```

`debug trace`（v0.4.0 起，**行为变更**）不再由 CLI 直连 Langfuse REST API，改为请求 customer-servhub-api 的 `GET /v1/debug/langfuse/trace/{trace_id}?env=<env>`，走 CLI 标准 Bearer 鉴权，由服务端按 env 选择 Langfuse 凭据并代理拉取 Trace。Trace ID 从 `debug record` 返回的 `trace_id` 字段获取。

### 工作空间 (`workspace`)

| 命令 | 说明 |
| --- | --- |
| `workspace list` | 列出当前账号可访问的工作空间 |
| `workspace points-consumes [--page <n>] [--page-size <n>]` | 分页查询积分账户消耗流水，固定返回 `extra` 调试字段 |
| `workspace points-consumes-daily [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>]` | 按天查询积分消耗趋势 |

-   `debug trace --langfuse-env <dev|prod>`：指定 Langfuse 环境，默认跟随全局 API 环境；未传全局 `--env` 时默认 `prod`。非法值报错并以非 0 退出，不发请求。

```bash
# 默认 prod 环境
cs-cli debug trace <trace_id>

# 指定 dev 环境
cs-cli debug trace <trace_id> --langfuse-env dev
```

> **迁移（v0.4.0 BREAKING）**：`config set --langfuse-host / --langfuse-public-key / --langfuse-secret-key` 及 `CLIConfig.langfuseHost / langfusePublicKey / langfuseSecretKey` 已移除；环境变量 `LANGFUSE_BASE_URL / LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY` 在 CLI 侧不再被读取。`~/.cs-cli/config.json` 中相关字段可直接删除。Langfuse 连接信息（含 dev/prod 双环境凭据）现由 customer-servhub-api 服务端的 `system_config` 表 `langfuse_config` 行统一管理（不再读服务端环境变量 / 内置常量），CLI 侧无需也无法再配置。

### 修复记录 (`repair-record`)

| 命令 | 说明 |
| --- | --- |
| `repair-record list [--issue <工单ID>] [--agent <AgentID>] [--workspace <工作空间ID>] [--page N] [--page-size N]` | 列出修复记录 |
| `repair-record create --issue <工单ID> --agent <AgentID> --action <修复动作> [--agent-name <名称>] [--type <类型>] [--status <状态>] [--duration <毫秒>] [--result <JSON>] [--workspace <工作空间ID>]` | 创建修复记录 |
| `repair-record update <record_id> --data <json|@file>` | 更新修复记录 |

`--status` 可选值：`pending`、`success`、`failed`、`partial`。

`repair-record update` 的 `--data` 支持的字段：`repair_action`、`repair_result`（JSON 对象）、`repair_type`、`repair_status`、`duration_ms`。

#### `repair_result` 结构

`--result` 或 `--data` 中的 `repair_result` 字段应为结构化 JSON，用于记录 AI 修复的完整推理过程，支持未来 AI 自我进化。

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `failure_type` | string | 问题分类: `wrong_answer` / `missing_answer` / `hallucination` / `wrong_routing` / `slow_response` / `other` |
| `root_component` | string | 归因组件: `sa_policy` / `faq` / `product_knowledge` / `customer_config` / `prompt` / `multiple` / `unknown` |
| `input` | string | 触发问题的用户输入 |
| `expected` | string | 期望的正确回复 |
| `actual` | string | 实际的错误回复 |
| **`diagnosis_reasoning`** | string | **AI 的诊断推理过程**：排查了什么、怎么定位根因、排除了哪些可能性（最高价值字段） |
| `diagnosis_confidence` | string | 诊断置信度: `high` / `medium` / `low` |
| `hypotheses_considered` | array | 考虑过的假设列表，每项含 `hypothesis`、`evidence_for`、`evidence_against`、`verdict` |
| `fix_strategy` | string | 修复策略: `create_sa` / `update_sa` / `delete_sa` / `update_faq` / `add_faq` / `update_product` / `update_config` / `multi_step` |
| **`fix_reasoning`** | string | **AI 的修复决策推理**：为什么选这个方案、为什么不选其他方案、预期风险和收益 |
| `alternatives_rejected` | array | 被否决的方案列表，每项含 `approach`、`reason` |
| `changes` | array | 变更快照，每项含 `component`、`action`、`target_id`、`before`、`after` |
| `target_fixed` | boolean | 目标问题是否修复 |
| `regression_summary` | string | 回归验证结果摘要 |
| `retry_history` | array | 重试记录，每项含 `round`、`what_failed`、`lesson`、`adjustment` |
| `duration_ms` | number | 修复总耗时（毫秒） |
| `repair_status` | string | 最终状态: `success` / `failed` / `partial` |
| `resolved_date` | string | 修复完成时间（ISO 格式） |

**加粗的两个字段**（`diagnosis_reasoning` 和 `fix_reasoning`）是对 AI 自我进化价值最大的：没有它们只能做模式匹配，有了它们才能做推理。

### 运维操作记录 (`ops-record`)

本组全部命令要求内部普通权限（`DEVELOPER`、`ADMIN` 或 `SYSTEM_ADMIN`）。

| 命令 | 说明 |
| --- | --- |
| `ops-record list [--workspace <工作空间ID>] [--agent <AgentID>] [--page N] [--page-size N]` | 列出运维操作记录 |
| `ops-record get <record_id>` | 获取运维操作记录详情 |
| `ops-record create --workspace <工作空间ID> --agent <AgentID> [--workspace-name <名称>] [--agent-name <名称>] [--path <文件路径>] [--remark <备注>]` | 创建运维操作记录 |
| `ops-record update <record_id> --data <json|@file>` | 更新运维操作记录 |
| `ops-record delete <record_id>` | 删除运维操作记录（软删除） |

`ops-record update` 的 `--data` 支持的字段：`workspace_name`、`agent_name`、`path`、`remark`。

创建记录时 `operator_id` 和 `operator_name` 自动从当前登录用户获取，无需手动指定。

### 商品变更事件消费 (`change-consumer`)

客服运维助手订阅并消费商品变更事件（**pull 模式**）。后端走 `/v1/change-consumers/*`，CLI 标准 Bearer 鉴权。默认 `consumer_key=ops_agent_assistant`。

标准工作流：`sub create` 创建订阅 → `delivery list` 轮询待消费项 → `delivery claim` 认领 → 处理 → `delivery complete` 回写。

| 命令 | 说明 |
| --- | --- |
| `change-consumer sub create --workspace-id <id> [--agent-config <id>] [--events <list>] [--resource-type product] [--consumer-key <key>] [--consumer-name <名称>] [--disabled] [--data <json|@file>]` | 创建订阅（最简只需 `--workspace-id`；`--agent-config` 缺省表示该工作空间全部商品事件） |
| `change-consumer sub list [--workspace-id <id>] [--agent-config <id>] [--consumer-key <key>] [--enabled <true|false>] [--page N] [--page-size N]` | 查询订阅列表 |
| `change-consumer sub update <subscription_id> --data <json|@file>` | 更新订阅（`enabled` / `events` / `filters` / `consumer_name`） |
| `change-consumer delivery list [--workspace-id <id>] [--agent-config <id>] [--consumer-key <key>] [--statuses <list>] [--cursor-created-at <iso>] [--cursor-delivery-id <id>] [--limit N]` | 查询待消费项（不传 `--statuses` 时后端默认返回 pending/claimed/failed；`--cursor-*` 做增量拉取） |
| `change-consumer delivery claim <delivery_id> --claimed-by <id> [--consumer-key <key>] [--lease-seconds N]` | 认领待消费项（已被其他实例认领且未过期时认领失败） |
| `change-consumer delivery complete <delivery_id> --status <completed|ignored|failed> [--result-action <action>] [--result-note <note>] [--last-error <error>] [--consumer-key <key>] [--data <json|@file>]` | 回写处理结果 |

-   `sub create` 默认 `--events product.updated,product.deleted`、`--resource-type product`；`--disabled` 创建为停用态（默认启用）。
-   `delivery complete` 的 `--status` 仅接受 `completed` / `ignored` / `failed`，其它值直接报错退出；不要长时间停留在 `claimed` 不回写。
-   `--result-action` 建议值：`supplemental_knowledge` / `product_association` / `rerun_product_study` / `refresh_recommended_qa` / `ignore_change` / `manual_follow_up`。
-   各子命令 `--data <json|@file>` 为高级用法，提供时覆盖对应结构化选项。

```bash
# 1. 创建订阅（该工作空间全部商品 更新/删除 事件）
cs-cli change-consumer sub create --workspace-id <ws> --events product.updated,product.deleted

# 2. 轮询待消费项
cs-cli change-consumer delivery list --workspace-id <ws> --statuses pending

# 3. 认领
cs-cli change-consumer delivery claim <delivery_id> --claimed-by ops-assistant-1

# 4. 处理后回写
cs-cli change-consumer delivery complete <delivery_id> --status completed \
  --result-action supplemental_knowledge --result-note "已补充商品知识"
```

### 运营监控 (`monitor`)

直接调用 `/v1/operation` 底层接口，返回原始数据，由调用方决定如何聚合统计。`fields`、`tickets list/delete` 要求内部普通权限；其余六个数据查询命令要求 `SYSTEM_ADMIN`。

#### 字段说明

| 命令 | 说明 |
| --- | --- |
| `monitor fields [resource]` | 查看监控资源的指标字段说明；本地静态输出，仍要求内部普通权限 |

#### Agent 生命周期

| 命令 | 说明 |
| --- | --- |
| `monitor agents [--page N] [--page-size N]` | Agent 生命周期列表（含构建进度、会话统计等原始数据） |

#### 监控工单

| 命令 | 说明 |
| --- | --- |
| `monitor tickets list [--agent <id>] [--status <状态>] [--severity <级别>]` | 列出监控工单 |
| `monitor tickets delete <ticket_id>` | 删除监控工单 |

#### 监控快照

| 命令 | 说明 |
| --- | --- |
| `monitor snapshots hourly --agent <id> --start <ISO> --end <ISO>` | 查询小时快照 |
| `monitor snapshots daily --agent <id> --start <日期> --end <日期>` | 查询日快照 |
| `monitor snapshots daily-range --agent <id> --start <日期> --end <日期>` | 查询日快照范围（含趋势） |

#### Agent 运营统计

| 命令 | 说明 |
| --- | --- |
| `monitor statistics [--start <ISO> --end <ISO>] [--page N] [--page-size N]` | Agent 运营统计（会话量、转人工率、响应时间等） |

#### 运营工作空间

| 命令 | 说明 |
| --- | --- |
| `monitor workspaces [--has-agent] [--page N] [--page-size N]` | 运营工作空间列表（含合同、客户信息） |

### 模型 Token 成本（`token-usage`，需超级管理员（SYSTEM\_ADMIN））

成本以 customer-observability-api 账本为准；`cost_cny` 是刊例价，`payable_cost_cny` / `discount_rate` 是实付。

| 命令 | 说明 |
| --- | --- |
| `token-usage summary --start <时间> --end <时间> [--workspace-id <id>] [--agent <id>] [--engine sol|luna|classic] [--include-test]` | 时间窗内的成本汇总 |

### 数据看板 (`dashboard`)

转人工率 / 接待量 / AI 回复量等运营指标。**数据 T+1**（`--end` 不能晚于昨天，否则后端返回 400）；`--start` / `--end` 为**闭区间**。渠道枚举：`qianniu`（千牛/淘宝）/ `jingdong` / `pinduoduo` / `douyin`。CLI 只产结构化数据，运营报告由上层（如 LLM）按场景加工。

| 命令 | 说明 |
| --- | --- |
| `dashboard summary [--config-id <id>] [--start <d>] [--end <d>] [--channel <c>]` | 全局或单店 KPI 汇总 + 环比（不传 `--config-id` 为全店铺聚合） |
| `dashboard trend [--config-id <id>] [--start <d>] [--end <d>] [--granularity day/week/month] [--channel <c>]` | 趋势（每日/周/月接待客户数、转人工数、转人工率），`--granularity` 默认 `day` |
| `dashboard shops [--start <d>] [--end <d>] [--channel <c>]` | 全部店铺统计列表（默认按转人工客户数降序），用于横向对比 |
| `dashboard export --start <d> --end <d> [--config-id <id> ...] [--channel <c>] [--out <path>]` | 店铺维度每日明细。默认解析为 JSON 行；`--out` 落盘原始 CSV |

## 常见问题

### @bty/customer-service-cli 是什么？

AI客户服务CLI -座席友好

### 如何安装 @bty/customer-service-cli？

在项目目录运行 npm install @bty/customer-service-cli，或使用 pnpm add / yarn add / bun add。它还提供命令行工具 cs-cli，可以用 npx cs-cli 直接运行。

### @bty/customer-service-cli 支持 TypeScript 吗？

该包的 package\.json 没有声明类型入口。需要类型提示时，可以在 npm 上查找社区维护的 @types/bty\_\_customer-service-cli 是否存在。

### @bty/customer-service-cli 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @bty/customer-service-cli 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=20\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @bty/customer-service-cli 可以商用吗？

@bty/customer-service-cli 使用 未声明 许可。package\.json 没有声明许可证。没有许可证的代码默认保留全部权利，商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括，不构成法律意见，请以许可原文为准。

### @bty/customer-service-cli 还在维护吗？

最新版本 0\.8\.5 发布于 2026/11/01（2 天前）；至今共发布 76 个版本，其中过去 12 个月发布了 76 个；首次发布于 2026/03/22。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

