@bty/customer-service-cli 0.4.3 → 0.4.5

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.
Files changed (3) hide show
  1. package/README.md +15 -2
  2. package/dist/bin.js +83 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -222,15 +222,18 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
222
222
  | `product get --agent <id> --product-id <商品ID>` | 获取商品详情(自动获取 db_id) |
223
223
  | `product update --agent <id> --product-id <商品ID> --update <json\|@file>` | 更新商品信息 |
224
224
  | `product update-sku --agent <id> (--sku <SKU名称> \| --sku-id <id>) --update <json\|@file>` | 更新 SKU 信息(`--sku-id` 优先于 `--sku`) |
225
+ | `product add --agent <id> --data <json\|@file> [--source <type>] [--no-identify]` | 直接传入结构化商品数据(JSON 数组)异步学习(manual / identify 默认开启) |
225
226
  | `product learn --agent <id> --url <URL...> [--source <type>] [--no-identify]` | 通过商品 URL 异步学习(manual / identify 默认开启) |
226
- | `product sync-taobao --agent <id> [--skip-hash-check]` | 触发淘宝店铺商品全量同步 |
227
+ | `product sync-taobao --agent <id> [--skip-hash-check] [--sync-type <full\|incremental>]` | 触发淘宝店铺商品同步 |
227
228
  | `product sync-taobao-item --agent <id> --product-id <商品ID>` | 触发单个淘宝商品同步 |
228
229
 
229
230
  `product update` 支持更新的字段包括:`卖点`、`补充知识`、`tag`、`参数`、`轮播图识别结果`、`商品详情页识别结果` 等。`--update` 接受 JSON 字符串或 `@文件路径`。
230
231
 
232
+ `product add` 与 `product learn` 走同一个上传接口(`/v1/knowledge/products/upload`),区别在于载荷:`add` 传结构化商品数据 `product_items`(已抓好的商品详情 JSON 数组),`learn` 传 `product_urls`(让后端去抓)。`--data` 接受 JSON 字符串或 `@文件路径`;传单个对象会自动包成数组,传 JSON 基本类型(数字 / 字符串等)会直接报错;`--source` 默认 `manual`,`--no-identify` 关闭自动识别。
233
+
231
234
  `product learn` 提交商品详情页 URL 进入异步学习队列(`/v1/knowledge/products/upload`),返回 `flow_id` 和 `knowledge_ids`。`--url` 可多值,用空格分隔;`--source` 默认 `manual`;传 `--no-identify` 可关闭自动识别。
232
235
 
233
- `product sync-taobao` / `product sync-taobao-item` 调用 customer-servhub-api 的授权店铺同步接口,要求 `--agent` 已唯一绑定一个淘宝授权店铺。前者触发店铺商品全量同步,后者按 `product_id` 补同步单个淘宝商品。
236
+ `product sync-taobao` / `product sync-taobao-item` 调用 customer-servhub-api 的授权店铺同步接口,要求 `--agent` 已唯一绑定一个淘宝授权店铺。前者支持 `--sync-type full|incremental`,默认 `full`;后者按 `product_id` 补同步单个淘宝商品。
234
237
 
235
238
  注意:这两个命令仅对已开通“高级工具”的店铺 Agent 生效。未开通高级工具时,即使 `--agent` 已绑定淘宝授权店铺,请求也可能在服务端失败。
236
239
 
@@ -281,10 +284,13 @@ cs-cli product update-sku --agent <id> --sku-id 6072595054179 --update '{"补充
281
284
  | 命令 | 说明 |
282
285
  | ---------------------------------------------------------------------------- | --------- |
283
286
  | `faq list --agent <id>` | 列出 FAQ 文件 |
287
+ | `faq content --agent <id> --file <文件名> [--keyword <词>] [--page-size <n>] [--next-group-id <id>]` | 列出文件内答案组(含 `answer_group_id` / 问题 / 答案) |
284
288
  | `faq add --agent <id> --file <文件名> --questions <问题1,问题2> --answers <答案=xxx>` | 添加 FAQ 内容 |
285
289
  | `faq update --agent <id> --file <文件名> --group-id <id> [--questions <...>] [--answers <...>] [--delete-chunks <id1,id2>]` | 更新已有 FAQ 答案组(`--questions` / `--answers` / `--delete-chunks` 至少一个) |
286
290
  | `faq delete --agent <id> --file <文件名> --group-id <id>` | 删除 FAQ 答案组 |
287
291
 
292
+ `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` 游标续翻。
293
+
288
294
  `faq update` 按 `answer_group_id` 定位目标组,POST 同一个 `faq_contents/save` 接口完成字段替换、问题替换与 chunk 级删除。`faq delete` 走 `DELETE /faq_contents/{group_id}` 整组删除。
289
295
 
290
296
  ### 扩展知识库 (`knowledge`)
@@ -541,6 +547,9 @@ cs-cli product update-sku --agent <id> --sku-id 6072595054179 --update '{"补充
541
547
  # 添加 FAQ
542
548
  cs-cli faq add --agent <id> --file "常见问题" --questions "怎么退款,如何退款" --answers "答案=请联系客服处理退款"
543
549
 
550
+ # 列出文件内答案组(拿 answer_group_id,供下面 update/delete 用)
551
+ cs-cli faq content --agent <id> --file "常见问题" --keyword 退款
552
+
544
553
  # 更新已有 FAQ 组答案
545
554
  cs-cli faq update --agent <id> --file "常见问题" --group-id 2 --answers "答案=请通过订单页面申请退款"
546
555
 
@@ -577,6 +586,10 @@ cs-cli product learn --agent <id> --url https://detail.tmall.com/item.htm?id=997
577
586
  # 多个 URL 一次提交,且跳过自动识别
578
587
  cs-cli product learn --agent <id> --url https://a.example/1 https://a.example/2 --no-identify
579
588
 
589
+ # 直接传入结构化商品数据(JSON 数组),或从文件读取
590
+ cs-cli product add --agent <id> --data '[{"title":"商品A","price":99}]'
591
+ cs-cli product add --agent <id> --data @products.json --source batch --no-identify
592
+
580
593
  # 通过上下文内容模糊搜索对话记录(自动使用当前工作空间,查询窗口最多 3 天)
581
594
  cs-cli conversation context-search --query "退款问题" --start 2026-03-22T00:00:00 --end 2026-03-22T23:59:59
582
595
 
package/dist/bin.js CHANGED
@@ -1513,6 +1513,22 @@ async function saveFaqContent(opts) {
1513
1513
  }
1514
1514
  );
1515
1515
  }
1516
+ async function listFaqContents(opts) {
1517
+ const request = createRequest();
1518
+ return request(
1519
+ getAiApiUrl(),
1520
+ `/mebsuta/api/v1/dataset/${opts.knowledgeId}/faq_contents`,
1521
+ {
1522
+ method: "GET",
1523
+ query: {
1524
+ page_size: opts.pageSize ?? 20,
1525
+ search_words: opts.searchWords,
1526
+ next_group_id: opts.nextGroupId,
1527
+ need_total: opts.needTotal ?? true
1528
+ }
1529
+ }
1530
+ );
1531
+ }
1516
1532
  async function deleteFaqGroup(opts) {
1517
1533
  const request = createRequest();
1518
1534
  return request(
@@ -1566,6 +1582,27 @@ function registerFaqCommand(program2) {
1566
1582
  process.exit(toExitCode(err));
1567
1583
  }
1568
1584
  });
1585
+ faq.command("content").description(
1586
+ "\u5217\u51FA\u6307\u5B9A FAQ \u6587\u4EF6\u5185\u7684\u7B54\u6848\u7EC4\uFF08\u542B answer_group_id / \u95EE\u9898 / \u7B54\u6848\uFF09\u3002\u7528\u4E8E\u4E3A faq update / faq delete \u627E\u5230\u76EE\u6807 group-id"
1587
+ ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption("--file <name>", "FAQ \u6587\u4EF6\u540D\uFF08\u9700\u7CBE\u786E\u5339\u914D faq list \u8FD4\u56DE\u7684 file_name\uFF09").option("--keyword <text>", "\u6309\u95EE\u9898/\u7B54\u6848\u5173\u952E\u8BCD\u641C\u7D22").option("--page-size <number>", "\u6BCF\u9875\u6570\u91CF", "20").option("--next-group-id <id>", "\u6E38\u6807\uFF1A\u4ECE\u8BE5 answer_group_id \u4E4B\u540E\u7EE7\u7EED\u7FFB\u9875").action(async (opts) => {
1588
+ try {
1589
+ const matched = await findFaqFile(opts.agent, opts.file, 100);
1590
+ if (!matched) {
1591
+ outputError(1, `FAQ file not found: ${opts.file}`);
1592
+ process.exit(1);
1593
+ }
1594
+ const data = await listFaqContents({
1595
+ knowledgeId: matched.knowledge_id,
1596
+ pageSize: Number(opts.pageSize),
1597
+ searchWords: opts.keyword,
1598
+ nextGroupId: opts.nextGroupId ? Number(opts.nextGroupId) : void 0
1599
+ });
1600
+ formatOutput({ success: true, data }, program2.opts().table);
1601
+ } catch (err) {
1602
+ reportCaughtError(err);
1603
+ process.exit(toExitCode(err));
1604
+ }
1605
+ });
1569
1606
  faq.command("add").description("\u5411\u6307\u5B9A FAQ \u6587\u4EF6\u6DFB\u52A0\u95EE\u7B54\u6761\u76EE\u3002\u5148\u7528 faq list \u83B7\u53D6\u6587\u4EF6\u540D").requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption("--file <name>", "FAQ \u6587\u4EF6\u540D\uFF08\u9700\u7CBE\u786E\u5339\u914D faq list \u8FD4\u56DE\u7684 file_name\uFF09").requiredOption(
1570
1607
  "--questions <items>",
1571
1608
  '\u76F8\u4F3C\u95EE\u9898\u5217\u8868\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u5982 "\u600E\u4E48\u9000\u6B3E,\u5982\u4F55\u9000\u6B3E,\u9000\u6B3E\u6D41\u7A0B"\uFF09'
@@ -2832,6 +2869,18 @@ async function uploadProductByUrl(opts) {
2832
2869
  }
2833
2870
  });
2834
2871
  }
2872
+ async function uploadProductData(opts) {
2873
+ const request = createRequest();
2874
+ return request(getCustomerServiceUrl(), "/v1/knowledge/products/upload", {
2875
+ method: "POST",
2876
+ body: {
2877
+ customer_agent_config_id: opts.agentConfigId,
2878
+ source_type: opts.sourceType ?? "manual",
2879
+ identify: opts.identify ?? true,
2880
+ product_items: opts.productItems
2881
+ }
2882
+ });
2883
+ }
2835
2884
  async function updateSku(opts) {
2836
2885
  if (!opts.skuId && !opts.sku) {
2837
2886
  throw new Error("updateSku \u9700\u8981\u63D0\u4F9B sku \u6216 sku_id \u5176\u4E2D\u4E4B\u4E00");
@@ -2853,7 +2902,8 @@ async function syncTaobaoProducts(opts) {
2853
2902
  method: "POST",
2854
2903
  body: {
2855
2904
  customer_agent_config_id: opts.agentConfigId,
2856
- skip_hash_check: opts.skipHashCheck ?? false
2905
+ skip_hash_check: opts.skipHashCheck ?? false,
2906
+ sync_type: opts.syncType ?? "full"
2857
2907
  }
2858
2908
  });
2859
2909
  }
@@ -2976,13 +3026,42 @@ function registerProductCommand(program2) {
2976
3026
  process.exit(toExitCode(err));
2977
3027
  }
2978
3028
  });
3029
+ product.command("add").description(
3030
+ "\u76F4\u63A5\u4F20\u5165\u7ED3\u6784\u5316\u5546\u54C1\u6570\u636E\uFF08JSON \u6570\u7EC4\uFF09\u8BA9 Agent \u5B66\u4E60\uFF0C\u5BF9\u63A5 POST /v1/knowledge/products/upload \u7684 product_items\u3002\u9ED8\u8BA4 source_type=manual / identify=true\u3002--no-identify \u53EF\u5173\u95ED\u81EA\u52A8\u8BC6\u522B"
3031
+ ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption(
3032
+ "--data <json>",
3033
+ '\u5546\u54C1\u6570\u636E JSON \u6570\u7EC4\u6216 @\u6587\u4EF6\u8DEF\u5F84\u3002\u5355\u4E2A\u5BF9\u8C61\u4F1A\u88AB\u5305\u6210\u6570\u7EC4\u3002\u793A\u4F8B: [{"title":"xxx",...}]'
3034
+ ).option("--source <type>", "\u6765\u6E90\u7C7B\u578B\uFF08\u5982 manual / batch\uFF09", "manual").option("--no-identify", "\u5173\u95ED\u81EA\u52A8\u8BC6\u522B\u5546\u54C1\u4FE1\u606F\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09").action(async (opts) => {
3035
+ try {
3036
+ const parsed = parseDataOption(opts.data);
3037
+ let productItems;
3038
+ if (Array.isArray(parsed)) {
3039
+ productItems = parsed;
3040
+ } else if (parsed !== null && typeof parsed === "object") {
3041
+ productItems = [parsed];
3042
+ } else {
3043
+ throw new Error("\u5546\u54C1\u6570\u636E\u5FC5\u987B\u662F JSON \u5BF9\u8C61\u6216\u5BF9\u8C61\u6570\u7EC4");
3044
+ }
3045
+ const data = await uploadProductData({
3046
+ agentConfigId: opts.agent,
3047
+ productItems,
3048
+ identify: opts.identify,
3049
+ sourceType: opts.source
3050
+ });
3051
+ formatOutput({ success: true, data }, program2.opts().table);
3052
+ } catch (err) {
3053
+ reportCaughtError(err);
3054
+ process.exit(toExitCode(err));
3055
+ }
3056
+ });
2979
3057
  product.command("sync-taobao").description(
2980
- "\u89E6\u53D1\u6DD8\u5B9D\u5E97\u94FA\u5546\u54C1\u5168\u91CF\u540C\u6B65\u3002\u8C03\u7528 customer-servhub-api \u7684\u6388\u6743\u5E97\u94FA\u540C\u6B65\u63A5\u53E3\uFF0C\u6309 Agent \u914D\u7F6E\u627E\u5230\u552F\u4E00\u7ED1\u5B9A\u5E97\u94FA\u540E\u5F02\u6B65\u542F\u52A8\u540C\u6B65\u4EFB\u52A1"
2981
- ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u5FC5\u987B\u5DF2\u552F\u4E00\u7ED1\u5B9A\u4E00\u4E2A\u6DD8\u5B9D\u6388\u6743\u5E97\u94FA\uFF09").option("--skip-hash-check", "\u8DF3\u8FC7\u54C8\u5E0C\u6821\u9A8C\uFF0C\u5F3A\u5236\u91CD\u65B0\u540C\u6B65\u5546\u54C1").action(async (opts) => {
3058
+ "\u89E6\u53D1\u6DD8\u5B9D\u5E97\u94FA\u5546\u54C1\u540C\u6B65\u3002\u8C03\u7528 customer-servhub-api \u7684\u6388\u6743\u5E97\u94FA\u540C\u6B65\u63A5\u53E3\uFF0C\u6309 Agent \u914D\u7F6E\u627E\u5230\u552F\u4E00\u7ED1\u5B9A\u5E97\u94FA\u540E\u5F02\u6B65\u542F\u52A8\u540C\u6B65\u4EFB\u52A1"
3059
+ ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u5FC5\u987B\u5DF2\u552F\u4E00\u7ED1\u5B9A\u4E00\u4E2A\u6DD8\u5B9D\u6388\u6743\u5E97\u94FA\uFF09").option("--skip-hash-check", "\u8DF3\u8FC7\u54C8\u5E0C\u6821\u9A8C\uFF0C\u5F3A\u5236\u91CD\u65B0\u540C\u6B65\u5546\u54C1").option("--sync-type <type>", "\u540C\u6B65\u7C7B\u578B\uFF1Afull / incremental\uFF0C\u9ED8\u8BA4 full", "full").action(async (opts) => {
2982
3060
  try {
2983
3061
  const data = await syncTaobaoProducts({
2984
3062
  agentConfigId: opts.agent,
2985
- skipHashCheck: Boolean(opts.skipHashCheck)
3063
+ skipHashCheck: Boolean(opts.skipHashCheck),
3064
+ syncType: opts.syncType
2986
3065
  });
2987
3066
  formatOutput({ success: true, data }, program2.opts().table);
2988
3067
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/customer-service-cli",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "AI Customer Service CLI - Agent friendly",
5
5
  "type": "module",
6
6
  "main": "./dist/bin.js",