@bty/customer-service-cli 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +43 -0
- package/dist/bin.js +521 -59
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.6 (2026-07-17)
|
|
4
|
+
|
|
5
|
+
- 新增 `media upload` 命令与 `product supplement` 子命令组,支持商品「补充知识」图片/视频维护,与前端上传行为一致:
|
|
6
|
+
- `media upload --file <path> --type image|video [--path-prefix /ai/knowledge/supplement] [--dry-run]`:本地图片/视频先经 `/v1/upload_sts` 取 STS 凭证上传到公开 OSS(`resource_bucket`),返回公网 URL;已是 http(s) URL 直接拒绝(无需上传)。上传 key 与前端一致:`${prefix}/${timestamp}_${filename}`。图片上限 10MB、视频上限 200MB,超限/类型不符在发起上传请求前直接失败。
|
|
7
|
+
- `product supplement list --agent <id> --product-id <id>`:读取并解析商品「补充知识」条目(容错解析数组 / JSON 字符串 / 多层编码)。
|
|
8
|
+
- `product supplement add --agent <id> --product-id <id> --question <Q> --answer <A> [--image <path|url>...] [--video <path|url>...] [--dry-run] [--backup <path>]`:read-modify-write 追加一条 Q/A + 媒体,保留旧条目全量写回;本地媒体自动上传、URL 直写。
|
|
9
|
+
- `product supplement append-media --agent <id> --product-id <id> --index <n> [--image ...] [--video ...] [--dry-run] [--backup <path>]`:仅向目标下标条目追加媒体,其它条目原样保留;去重合并,未提供任何媒体时报错。
|
|
10
|
+
- Dashboard 命令使用客服 API 原域名下的 `/observability` 网关前缀,无需新增地址配置。
|
|
11
|
+
|
|
12
|
+
## 0.6.5 (2026-07-14)
|
|
13
|
+
|
|
14
|
+
- `debug ask` / `debug reproduce` 新增 `--mock <json|@file>`(工具结果 mock,形如 `[{ tool_name, result }]`)与 `--data-overlay <json|@file>`(本轮 `debug_data_overlay` 数据覆盖),发送时透传给 `/v1/debug/chat/agent_message`;`reproduce --dry-run` 输出中同步包含 mock 与 overlay。
|
|
15
|
+
- 新增 `debug data snapshot --agent <id> [--product-id <商品ID...>]`:把当前 Agent 的 SOP / 商品 / 活动等数据导出为 `debug_data_overlay` v1 JSON(写 stdout),配合 `--data-overlay @file` 复跑固定数据集;`--product-id` 可重复传多个只导指定商品,不传则分页导出全量商品。
|
|
16
|
+
|
|
3
17
|
## 0.6.4 (2026-07-04)
|
|
4
18
|
|
|
5
19
|
- `conversation transfer-human` 适配服务端 cursor 分页:移除 CLI 暴露的 `--page` 参数,CLI 自动沿 `next_cursor` 拉取全部结果并合并返回,避免深翻页请求压垮数据库。
|
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ pnpm build
|
|
|
29
29
|
| 服务 | 默认地址 |
|
|
30
30
|
| -------------- | --------------------------------------------- |
|
|
31
31
|
| 客服 API | `https://customer-servhub-api.betteryeah.com` |
|
|
32
|
+
| Dashboard 只读 API | 客服 API 地址追加 `/observability` |
|
|
32
33
|
| AI API | `https://ai-api.betteryeah.com` |
|
|
33
34
|
| Customer Agent | `https://customer-agent.bantouyan.com` |
|
|
34
35
|
| Langfuse | `http://192.168.40.10:3000` |
|
|
@@ -41,6 +42,8 @@ pnpm build
|
|
|
41
42
|
cs-cli config set --cs-api https://your-api.example.com
|
|
42
43
|
```
|
|
43
44
|
|
|
45
|
+
Dashboard 命令会自动请求该地址下的 `/observability/v1/dashboard/**`。
|
|
46
|
+
|
|
44
47
|
### API 地址优先级
|
|
45
48
|
|
|
46
49
|
每个 API 地址的读取顺序为 `环境变量 > config.json > 内置默认值`。环境变量为 `undefined` 或空字符串 `""` 都视为未设置,继续向下 fallback。
|
|
@@ -231,6 +234,9 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
|
|
|
231
234
|
| `product related-knowledge add --agent <id> --product-id <商品ID> --type <qa\|common> (--file <文件名> \| --file-id <id> --partition-id <id>)` | 给商品追加关联知识库文件 |
|
|
232
235
|
| `product related-knowledge remove --agent <id> --product-id <商品ID> --type <qa\|common> --file-id <id>` | 移除商品绑定的指定知识库文件 |
|
|
233
236
|
| `product related-knowledge set --agent <id> --product-id <商品ID> --bindings <json\|@file>` | 覆盖商品绑定的全部知识库文件 |
|
|
237
|
+
| `product supplement list --agent <id> --product-id <商品ID>` | 列出商品补充知识条目(含每条的 question / answer / images / videos) |
|
|
238
|
+
| `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 保留旧条目 |
|
|
239
|
+
| `product supplement append-media --agent <id> --product-id <商品ID> --index <n> [--image ...] [--video ...] [--dry-run] [--backup <path>]` | 向指定下标条目追加图片/视频,其它条目原样保留、去重合并 |
|
|
234
240
|
|
|
235
241
|
`product update` 支持更新的字段包括:`卖点`、`补充知识`、`tag`、`参数`、`轮播图识别结果`、`商品详情页识别结果` 等。`--update` 接受 JSON 字符串或 `@文件路径`。
|
|
236
242
|
|
|
@@ -246,6 +252,10 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
|
|
|
246
252
|
|
|
247
253
|
`product related-knowledge` 管理商品详情的“关联知识库”文件级绑定。`--type qa` 对应 FAQ 知识库,`--type common` 对应扩展知识库。`add` / `remove` 会先读取当前绑定,再通过服务端全量替换接口写回;重复添加或移除不存在的文件保持幂等。`--file <文件名>` 会按当前 Agent 的知识库文件列表做精确匹配;文件名不存在或不唯一时,改用 `--file-id` + `--partition-id` 显式模式。
|
|
248
254
|
|
|
255
|
+
`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` 指定的条目追加媒体,去重合并,其它条目原样保留。
|
|
256
|
+
|
|
257
|
+
单文件上传也可单独用 `media upload`(见下方「媒体上传」小节),拿到公网 URL 后再作为 `--image` / `--video` 的值传入。
|
|
258
|
+
|
|
249
259
|
```bash
|
|
250
260
|
# 更新卖点
|
|
251
261
|
cs-cli product update --agent <id> --product-id <商品ID> --update '{"卖点":"新卖点内容"}'
|
|
@@ -267,6 +277,39 @@ cs-cli product related-knowledge add --agent <id> --product-id <商品ID> --type
|
|
|
267
277
|
|
|
268
278
|
# 显式 ID 模式绑定扩展知识文件
|
|
269
279
|
cs-cli product related-knowledge add --agent <id> --product-id <商品ID> --type common --file-id 622474 --partition-id 12345 --file-name "售后规则.md"
|
|
280
|
+
|
|
281
|
+
# 查看商品当前补充知识条目
|
|
282
|
+
cs-cli product supplement list --agent <id> --product-id <商品ID>
|
|
283
|
+
|
|
284
|
+
# 新增一条带图问答(本地图片自动上传,已有 URL 直写)
|
|
285
|
+
cs-cli product supplement add --agent <id> --product-id <商品ID> \
|
|
286
|
+
--question "尺码怎么选?" --answer "参考尺码表,拍下留言身高体重" \
|
|
287
|
+
--image ./size-chart.png --image https://xxx.com/a.png
|
|
288
|
+
|
|
289
|
+
# 先 dry-run 预览最终写入 JSON,不落库
|
|
290
|
+
cs-cli product supplement add --agent <id> --product-id <商品ID> \
|
|
291
|
+
--question "Q" --answer "A" --video ./demo.mp4 --dry-run
|
|
292
|
+
|
|
293
|
+
# 向第 0 条补充知识追加媒体,写前备份
|
|
294
|
+
cs-cli product supplement append-media --agent <id> --product-id <商品ID> \
|
|
295
|
+
--index 0 --image ./extra.png --backup ./supplement-backup.json
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
### 媒体上传 (`media`)
|
|
300
|
+
|
|
301
|
+
| 命令 | 说明 |
|
|
302
|
+
| --- | --- |
|
|
303
|
+
| `media upload --file <path> [--type <image\|video>] [--path-prefix <prefix>] [--dry-run]` | 上传单个本地图片/视频到公开 OSS,返回可访问 URL |
|
|
304
|
+
|
|
305
|
+
`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` 仅校验并输出将要上传的信息、不实际上传。
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
# 上传本地图片,拿到公网 URL
|
|
309
|
+
cs-cli media upload --file ./banner.png --type image
|
|
310
|
+
|
|
311
|
+
# 自定义路径前缀 + 预览(不实际上传)
|
|
312
|
+
cs-cli media upload --file ./demo.mp4 --path-prefix /ai/knowledge/supplement --dry-run
|
|
270
313
|
```
|
|
271
314
|
|
|
272
315
|
|
package/dist/bin.js
CHANGED
|
@@ -318,7 +318,7 @@ function toExitCode(err) {
|
|
|
318
318
|
return 1;
|
|
319
319
|
}
|
|
320
320
|
function createRequest(globalTimeout) {
|
|
321
|
-
return async function request(baseUrl,
|
|
321
|
+
return async function request(baseUrl, path7, options) {
|
|
322
322
|
const headers = {
|
|
323
323
|
"Content-Type": "application/json",
|
|
324
324
|
...options.headers
|
|
@@ -346,7 +346,7 @@ function createRequest(globalTimeout) {
|
|
|
346
346
|
if (workspaceId) {
|
|
347
347
|
headers["workspace-id"] = workspaceId;
|
|
348
348
|
}
|
|
349
|
-
let url = `${baseUrl}${
|
|
349
|
+
let url = `${baseUrl}${path7}`;
|
|
350
350
|
if (options.query) {
|
|
351
351
|
const params = new URLSearchParams();
|
|
352
352
|
for (const [key, value] of Object.entries(options.query)) {
|
|
@@ -485,6 +485,9 @@ function getCustomerServiceUrl() {
|
|
|
485
485
|
const config = readConfig();
|
|
486
486
|
return config?.customerServiceApiUrl ?? DEFAULT_CS_API_URL;
|
|
487
487
|
}
|
|
488
|
+
function getObservabilityUrl() {
|
|
489
|
+
return `${getCustomerServiceUrl().replace(/\/+$/, "")}/observability`;
|
|
490
|
+
}
|
|
488
491
|
function getAiApiUrl() {
|
|
489
492
|
const envUrl = process.env.CS_AI_API_URL;
|
|
490
493
|
if (envUrl) return envUrl;
|
|
@@ -1401,22 +1404,22 @@ function registerConversationCommand(program2) {
|
|
|
1401
1404
|
var DASHBOARD_PREFIX = "/v1/dashboard";
|
|
1402
1405
|
async function getDashboardSummary(opts) {
|
|
1403
1406
|
const request = createRequest();
|
|
1404
|
-
const
|
|
1407
|
+
const path7 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/summary` : `${DASHBOARD_PREFIX}/summary`;
|
|
1405
1408
|
const query = {};
|
|
1406
1409
|
if (opts.startDate) query.start_date = opts.startDate;
|
|
1407
1410
|
if (opts.endDate) query.end_date = opts.endDate;
|
|
1408
1411
|
if (opts.channel) query.channel = opts.channel;
|
|
1409
|
-
return request(
|
|
1412
|
+
return request(getObservabilityUrl(), path7, { method: "GET", query });
|
|
1410
1413
|
}
|
|
1411
1414
|
async function getDashboardTrend(opts) {
|
|
1412
1415
|
const request = createRequest();
|
|
1413
|
-
const
|
|
1416
|
+
const path7 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/trend` : `${DASHBOARD_PREFIX}/trend`;
|
|
1414
1417
|
const query = {};
|
|
1415
1418
|
if (opts.startDate) query.start_date = opts.startDate;
|
|
1416
1419
|
if (opts.endDate) query.end_date = opts.endDate;
|
|
1417
1420
|
if (opts.granularity) query.granularity = opts.granularity;
|
|
1418
1421
|
if (opts.channel) query.channel = opts.channel;
|
|
1419
|
-
return request(
|
|
1422
|
+
return request(getObservabilityUrl(), path7, { method: "GET", query });
|
|
1420
1423
|
}
|
|
1421
1424
|
async function listShopStatistics(opts) {
|
|
1422
1425
|
const request = createRequest();
|
|
@@ -1424,7 +1427,7 @@ async function listShopStatistics(opts) {
|
|
|
1424
1427
|
if (opts.startDate) query.start_date = opts.startDate;
|
|
1425
1428
|
if (opts.endDate) query.end_date = opts.endDate;
|
|
1426
1429
|
if (opts.channel) query.channel = opts.channel;
|
|
1427
|
-
return request(
|
|
1430
|
+
return request(getObservabilityUrl(), `${DASHBOARD_PREFIX}/shop-statistics`, {
|
|
1428
1431
|
method: "GET",
|
|
1429
1432
|
query
|
|
1430
1433
|
});
|
|
@@ -1435,7 +1438,7 @@ async function getIntentStatistics(opts) {
|
|
|
1435
1438
|
if (opts.startDate) query.start_date = opts.startDate;
|
|
1436
1439
|
if (opts.endDate) query.end_date = opts.endDate;
|
|
1437
1440
|
return request(
|
|
1438
|
-
|
|
1441
|
+
getObservabilityUrl(),
|
|
1439
1442
|
`${DASHBOARD_PREFIX}/${opts.configId}/intent-statistics`,
|
|
1440
1443
|
{ method: "GET", query }
|
|
1441
1444
|
);
|
|
@@ -1451,7 +1454,7 @@ async function listIntentConversations(opts) {
|
|
|
1451
1454
|
if (opts.page) query.page = opts.page;
|
|
1452
1455
|
if (opts.pageSize) query.page_size = opts.pageSize;
|
|
1453
1456
|
return request(
|
|
1454
|
-
|
|
1457
|
+
getObservabilityUrl(),
|
|
1455
1458
|
`${DASHBOARD_PREFIX}/${opts.configId}/intent-conversations`,
|
|
1456
1459
|
{ method: "GET", query }
|
|
1457
1460
|
);
|
|
@@ -1539,7 +1542,7 @@ async function exportShopData(opts) {
|
|
|
1539
1542
|
if (opts.configIds && opts.configIds.length > 0) body.config_ids = opts.configIds;
|
|
1540
1543
|
if (opts.channel) body.channel = opts.channel;
|
|
1541
1544
|
const data = await request(
|
|
1542
|
-
|
|
1545
|
+
getObservabilityUrl(),
|
|
1543
1546
|
`${DASHBOARD_PREFIX}/export`,
|
|
1544
1547
|
{ method: "POST", body }
|
|
1545
1548
|
);
|
|
@@ -1636,12 +1639,12 @@ function registerDashboardCommand(program2) {
|
|
|
1636
1639
|
channel: opts.channel
|
|
1637
1640
|
});
|
|
1638
1641
|
if (opts.out) {
|
|
1639
|
-
const { path:
|
|
1642
|
+
const { path: path7, bytes } = await writeBinaryToFile(
|
|
1640
1643
|
opts.out,
|
|
1641
1644
|
Buffer.from(result.csv, "utf-8")
|
|
1642
1645
|
);
|
|
1643
1646
|
formatOutput(
|
|
1644
|
-
{ success: true, data: { path:
|
|
1647
|
+
{ success: true, data: { path: path7, bytes, count: result.rows.length } },
|
|
1645
1648
|
program2.opts().table
|
|
1646
1649
|
);
|
|
1647
1650
|
} else {
|
|
@@ -4062,6 +4065,252 @@ function registerKnowledgeCommand(program2) {
|
|
|
4062
4065
|
registerKnowledgeFileBindProductsCommand(knowledge, program2, "common");
|
|
4063
4066
|
}
|
|
4064
4067
|
|
|
4068
|
+
// src/lib/media-upload.ts
|
|
4069
|
+
import fs9 from "fs";
|
|
4070
|
+
import path5 from "path";
|
|
4071
|
+
import OSS from "ali-oss";
|
|
4072
|
+
|
|
4073
|
+
// src/client/oss-api.ts
|
|
4074
|
+
import CryptoJS2 from "crypto-js";
|
|
4075
|
+
var STS_SECRET_KEY = "asdb2l0jskjft1q314f3ffjy71k9shj6";
|
|
4076
|
+
var IV_BYTES_LEN = 16;
|
|
4077
|
+
function decryptStsPayload(ciphertext) {
|
|
4078
|
+
const ciphertextBytes = CryptoJS2.enc.Base64.parse(
|
|
4079
|
+
ciphertext.replaceAll("-", "+").replaceAll("_", "/")
|
|
4080
|
+
);
|
|
4081
|
+
const iv = ciphertextBytes.clone();
|
|
4082
|
+
iv.sigBytes = IV_BYTES_LEN;
|
|
4083
|
+
iv.clamp();
|
|
4084
|
+
const encryptedContent = ciphertextBytes.clone();
|
|
4085
|
+
encryptedContent.words.splice(0, IV_BYTES_LEN / 4);
|
|
4086
|
+
encryptedContent.sigBytes -= IV_BYTES_LEN;
|
|
4087
|
+
const decrypted = CryptoJS2.AES.decrypt(
|
|
4088
|
+
// biome-ignore lint/suspicious/noExplicitAny: CryptoJS CipherParams 结构,仅需 ciphertext 字段
|
|
4089
|
+
{ ciphertext: encryptedContent },
|
|
4090
|
+
CryptoJS2.enc.Utf8.parse(STS_SECRET_KEY),
|
|
4091
|
+
{
|
|
4092
|
+
iv,
|
|
4093
|
+
mode: CryptoJS2.mode.CBC,
|
|
4094
|
+
padding: CryptoJS2.pad.Pkcs7
|
|
4095
|
+
}
|
|
4096
|
+
);
|
|
4097
|
+
return decrypted.toString(CryptoJS2.enc.Utf8);
|
|
4098
|
+
}
|
|
4099
|
+
async function getUploadStsToken() {
|
|
4100
|
+
const request = createRequest();
|
|
4101
|
+
const encrypted = await request(getAiApiUrl(), "/v1/upload_sts", {
|
|
4102
|
+
method: "GET"
|
|
4103
|
+
});
|
|
4104
|
+
if (typeof encrypted !== "string" || encrypted.length === 0) {
|
|
4105
|
+
throw new Error("\u83B7\u53D6 OSS \u4E0A\u4F20\u51ED\u8BC1\u5931\u8D25\uFF1A\u63A5\u53E3\u672A\u8FD4\u56DE\u52A0\u5BC6\u6570\u636E");
|
|
4106
|
+
}
|
|
4107
|
+
let decoded;
|
|
4108
|
+
try {
|
|
4109
|
+
decoded = decryptStsPayload(encrypted);
|
|
4110
|
+
} catch (err) {
|
|
4111
|
+
throw new Error(`OSS \u4E0A\u4F20\u51ED\u8BC1\u89E3\u5BC6\u5931\u8D25\uFF1A${err.message}`);
|
|
4112
|
+
}
|
|
4113
|
+
let token;
|
|
4114
|
+
try {
|
|
4115
|
+
token = JSON.parse(decoded);
|
|
4116
|
+
} catch {
|
|
4117
|
+
throw new Error("OSS \u4E0A\u4F20\u51ED\u8BC1\u89E3\u6790\u5931\u8D25\uFF1A\u89E3\u5BC6\u7ED3\u679C\u4E0D\u662F\u5408\u6CD5 JSON");
|
|
4118
|
+
}
|
|
4119
|
+
if (!token.access_key_id || !token.access_key_secret) {
|
|
4120
|
+
throw new Error("OSS \u4E0A\u4F20\u51ED\u8BC1\u7F3A\u5C11 access_key_id / access_key_secret");
|
|
4121
|
+
}
|
|
4122
|
+
return token;
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
// src/lib/media-upload.ts
|
|
4126
|
+
var IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
4127
|
+
var VIDEO_MAX_BYTES = 200 * 1024 * 1024;
|
|
4128
|
+
var DEFAULT_PATH_PREFIX = "/ai/knowledge/supplement";
|
|
4129
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set([
|
|
4130
|
+
".jpg",
|
|
4131
|
+
".jpeg",
|
|
4132
|
+
".png",
|
|
4133
|
+
".gif",
|
|
4134
|
+
".webp",
|
|
4135
|
+
".bmp",
|
|
4136
|
+
".svg",
|
|
4137
|
+
".tiff",
|
|
4138
|
+
".tif",
|
|
4139
|
+
".ico",
|
|
4140
|
+
".heic",
|
|
4141
|
+
".heif",
|
|
4142
|
+
".avif"
|
|
4143
|
+
]);
|
|
4144
|
+
var VIDEO_EXTS = /* @__PURE__ */ new Set([
|
|
4145
|
+
".mp4",
|
|
4146
|
+
".mov",
|
|
4147
|
+
".avi",
|
|
4148
|
+
".mkv",
|
|
4149
|
+
".webm",
|
|
4150
|
+
".flv",
|
|
4151
|
+
".wmv",
|
|
4152
|
+
".m4v",
|
|
4153
|
+
".mpeg",
|
|
4154
|
+
".mpg",
|
|
4155
|
+
".3gp"
|
|
4156
|
+
]);
|
|
4157
|
+
function isRemoteUrl(value) {
|
|
4158
|
+
return /^https?:\/\//i.test(value.trim());
|
|
4159
|
+
}
|
|
4160
|
+
function inferMediaType(filePath) {
|
|
4161
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
4162
|
+
if (IMAGE_EXTS.has(ext)) return "image";
|
|
4163
|
+
if (VIDEO_EXTS.has(ext)) return "video";
|
|
4164
|
+
return void 0;
|
|
4165
|
+
}
|
|
4166
|
+
function extMatchesType(filePath, type) {
|
|
4167
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
4168
|
+
if (type === "image") return !VIDEO_EXTS.has(ext);
|
|
4169
|
+
return !IMAGE_EXTS.has(ext);
|
|
4170
|
+
}
|
|
4171
|
+
function validateLocalFile(opts) {
|
|
4172
|
+
const { filePath } = opts;
|
|
4173
|
+
const absPath = path5.resolve(filePath);
|
|
4174
|
+
let stat;
|
|
4175
|
+
try {
|
|
4176
|
+
stat = fs9.statSync(absPath);
|
|
4177
|
+
} catch {
|
|
4178
|
+
throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`);
|
|
4179
|
+
}
|
|
4180
|
+
if (!stat.isFile()) {
|
|
4181
|
+
throw new Error(`\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${filePath}`);
|
|
4182
|
+
}
|
|
4183
|
+
const type = opts.type ?? inferMediaType(absPath);
|
|
4184
|
+
if (!type) {
|
|
4185
|
+
throw new Error(`\u65E0\u6CD5\u6839\u636E\u6269\u5C55\u540D\u5224\u65AD\u5A92\u4F53\u7C7B\u578B\uFF0C\u8BF7\u663E\u5F0F\u6307\u5B9A --type image|video: ${filePath}`);
|
|
4186
|
+
}
|
|
4187
|
+
if (type !== "image" && type !== "video") {
|
|
4188
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u5A92\u4F53\u7C7B\u578B: ${type}\uFF08\u4EC5\u652F\u6301 image / video\uFF09`);
|
|
4189
|
+
}
|
|
4190
|
+
if (!extMatchesType(absPath, type)) {
|
|
4191
|
+
throw new Error(`\u6587\u4EF6\u6269\u5C55\u540D\u4E0E\u58F0\u660E\u7684\u7C7B\u578B ${type} \u4E0D\u5339\u914D: ${filePath}`);
|
|
4192
|
+
}
|
|
4193
|
+
const limit = type === "image" ? IMAGE_MAX_BYTES : VIDEO_MAX_BYTES;
|
|
4194
|
+
if (stat.size > limit) {
|
|
4195
|
+
const limitMb = Math.round(limit / (1024 * 1024));
|
|
4196
|
+
const sizeMb = (stat.size / (1024 * 1024)).toFixed(2);
|
|
4197
|
+
throw new Error(
|
|
4198
|
+
`${type === "image" ? "\u56FE\u7247" : "\u89C6\u9891"}\u5927\u5C0F ${sizeMb}MB \u8D85\u8FC7\u4E0A\u9650 ${limitMb}MB: ${filePath}`
|
|
4199
|
+
);
|
|
4200
|
+
}
|
|
4201
|
+
return {
|
|
4202
|
+
absPath,
|
|
4203
|
+
filename: path5.basename(absPath),
|
|
4204
|
+
type,
|
|
4205
|
+
sizeBytes: stat.size
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4208
|
+
function buildObjectKey(opts) {
|
|
4209
|
+
const ts = opts.timestamp ?? Date.now();
|
|
4210
|
+
const prefix = opts.pathPrefix.replace(/\/+$/, "").replace(/^\/+/, "");
|
|
4211
|
+
return `${prefix}/${ts}_${opts.filename}`;
|
|
4212
|
+
}
|
|
4213
|
+
var defaultOssPut = async ({ token, bucket, key, bytes }) => {
|
|
4214
|
+
const client = new OSS({
|
|
4215
|
+
region: token.region || "oss-cn-hangzhou",
|
|
4216
|
+
accessKeyId: token.access_key_id,
|
|
4217
|
+
accessKeySecret: token.access_key_secret,
|
|
4218
|
+
stsToken: token.security_token,
|
|
4219
|
+
bucket,
|
|
4220
|
+
secure: true,
|
|
4221
|
+
...token.endpoint ? { endpoint: token.endpoint } : {}
|
|
4222
|
+
});
|
|
4223
|
+
const normalizedKey = key.replace(/^\/+/, "");
|
|
4224
|
+
const result = await client.put(normalizedKey, bytes);
|
|
4225
|
+
const rawUrl = result?.url ?? result?.res?.requestUrls?.[0];
|
|
4226
|
+
if (!rawUrl) {
|
|
4227
|
+
throw new Error("\u4E0A\u4F20\u6210\u529F\u4F46\u672A\u80FD\u89E3\u6790\u8FD4\u56DE URL");
|
|
4228
|
+
}
|
|
4229
|
+
return rawUrl.split("?")[0];
|
|
4230
|
+
};
|
|
4231
|
+
var ossPutImpl = defaultOssPut;
|
|
4232
|
+
async function uploadLocalFile(opts) {
|
|
4233
|
+
const token = await getUploadStsToken();
|
|
4234
|
+
const bucket = token.resource_bucket;
|
|
4235
|
+
if (!bucket) {
|
|
4236
|
+
throw new Error("OSS \u4E0A\u4F20\u51ED\u8BC1\u7F3A\u5C11 resource_bucket\uFF08\u516C\u5F00\u6876\uFF09\uFF0C\u65E0\u6CD5\u4E0A\u4F20\u516C\u5F00\u5A92\u4F53");
|
|
4237
|
+
}
|
|
4238
|
+
const key = buildObjectKey({
|
|
4239
|
+
pathPrefix: opts.pathPrefix ?? DEFAULT_PATH_PREFIX,
|
|
4240
|
+
filename: opts.file.filename,
|
|
4241
|
+
timestamp: opts.timestamp
|
|
4242
|
+
});
|
|
4243
|
+
const bytes = fs9.readFileSync(opts.file.absPath);
|
|
4244
|
+
try {
|
|
4245
|
+
return await ossPutImpl({ token, bucket, key, bytes });
|
|
4246
|
+
} catch (err) {
|
|
4247
|
+
throw new Error(`OSS \u4E0A\u4F20\u5931\u8D25: ${err.message}`);
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
async function resolveMediaInput(opts) {
|
|
4251
|
+
const value = opts.value.trim();
|
|
4252
|
+
if (isRemoteUrl(value)) {
|
|
4253
|
+
return value;
|
|
4254
|
+
}
|
|
4255
|
+
const file = validateLocalFile({ filePath: value, type: opts.type });
|
|
4256
|
+
if (opts.dryRun) {
|
|
4257
|
+
return `<dry-run:will-upload:${file.filename}>`;
|
|
4258
|
+
}
|
|
4259
|
+
return uploadLocalFile({ file, pathPrefix: opts.pathPrefix });
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
// src/commands/media.ts
|
|
4263
|
+
function registerMediaCommand(program2) {
|
|
4264
|
+
const media = program2.command("media").description(
|
|
4265
|
+
"\u5A92\u4F53\u4E0A\u4F20 \u2014\u2014 \u5C06\u672C\u5730\u56FE\u7247/\u89C6\u9891\u4E0A\u4F20\u5230\u516C\u5F00 OSS \u5E76\u8FD4\u56DE\u516C\u7F51 URL\u3002\u7528\u4E8E\u7EF4\u62A4\u5546\u54C1\u8865\u5145\u77E5\u8BC6\u4E2D\u7684 images / videos\uFF08\u8DEF\u5F84\u524D\u7F00\u4E0E\u524D\u7AEF\u4E00\u81F4\uFF09"
|
|
4266
|
+
);
|
|
4267
|
+
media.command("upload").description(
|
|
4268
|
+
"\u4E0A\u4F20\u5355\u4E2A\u672C\u5730\u56FE\u7247/\u89C6\u9891\u5230\u516C\u5F00 OSS\uFF0C\u8FD4\u56DE\u53EF\u8BBF\u95EE URL\u3002\u56FE\u7247\u9650 10MB\u3001\u89C6\u9891\u9650 200MB\uFF1B\u8D85\u9650\u76F4\u63A5\u62D2\u7EDD\uFF0C\u4E0D\u53D1\u4E0A\u4F20\u8BF7\u6C42"
|
|
4269
|
+
).requiredOption("--file <path>", "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84").option("--type <type>", "\u5A92\u4F53\u7C7B\u578B: image | video\uFF08\u7F3A\u7701\u6309\u6269\u5C55\u540D\u63A8\u65AD\uFF09").option(
|
|
4270
|
+
"--path-prefix <prefix>",
|
|
4271
|
+
"OSS \u8DEF\u5F84\u524D\u7F00\uFF0C\u6700\u7EC8 key \u4E3A <prefix>/<timestamp>_<filename>",
|
|
4272
|
+
DEFAULT_PATH_PREFIX
|
|
4273
|
+
).option("--dry-run", "\u4EC5\u6821\u9A8C\u5E76\u8F93\u51FA\u5C06\u8981\u4E0A\u4F20\u7684\u4FE1\u606F\uFF0C\u4E0D\u5B9E\u9645\u4E0A\u4F20").action(async (opts) => {
|
|
4274
|
+
try {
|
|
4275
|
+
if (isRemoteUrl(opts.file)) {
|
|
4276
|
+
throw new Error("--file \u9700\u4E3A\u672C\u5730\u8DEF\u5F84\uFF1B\u5DF2\u662F URL \u7684\u5A92\u4F53\u65E0\u9700\u4E0A\u4F20");
|
|
4277
|
+
}
|
|
4278
|
+
const type = opts.type;
|
|
4279
|
+
if (type && type !== "image" && type !== "video") {
|
|
4280
|
+
throw new Error(`--type \u4EC5\u652F\u6301 image | video\uFF0C\u6536\u5230: ${type}`);
|
|
4281
|
+
}
|
|
4282
|
+
const file = validateLocalFile({ filePath: opts.file, type });
|
|
4283
|
+
if (opts.dryRun) {
|
|
4284
|
+
formatOutput(
|
|
4285
|
+
{
|
|
4286
|
+
success: true,
|
|
4287
|
+
data: {
|
|
4288
|
+
dryRun: true,
|
|
4289
|
+
file: file.absPath,
|
|
4290
|
+
type: file.type,
|
|
4291
|
+
sizeBytes: file.sizeBytes,
|
|
4292
|
+
pathPrefix: opts.pathPrefix
|
|
4293
|
+
}
|
|
4294
|
+
},
|
|
4295
|
+
program2.opts().table
|
|
4296
|
+
);
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
const url = await uploadLocalFile({ file, pathPrefix: opts.pathPrefix });
|
|
4300
|
+
formatOutput(
|
|
4301
|
+
{
|
|
4302
|
+
success: true,
|
|
4303
|
+
data: { url, type: file.type, sizeBytes: file.sizeBytes }
|
|
4304
|
+
},
|
|
4305
|
+
program2.opts().table
|
|
4306
|
+
);
|
|
4307
|
+
} catch (err) {
|
|
4308
|
+
reportCaughtError(err);
|
|
4309
|
+
process.exit(toExitCode(err));
|
|
4310
|
+
}
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4065
4314
|
// src/client/monitor-api.ts
|
|
4066
4315
|
var MONITOR_PATH_PREFIX = "/v1/operation";
|
|
4067
4316
|
async function listAgentLifecycle(opts) {
|
|
@@ -4954,44 +5203,6 @@ function registerNodeTemplateCommand(program2) {
|
|
|
4954
5203
|
});
|
|
4955
5204
|
}
|
|
4956
5205
|
|
|
4957
|
-
// src/client/ops-agent-api.ts
|
|
4958
|
-
async function listOpsAgentConversations(opts) {
|
|
4959
|
-
const workspaceId = getWorkspaceId(opts.workspaceId);
|
|
4960
|
-
const request = createRequest();
|
|
4961
|
-
const query = {
|
|
4962
|
-
page: opts.page ?? 1,
|
|
4963
|
-
page_size: opts.pageSize ?? 20
|
|
4964
|
-
};
|
|
4965
|
-
if (opts.customerId) query.customer_id = opts.customerId;
|
|
4966
|
-
if (opts.agentId) query.agent_id = opts.agentId;
|
|
4967
|
-
return request(getCustomerServiceUrl(), "/v1/ops-agent/conversations", {
|
|
4968
|
-
method: "GET",
|
|
4969
|
-
query,
|
|
4970
|
-
headers: { "workspace-id": workspaceId }
|
|
4971
|
-
});
|
|
4972
|
-
}
|
|
4973
|
-
|
|
4974
|
-
// src/commands/ops-agent.ts
|
|
4975
|
-
function registerOpsAgentCommand(program2) {
|
|
4976
|
-
const opsAgent = program2.command("ops-agent").description("Ops Agent\uFF08\u5BA2\u670D\u52A9\u624B\uFF09\u67E5\u8BE2\u547D\u4EE4");
|
|
4977
|
-
opsAgent.command("conversations").description(
|
|
4978
|
-
"\u67E5\u8BE2\u5BA2\u670D\u52A9\u624B\u4F1A\u8BDD\u5217\u8868\u3002\u8FD4\u56DE workspace_id\u3001customer_id\u3001title\u3001agent_id\u3001created_at\u3001updated_at"
|
|
4979
|
-
).option("--customer-id <id>", "\u6309 customer_id \u7B5B\u9009").option("--agent-id <id>", "\u6309 Ops Agent \u914D\u7F6E ID\uFF08customer_agent_config_id\uFF09\u7B5B\u9009").option("--page <number>", "\u9875\u7801", "1").option("--page-size <number>", "\u6BCF\u9875\u6570\u91CF", "20").action(async (opts) => {
|
|
4980
|
-
try {
|
|
4981
|
-
const data = await listOpsAgentConversations({
|
|
4982
|
-
customerId: opts.customerId,
|
|
4983
|
-
agentId: opts.agentId,
|
|
4984
|
-
page: Number(opts.page),
|
|
4985
|
-
pageSize: Number(opts.pageSize)
|
|
4986
|
-
});
|
|
4987
|
-
formatOutput({ success: true, data }, program2.opts().table);
|
|
4988
|
-
} catch (err) {
|
|
4989
|
-
reportCaughtError(err);
|
|
4990
|
-
process.exit(toExitCode(err));
|
|
4991
|
-
}
|
|
4992
|
-
});
|
|
4993
|
-
}
|
|
4994
|
-
|
|
4995
5206
|
// src/client/operations-record-api.ts
|
|
4996
5207
|
var PATH_PREFIX3 = "/v1/agent_operations_records";
|
|
4997
5208
|
async function listOperationsRecords(opts) {
|
|
@@ -5106,6 +5317,112 @@ function registerOperationsRecordCommand(program2) {
|
|
|
5106
5317
|
});
|
|
5107
5318
|
}
|
|
5108
5319
|
|
|
5320
|
+
// src/client/ops-agent-api.ts
|
|
5321
|
+
async function listOpsAgentConversations(opts) {
|
|
5322
|
+
const workspaceId = getWorkspaceId(opts.workspaceId);
|
|
5323
|
+
const request = createRequest();
|
|
5324
|
+
const query = {
|
|
5325
|
+
page: opts.page ?? 1,
|
|
5326
|
+
page_size: opts.pageSize ?? 20
|
|
5327
|
+
};
|
|
5328
|
+
if (opts.customerId) query.customer_id = opts.customerId;
|
|
5329
|
+
if (opts.agentId) query.agent_id = opts.agentId;
|
|
5330
|
+
return request(getCustomerServiceUrl(), "/v1/ops-agent/conversations", {
|
|
5331
|
+
method: "GET",
|
|
5332
|
+
query,
|
|
5333
|
+
headers: { "workspace-id": workspaceId }
|
|
5334
|
+
});
|
|
5335
|
+
}
|
|
5336
|
+
|
|
5337
|
+
// src/commands/ops-agent.ts
|
|
5338
|
+
function registerOpsAgentCommand(program2) {
|
|
5339
|
+
const opsAgent = program2.command("ops-agent").description("Ops Agent\uFF08\u5BA2\u670D\u52A9\u624B\uFF09\u67E5\u8BE2\u547D\u4EE4");
|
|
5340
|
+
opsAgent.command("conversations").description(
|
|
5341
|
+
"\u67E5\u8BE2\u5BA2\u670D\u52A9\u624B\u4F1A\u8BDD\u5217\u8868\u3002\u8FD4\u56DE workspace_id\u3001customer_id\u3001title\u3001agent_id\u3001created_at\u3001updated_at"
|
|
5342
|
+
).option("--customer-id <id>", "\u6309 customer_id \u7B5B\u9009").option("--agent-id <id>", "\u6309 Ops Agent \u914D\u7F6E ID\uFF08customer_agent_config_id\uFF09\u7B5B\u9009").option("--page <number>", "\u9875\u7801", "1").option("--page-size <number>", "\u6BCF\u9875\u6570\u91CF", "20").action(async (opts) => {
|
|
5343
|
+
try {
|
|
5344
|
+
const data = await listOpsAgentConversations({
|
|
5345
|
+
customerId: opts.customerId,
|
|
5346
|
+
agentId: opts.agentId,
|
|
5347
|
+
page: Number(opts.page),
|
|
5348
|
+
pageSize: Number(opts.pageSize)
|
|
5349
|
+
});
|
|
5350
|
+
formatOutput({ success: true, data }, program2.opts().table);
|
|
5351
|
+
} catch (err) {
|
|
5352
|
+
reportCaughtError(err);
|
|
5353
|
+
process.exit(toExitCode(err));
|
|
5354
|
+
}
|
|
5355
|
+
});
|
|
5356
|
+
}
|
|
5357
|
+
|
|
5358
|
+
// src/commands/product.ts
|
|
5359
|
+
import fs10 from "fs";
|
|
5360
|
+
|
|
5361
|
+
// src/lib/supplement.ts
|
|
5362
|
+
function parseSupplementField(value) {
|
|
5363
|
+
let current = value;
|
|
5364
|
+
for (let i = 0; i < 3; i++) {
|
|
5365
|
+
if (Array.isArray(current)) {
|
|
5366
|
+
return current.filter((it) => it != null && typeof it === "object");
|
|
5367
|
+
}
|
|
5368
|
+
if (typeof current !== "string") return [];
|
|
5369
|
+
const trimmed = current.trim();
|
|
5370
|
+
if (trimmed === "") return [];
|
|
5371
|
+
try {
|
|
5372
|
+
current = JSON.parse(trimmed);
|
|
5373
|
+
} catch {
|
|
5374
|
+
throw new Error(`\u8865\u5145\u77E5\u8BC6 JSON \u89E3\u6790\u5931\u8D25: ${truncate(trimmed)}`);
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
return Array.isArray(current) ? current.filter((it) => it != null && typeof it === "object") : [];
|
|
5378
|
+
}
|
|
5379
|
+
function truncate(s, max = 120) {
|
|
5380
|
+
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
5381
|
+
}
|
|
5382
|
+
function mergeUnique(existing, incoming) {
|
|
5383
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5384
|
+
const result = [];
|
|
5385
|
+
for (const v of [...existing ?? [], ...incoming]) {
|
|
5386
|
+
const t = (v ?? "").trim();
|
|
5387
|
+
if (t === "" || seen.has(t)) continue;
|
|
5388
|
+
seen.add(t);
|
|
5389
|
+
result.push(t);
|
|
5390
|
+
}
|
|
5391
|
+
return result;
|
|
5392
|
+
}
|
|
5393
|
+
function addSupplementItem(existing, item) {
|
|
5394
|
+
const next = {
|
|
5395
|
+
question: item.question,
|
|
5396
|
+
answer: item.answer
|
|
5397
|
+
};
|
|
5398
|
+
if (item.images && item.images.length > 0) next.images = mergeUnique([], item.images);
|
|
5399
|
+
if (item.videos && item.videos.length > 0) next.videos = mergeUnique([], item.videos);
|
|
5400
|
+
return [...existing, next];
|
|
5401
|
+
}
|
|
5402
|
+
function appendMediaToItem(existing, opts) {
|
|
5403
|
+
if (!Number.isInteger(opts.index) || opts.index < 0 || opts.index >= existing.length) {
|
|
5404
|
+
throw new Error(`\u8865\u5145\u77E5\u8BC6\u6761\u76EE\u4E0B\u6807\u8D8A\u754C: index=${opts.index}\uFF08\u5F53\u524D\u5171 ${existing.length} \u6761\uFF09`);
|
|
5405
|
+
}
|
|
5406
|
+
return existing.map((it, i) => {
|
|
5407
|
+
if (i !== opts.index) return it;
|
|
5408
|
+
const updated = { ...it };
|
|
5409
|
+
if (opts.images && opts.images.length > 0) {
|
|
5410
|
+
updated.images = mergeUnique(asStringArray(it.images), opts.images);
|
|
5411
|
+
}
|
|
5412
|
+
if (opts.videos && opts.videos.length > 0) {
|
|
5413
|
+
updated.videos = mergeUnique(asStringArray(it.videos), opts.videos);
|
|
5414
|
+
}
|
|
5415
|
+
return updated;
|
|
5416
|
+
});
|
|
5417
|
+
}
|
|
5418
|
+
function asStringArray(value) {
|
|
5419
|
+
if (!Array.isArray(value)) return [];
|
|
5420
|
+
return value.filter((v) => typeof v === "string");
|
|
5421
|
+
}
|
|
5422
|
+
function serializeSupplement(items) {
|
|
5423
|
+
return JSON.stringify(items);
|
|
5424
|
+
}
|
|
5425
|
+
|
|
5109
5426
|
// src/commands/product.ts
|
|
5110
5427
|
async function getDbId(agentConfigId) {
|
|
5111
5428
|
const agent = await getAgent(agentConfigId);
|
|
@@ -5130,6 +5447,41 @@ async function findProduct(agentConfigId, productId) {
|
|
|
5130
5447
|
}
|
|
5131
5448
|
}
|
|
5132
5449
|
}
|
|
5450
|
+
async function loadProductSupplement(agentConfigId, productId) {
|
|
5451
|
+
const [dbId, matched] = await Promise.all([
|
|
5452
|
+
getDbId(agentConfigId),
|
|
5453
|
+
findProduct(agentConfigId, productId)
|
|
5454
|
+
]);
|
|
5455
|
+
const detail = await getProductDetail({
|
|
5456
|
+
agentConfigId,
|
|
5457
|
+
dbId,
|
|
5458
|
+
productId
|
|
5459
|
+
});
|
|
5460
|
+
const items = parseSupplementField(detail.\u8865\u5145\u77E5\u8BC6);
|
|
5461
|
+
return {
|
|
5462
|
+
dbId,
|
|
5463
|
+
dataId: detail.id,
|
|
5464
|
+
knowledgeId: matched.knowledge_id,
|
|
5465
|
+
items,
|
|
5466
|
+
raw: detail.\u8865\u5145\u77E5\u8BC6
|
|
5467
|
+
};
|
|
5468
|
+
}
|
|
5469
|
+
async function writeProductSupplement(ctx, items) {
|
|
5470
|
+
return updateProduct({
|
|
5471
|
+
dataId: ctx.dataId,
|
|
5472
|
+
knowledgeId: ctx.knowledgeId,
|
|
5473
|
+
dbId: ctx.dbId,
|
|
5474
|
+
updateDict: { \u8865\u5145\u77E5\u8BC6: serializeSupplement(items) }
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5477
|
+
async function resolveMediaList(values, type, dryRun) {
|
|
5478
|
+
if (!values || values.length === 0) return [];
|
|
5479
|
+
const urls = [];
|
|
5480
|
+
for (const value of values) {
|
|
5481
|
+
urls.push(await resolveMediaInput({ value, type, dryRun }));
|
|
5482
|
+
}
|
|
5483
|
+
return urls;
|
|
5484
|
+
}
|
|
5133
5485
|
function registerProductCommand(program2) {
|
|
5134
5486
|
const product = program2.command("product").description(
|
|
5135
5487
|
"\u5546\u54C1\u77E5\u8BC6\u5E93\u7BA1\u7406 \u2014\u2014 Agent \u5173\u8054\u7684\u5546\u54C1\u6570\u636E\uFF08\u5356\u70B9\u3001\u8865\u5145\u77E5\u8BC6\u3001SKU\u3001\u53C2\u6570\u3001\u56FE\u7247\u8BC6\u522B\u7ED3\u679C\u7B49\uFF09\u3002Agent \u56DE\u7B54\u5546\u54C1\u76F8\u5173\u95EE\u9898\u65F6\u4F9D\u8D56\u6B64\u6570\u636E\u6E90"
|
|
@@ -5315,8 +5667,117 @@ function registerProductCommand(program2) {
|
|
|
5315
5667
|
process.exit(toExitCode(err));
|
|
5316
5668
|
}
|
|
5317
5669
|
});
|
|
5670
|
+
registerProductSupplementCommand(product, program2);
|
|
5318
5671
|
registerProductRelatedKnowledgeCommand(product, program2);
|
|
5319
5672
|
}
|
|
5673
|
+
function registerProductSupplementCommand(product, program2) {
|
|
5674
|
+
const supplement = product.command("supplement").description(
|
|
5675
|
+
"\u5546\u54C1\u8865\u5145\u77E5\u8BC6\uFF08\u95EE\u7B54 + \u56FE\u7247/\u89C6\u9891\uFF09\u7EF4\u62A4\u3002\u5199\u5165\u91C7\u7528 read-modify-write\uFF0C\u53EA\u5408\u5E76\u76EE\u6807\u6761\u76EE\uFF0C\u4E0D\u8986\u76D6\u5176\u5B83\u8865\u5145\u77E5\u8BC6"
|
|
5676
|
+
);
|
|
5677
|
+
supplement.command("list").description("\u5217\u51FA\u5546\u54C1\u5F53\u524D\u8865\u5145\u77E5\u8BC6\u6761\u76EE\uFF08\u542B\u6BCF\u6761\u7684 question / answer / images / videos\uFF09").requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption("--product-id <product_id>", "\u5546\u54C1 ID\uFF08\u4ECE product list \u83B7\u53D6\uFF09").action(async (opts) => {
|
|
5678
|
+
try {
|
|
5679
|
+
const ctx = await loadProductSupplement(opts.agent, opts.productId);
|
|
5680
|
+
formatOutput(
|
|
5681
|
+
{
|
|
5682
|
+
success: true,
|
|
5683
|
+
data: {
|
|
5684
|
+
product_id: opts.productId,
|
|
5685
|
+
count: ctx.items.length,
|
|
5686
|
+
\u8865\u5145\u77E5\u8BC6: ctx.items
|
|
5687
|
+
}
|
|
5688
|
+
},
|
|
5689
|
+
program2.opts().table
|
|
5690
|
+
);
|
|
5691
|
+
} catch (err) {
|
|
5692
|
+
reportCaughtError(err);
|
|
5693
|
+
process.exit(toExitCode(err));
|
|
5694
|
+
}
|
|
5695
|
+
});
|
|
5696
|
+
supplement.command("add").description(
|
|
5697
|
+
"\u65B0\u589E\u4E00\u6761\u8865\u5145\u77E5\u8BC6\uFF08Q/A + \u53EF\u9009\u56FE\u7247/\u89C6\u9891\uFF09\u3002--image / --video \u652F\u6301\u672C\u5730\u6587\u4EF6\uFF08\u81EA\u52A8\u4E0A\u4F20\u516C\u5F00 OSS\uFF09\u6216\u5DF2\u6709 URL\uFF08\u76F4\u5199\uFF09\u3002read-modify-write\uFF1A\u4FDD\u7559\u5168\u90E8\u65E2\u6709\u6761\u76EE\uFF0C\u4EC5\u8FFD\u52A0\u65B0\u6761\u76EE"
|
|
5698
|
+
).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption("--product-id <product_id>", "\u5546\u54C1 ID\uFF08\u4ECE product list \u83B7\u53D6\uFF09").requiredOption("--question <text>", "\u5E38\u95EE\u95EE\u9898").requiredOption("--answer <text>", "\u56DE\u7B54\u5185\u5BB9").option("--image <path-or-url...>", "\u56FE\u7247\u672C\u5730\u8DEF\u5F84\u6216 URL\uFF08\u53EF\u591A\u4E2A\uFF09").option("--video <path-or-url...>", "\u89C6\u9891\u672C\u5730\u8DEF\u5F84\u6216 URL\uFF08\u53EF\u591A\u4E2A\uFF09").option("--dry-run", "\u53EA\u8F93\u51FA\u6700\u7EC8\u5C06\u5199\u5165\u7684 JSON\uFF0C\u4E0D\u5B9E\u9645\u4E0A\u4F20/\u66F4\u65B0").option("--backup <path>", "\u5199\u5165\u524D\u5C06\u66F4\u65B0\u524D\u8865\u5145\u77E5\u8BC6\u5FEB\u7167\u4FDD\u5B58\u5230\u6307\u5B9A\u6587\u4EF6").action(async (opts) => {
|
|
5699
|
+
try {
|
|
5700
|
+
const ctx = await loadProductSupplement(opts.agent, opts.productId);
|
|
5701
|
+
maybeWriteBackup(opts.backup, ctx.items);
|
|
5702
|
+
const images = await resolveMediaList(opts.image, "image", Boolean(opts.dryRun));
|
|
5703
|
+
const videos = await resolveMediaList(opts.video, "video", Boolean(opts.dryRun));
|
|
5704
|
+
const nextItems = addSupplementItem(ctx.items, {
|
|
5705
|
+
question: opts.question,
|
|
5706
|
+
answer: opts.answer,
|
|
5707
|
+
images,
|
|
5708
|
+
videos
|
|
5709
|
+
});
|
|
5710
|
+
if (opts.dryRun) {
|
|
5711
|
+
formatOutput(
|
|
5712
|
+
{
|
|
5713
|
+
success: true,
|
|
5714
|
+
data: {
|
|
5715
|
+
dryRun: true,
|
|
5716
|
+
before: ctx.items,
|
|
5717
|
+
after: nextItems,
|
|
5718
|
+
update_dict: { \u8865\u5145\u77E5\u8BC6: serializeSupplement(nextItems) }
|
|
5719
|
+
}
|
|
5720
|
+
},
|
|
5721
|
+
program2.opts().table
|
|
5722
|
+
);
|
|
5723
|
+
return;
|
|
5724
|
+
}
|
|
5725
|
+
const data = await writeProductSupplement(ctx, nextItems);
|
|
5726
|
+
formatOutput(
|
|
5727
|
+
{ success: true, data: { updated: true, count: nextItems.length, result: data } },
|
|
5728
|
+
program2.opts().table
|
|
5729
|
+
);
|
|
5730
|
+
} catch (err) {
|
|
5731
|
+
reportCaughtError(err);
|
|
5732
|
+
process.exit(toExitCode(err));
|
|
5733
|
+
}
|
|
5734
|
+
});
|
|
5735
|
+
supplement.command("append-media").description(
|
|
5736
|
+
"\u5411\u6307\u5B9A\u4E0B\u6807\u7684\u5DF2\u6709\u8865\u5145\u77E5\u8BC6\u6761\u76EE\u8FFD\u52A0\u56FE\u7247/\u89C6\u9891\u3002\u539F\u6709 question / answer / images / videos \u53CA\u5176\u5B83\u6761\u76EE\u5747\u4E0D\u4E22\u5931"
|
|
5737
|
+
).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption("--product-id <product_id>", "\u5546\u54C1 ID\uFF08\u4ECE product list \u83B7\u53D6\uFF09").requiredOption("--index <number>", "\u76EE\u6807\u8865\u5145\u77E5\u8BC6\u6761\u76EE\u4E0B\u6807\uFF08\u4ECE 0 \u5F00\u59CB\uFF0C\u89C1 supplement list\uFF09").option("--image <path-or-url...>", "\u56FE\u7247\u672C\u5730\u8DEF\u5F84\u6216 URL\uFF08\u53EF\u591A\u4E2A\uFF09").option("--video <path-or-url...>", "\u89C6\u9891\u672C\u5730\u8DEF\u5F84\u6216 URL\uFF08\u53EF\u591A\u4E2A\uFF09").option("--dry-run", "\u53EA\u8F93\u51FA\u6700\u7EC8\u5C06\u5199\u5165\u7684 JSON\uFF0C\u4E0D\u5B9E\u9645\u4E0A\u4F20/\u66F4\u65B0").option("--backup <path>", "\u5199\u5165\u524D\u5C06\u66F4\u65B0\u524D\u8865\u5145\u77E5\u8BC6\u5FEB\u7167\u4FDD\u5B58\u5230\u6307\u5B9A\u6587\u4EF6").action(async (opts) => {
|
|
5738
|
+
try {
|
|
5739
|
+
const index = Number(opts.index);
|
|
5740
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
5741
|
+
throw new Error(`--index \u5FC5\u987B\u4E3A\u975E\u8D1F\u6574\u6570\uFF0C\u6536\u5230: ${opts.index}`);
|
|
5742
|
+
}
|
|
5743
|
+
if ((!opts.image || opts.image.length === 0) && (!opts.video || opts.video.length === 0)) {
|
|
5744
|
+
throw new Error("\u8BF7\u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A --image \u6216 --video");
|
|
5745
|
+
}
|
|
5746
|
+
const ctx = await loadProductSupplement(opts.agent, opts.productId);
|
|
5747
|
+
maybeWriteBackup(opts.backup, ctx.items);
|
|
5748
|
+
const images = await resolveMediaList(opts.image, "image", Boolean(opts.dryRun));
|
|
5749
|
+
const videos = await resolveMediaList(opts.video, "video", Boolean(opts.dryRun));
|
|
5750
|
+
const nextItems = appendMediaToItem(ctx.items, { index, images, videos });
|
|
5751
|
+
if (opts.dryRun) {
|
|
5752
|
+
formatOutput(
|
|
5753
|
+
{
|
|
5754
|
+
success: true,
|
|
5755
|
+
data: {
|
|
5756
|
+
dryRun: true,
|
|
5757
|
+
before: ctx.items,
|
|
5758
|
+
after: nextItems,
|
|
5759
|
+
update_dict: { \u8865\u5145\u77E5\u8BC6: serializeSupplement(nextItems) }
|
|
5760
|
+
}
|
|
5761
|
+
},
|
|
5762
|
+
program2.opts().table
|
|
5763
|
+
);
|
|
5764
|
+
return;
|
|
5765
|
+
}
|
|
5766
|
+
const data = await writeProductSupplement(ctx, nextItems);
|
|
5767
|
+
formatOutput(
|
|
5768
|
+
{ success: true, data: { updated: true, index, result: data } },
|
|
5769
|
+
program2.opts().table
|
|
5770
|
+
);
|
|
5771
|
+
} catch (err) {
|
|
5772
|
+
reportCaughtError(err);
|
|
5773
|
+
process.exit(toExitCode(err));
|
|
5774
|
+
}
|
|
5775
|
+
});
|
|
5776
|
+
}
|
|
5777
|
+
function maybeWriteBackup(backupPath, items) {
|
|
5778
|
+
if (!backupPath) return;
|
|
5779
|
+
fs10.writeFileSync(backupPath, JSON.stringify(items, null, 2), "utf-8");
|
|
5780
|
+
}
|
|
5320
5781
|
|
|
5321
5782
|
// src/client/repair-record-api.ts
|
|
5322
5783
|
var PATH_PREFIX4 = "/api/repair-records";
|
|
@@ -5534,7 +5995,7 @@ function registerSACommand(program2) {
|
|
|
5534
5995
|
}
|
|
5535
5996
|
|
|
5536
5997
|
// src/commands/special-project.ts
|
|
5537
|
-
import
|
|
5998
|
+
import fs11 from "fs";
|
|
5538
5999
|
|
|
5539
6000
|
// src/client/special-project-api.ts
|
|
5540
6001
|
async function createSpecialProject(input) {
|
|
@@ -5577,7 +6038,7 @@ async function upsertSpecialProjects(items, apply) {
|
|
|
5577
6038
|
// src/commands/special-project.ts
|
|
5578
6039
|
function readJsonArg(value) {
|
|
5579
6040
|
if (value.startsWith("@")) {
|
|
5580
|
-
return
|
|
6041
|
+
return fs11.readFileSync(value.slice(1), "utf-8");
|
|
5581
6042
|
}
|
|
5582
6043
|
return value;
|
|
5583
6044
|
}
|
|
@@ -5654,11 +6115,11 @@ function registerSpecialProjectCommand(program2) {
|
|
|
5654
6115
|
}
|
|
5655
6116
|
|
|
5656
6117
|
// src/commands/testset.ts
|
|
5657
|
-
import
|
|
6118
|
+
import fs13 from "fs";
|
|
5658
6119
|
|
|
5659
6120
|
// src/client/testset-api.ts
|
|
5660
|
-
import
|
|
5661
|
-
import
|
|
6121
|
+
import fs12 from "fs";
|
|
6122
|
+
import path6 from "path";
|
|
5662
6123
|
function unwrapPaginated(raw, fallbackPageSize) {
|
|
5663
6124
|
return {
|
|
5664
6125
|
items: Array.isArray(raw?.data) ? raw?.data : [],
|
|
@@ -5686,14 +6147,14 @@ async function listTestSets(query) {
|
|
|
5686
6147
|
}
|
|
5687
6148
|
async function createTestSetFromFile(input) {
|
|
5688
6149
|
const request = createRequest();
|
|
5689
|
-
const fileBytes =
|
|
6150
|
+
const fileBytes = fs12.readFileSync(input.filePath);
|
|
5690
6151
|
const formData = new FormData();
|
|
5691
6152
|
formData.append(
|
|
5692
6153
|
"file",
|
|
5693
6154
|
new Blob([fileBytes], {
|
|
5694
6155
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
5695
6156
|
}),
|
|
5696
|
-
|
|
6157
|
+
path6.basename(input.filePath)
|
|
5697
6158
|
);
|
|
5698
6159
|
return request(getCustomerServiceUrl(), "/v1/test_sets/file/upload", {
|
|
5699
6160
|
method: "POST",
|
|
@@ -6190,7 +6651,7 @@ function readPromptInput(value) {
|
|
|
6190
6651
|
if (!filePath) {
|
|
6191
6652
|
throw new Error("File path cannot be empty after @");
|
|
6192
6653
|
}
|
|
6193
|
-
return
|
|
6654
|
+
return fs13.readFileSync(filePath, "utf-8");
|
|
6194
6655
|
}
|
|
6195
6656
|
return value;
|
|
6196
6657
|
}
|
|
@@ -6225,7 +6686,7 @@ function registerTestsetCommand(program2) {
|
|
|
6225
6686
|
});
|
|
6226
6687
|
testset.command("create").description("\u901A\u8FC7 xlsx \u6587\u4EF6\u521B\u5EFA\u6D4B\u8BD5\u96C6").requiredOption("--customer-agent-config-id <id>", "Agent \u914D\u7F6E ID\uFF08\u5FC5\u586B\uFF0C\u4E0E\u540E\u7AEF\u5951\u7EA6\u5BF9\u9F50\uFF09").requiredOption("--file <path>", "\u6D4B\u8BD5\u96C6 xlsx \u6587\u4EF6\u8DEF\u5F84").option("--name <text>", "\u6D4B\u8BD5\u96C6\u540D\u79F0\uFF1B\u4E0D\u4F20\u5219\u540E\u7AEF\u53D6\u6587\u4EF6\u540D").option("--description <text>", "\u6D4B\u8BD5\u96C6\u63CF\u8FF0").option("--use-type <text>", "\u6587\u4EF6\u7528\u9014\u6807\u8BC6").action(async (opts) => {
|
|
6227
6688
|
try {
|
|
6228
|
-
if (!
|
|
6689
|
+
if (!fs13.existsSync(opts.file)) {
|
|
6229
6690
|
outputError(1, `\u6587\u4EF6\u4E0D\u5B58\u5728: ${opts.file}`);
|
|
6230
6691
|
process.exit(1);
|
|
6231
6692
|
}
|
|
@@ -6414,6 +6875,7 @@ registerAgentCommand(program);
|
|
|
6414
6875
|
registerOpsAgentCommand(program);
|
|
6415
6876
|
registerSACommand(program);
|
|
6416
6877
|
registerProductCommand(program);
|
|
6878
|
+
registerMediaCommand(program);
|
|
6417
6879
|
registerKnowledgeCommand(program);
|
|
6418
6880
|
registerActivityCommand(program);
|
|
6419
6881
|
registerIssueCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bty/customer-service-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "AI Customer Service CLI - Agent friendly",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/bin.js",
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
"CHANGELOG.md"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"ali-oss": "^6.22.0",
|
|
15
16
|
"cli-table3": "^0.6.0",
|
|
16
17
|
"commander": "^14.0.0",
|
|
17
18
|
"crypto-js": "^4.1.1"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
21
|
+
"@types/ali-oss": "^6.16.11",
|
|
20
22
|
"@types/crypto-js": "^4.1.1",
|
|
21
23
|
"@types/node": "^22.10.0",
|
|
22
24
|
"tsup": "^8.0.0",
|