@bty/customer-service-cli 0.6.12 → 0.6.18

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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.18 (2026-09-21)
4
+
5
+ - `agent update` 只应传需要修改的字段:`--data` 的帮助文案不再宣传 `knowledge_base_config` / `database_config`,并明确提示不要 `agent get` 导出后整对象回传。这两个字段由服务端维护,`customer-servhub-api PUT /v1/customer-agent/{config_id}` 已改为拒绝更新(传入返回 400)。背景:整份回传会把知识库/数据库列表整份写回,并在 fork 时被放大成平台侧大量重复分区创建。
6
+
7
+ ## 0.6.17 (2026-09-10)
8
+
9
+ - 新增 `product image-identify`:按自定义提示词只重新识别某一张主图/详情图,寻址用 `--main <n>` / `--detail <n>`(从 1 开始,二选一),`--prompt "..."`(支持 `@文件`)覆盖本次提示词。默认只打印新旧对照,`--apply` 才写回识别文字(main→`轮播图识别结果`,detail→`商品详情页识别结果`);`--show-prompt` 只回当前生效提示词与解析出的图片 URL,不识别、不写回。对接 `customer-servhub-api POST /v1/knowledge/product/image/identify`。
10
+
11
+ ## 0.6.16 (2026-09-08)
12
+
13
+ - 新增 `product shelf`:批量设置商品上下架状态,写 BTY「店铺商品目录」的「商品状态」列(合法值仅 `上架` / `下架`),与商品详情页的上架/下架下拉框同一个 `POST /v1/knowledge/product/update` 接口。标为 `下架` 后商品推荐类工具不再选中该商品(服务端据此派生只读的 `is_downshelf`)。商品用 `--ids` / `--ids-file` 指定;批量前先整体翻一遍商品列表建 `product_id → knowledge_id` 索引(避免逐个商品重复分页);已是目标状态的商品跳过不写;每条写入后**回读校验**「商品状态」确实变更,防接口假成功。支持 `--concurrency`(默认 5)与 `--yes`(≥20 条时跳过二次确认),输出标准批量报告,部分失败退出码 2、全部失败 1。
14
+
15
+ ## 0.6.14 (2026-09-02)
16
+
17
+ - `agent fork` 新增 `--copy-authorized-shop-id`:与 `--clone-tools` 同用时,克隆的工具沿用来源工具的店铺授权 `authorized_shop_id`;默认关闭(清空),仅在用户明确要求全量复制工具时开启。
18
+
19
+ ## 0.6.13 (2026-08-31)
20
+
21
+ - 新增 `agent fork`,对接 `customer-servhub-api /v1/customer-agent/fork_agent`,复制 Agent 时必须显式指定 `--source-customer-config-id` 与 `--target-workspace-id`;同空间按当前用户的 workspace 权限校验,跨空间仅系统管理员可用。`--clone-tools` 用于复制来源 Agent 当前已绑定的全部工具(含系统工具和自定义工作流工具),多数场景建议开启。
22
+
3
23
  ## 0.6.12 (2026-08-31)
4
24
 
5
25
  - `channel-metric query` 新增 `--format json|markdown|table`(默认 json):markdown 输出适合贴文档/群消息,table 输出终端表格;json 保持原结构不变。
package/README.md CHANGED
@@ -204,6 +204,9 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
204
204
  | `agent list [--page N] [--page-size N]` | 列出 Agent 列表 |
205
205
  | `agent get <config_id>` | 获取 Agent 详情 |
206
206
  | `agent update <config_id> --data <json|@file>` | 更新 Agent 配置 |
207
+ | `agent fork --source-customer-config-id <id> --target-workspace-id <id> [选项]` | 复制 Agent;同空间校验当前用户权限,跨空间仅系统管理员可用 |
208
+
209
+ `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。
207
210
 
208
211
 
209
212
  ### 场景动作 (`sa`)
@@ -244,6 +247,7 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
244
247
  | `product list --agent <id> [--keyword <关键词>] [--status <状态>] [--tag <标签>]` | 列出商品 |
245
248
  | `product get --agent <id> --product-id <商品ID>` | 获取商品详情(自动获取 db_id) |
246
249
  | `product update --agent <id> --product-id <商品ID> --update <json\|@file>` | 更新商品信息 |
250
+ | `product shelf --agent <id> --status <上架\|下架> (--ids <id,id> \| --ids-file <path>) [--concurrency <n>] [--yes]` | 批量设置商品上下架状态(写「商品状态」列,写后回读校验) |
247
251
  | `product update-sku --agent <id> (--sku <SKU名称> \| --sku-id <id>) --update <json\|@file>` | 更新 SKU 信息(`--sku-id` 优先于 `--sku`) |
248
252
  | `product add --agent <id> --data <json\|@file> [--source <type>] [--no-identify]` | 直接传入结构化商品数据(JSON 数组)异步学习(manual / identify 默认开启) |
249
253
  | `product learn --agent <id> --url <URL...> [--source <type>] [--no-identify]` | 通过商品 URL 异步学习(manual / identify 默认开启) |
@@ -858,6 +862,18 @@ cs-cli sa versions --agent <id> --id 3
858
862
  # 使用 JSON 文件更新 Agent
859
863
  cs-cli agent update <config_id> --data @config.json
860
864
 
865
+ # 在明确指定来源 Agent 与目标空间后复制 Agent
866
+ cs-cli agent fork \
867
+ --source-customer-config-id <source_config_id> \
868
+ --target-workspace-id <target_workspace_id> \
869
+ --clone-tools
870
+
871
+ # 全量复制工具(含店铺授权)
872
+ cs-cli agent fork \
873
+ --source-customer-config-id <source_config_id> \
874
+ --target-workspace-id <target_workspace_id> \
875
+ --clone-tools --copy-authorized-shop-id
876
+
861
877
  # 更新商品卖点
862
878
  cs-cli product update --agent <id> --product-id 977691714398 --update '{"卖点":"新卖点"}'
863
879
 
package/dist/bin.js CHANGED
@@ -563,6 +563,13 @@ async function patchActivityEnabled(activityId, enabled) {
563
563
  body: { enabled }
564
564
  });
565
565
  }
566
+ async function patchActivityFeatured(activityId, isFeatured) {
567
+ const request = createRequest();
568
+ return request(getCustomerServiceUrl(), `${PATH_PREFIX}/${activityId}/featured`, {
569
+ method: "PATCH",
570
+ body: { is_featured: isFeatured }
571
+ });
572
+ }
566
573
  async function deleteActivity(activityId) {
567
574
  const request = createRequest();
568
575
  return request(getCustomerServiceUrl(), `${PATH_PREFIX}/${activityId}`, {
@@ -704,6 +711,19 @@ function registerActivityCommand(program2) {
704
711
  process.exit(toExitCode(err));
705
712
  }
706
713
  });
714
+ activity.command("set-featured").description(
715
+ "\u5207\u6362\u5355\u6761\u6D3B\u52A8\u4E3B\u63A8\u72B6\u6001\uFF08\u5217\u8868\u884C toggle \u7528\uFF0CPATCH /featured\uFF09\u3002\u4EC5\u5199 is_featured \u4E00\u4E2A\u5B57\u6BB5"
716
+ ).argument("<activity_id>", "\u6D3B\u52A8 ID").requiredOption("--featured <bool>", "true | false").action(async (activityIdRaw, opts) => {
717
+ try {
718
+ const activityId = parseActivityId(activityIdRaw);
719
+ const featured = parseEnabledFlag(opts.featured);
720
+ const data = await patchActivityFeatured(activityId, featured);
721
+ formatOutput({ success: true, data }, program2.opts().table);
722
+ } catch (err) {
723
+ reportCaughtError(err);
724
+ process.exit(toExitCode(err));
725
+ }
726
+ });
707
727
  activity.command("delete").description("\u8F6F\u5220\u6D3B\u52A8 + \u7EA7\u8054\u8F6F\u5220 activity_product \u5173\u8054").argument("<activity_id>", "\u6D3B\u52A8 ID").action(async (activityIdRaw) => {
708
728
  try {
709
729
  const activityId = parseActivityId(activityIdRaw);
@@ -753,6 +773,21 @@ async function updateAgent(configId, data) {
753
773
  body: data
754
774
  });
755
775
  }
776
+ async function forkAgent(opts) {
777
+ const request = createRequest();
778
+ return request(getCustomerServiceUrl(), "/v1/customer-agent/fork_agent", {
779
+ method: "POST",
780
+ body: {
781
+ source_customer_config_id: opts.sourceCustomerConfigId,
782
+ target_workspace_id: opts.targetWorkspaceId,
783
+ copy_product_data: opts.copyProductData ?? true,
784
+ copy_activity_data: opts.copyActivityData ?? true,
785
+ shop_id: opts.shopId,
786
+ clone_tools: opts.cloneTools ?? false,
787
+ copy_authorized_shop_id: opts.copyAuthorizedShopId ?? false
788
+ }
789
+ });
790
+ }
756
791
 
757
792
  // src/commands/agent.ts
758
793
  function registerAgentCommand(program2) {
@@ -785,10 +820,10 @@ function registerAgentCommand(program2) {
785
820
  }
786
821
  });
787
822
  agent.command("update").description(
788
- "\u66F4\u65B0 Agent \u914D\u7F6E\uFF08\u5982 customer_config\u3001forbidden_words \u7B49\uFF09\u3002\u5EFA\u8BAE\u5148 agent get \u5BFC\u51FA\u5F53\u524D\u914D\u7F6E\u518D\u4FEE\u6539"
823
+ "\u66F4\u65B0 Agent \u914D\u7F6E\uFF08\u5982 customer_config\u3001forbidden_words \u7B49\uFF09\u3002\u53EA\u4F20\u9700\u8981\u4FEE\u6539\u7684\u5B57\u6BB5\uFF0C\u4E0D\u8981\u6574\u5BF9\u8C61\u56DE\u4F20"
789
824
  ).argument("<config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption(
790
825
  "--data <json>",
791
- "JSON \u6570\u636E\u6216 @\u6587\u4EF6\u8DEF\u5F84\u3002\u53EF\u66F4\u65B0\u5B57\u6BB5\u5305\u62EC customer_config\u3001forbidden_words\u3001knowledge_base_config \u7B49"
826
+ "JSON \u6570\u636E\u6216 @\u6587\u4EF6\u8DEF\u5F84\u3002\u53EF\u66F4\u65B0\u5B57\u6BB5\u5305\u62EC customer_config\u3001forbidden_words \u7B49\uFF1Bdatabase_config / knowledge_base_config \u7531\u670D\u52A1\u7AEF\u7EF4\u62A4\uFF0CCLI \u62D2\u7EDD\u66F4\u65B0"
792
827
  ).action(async (configId, opts) => {
793
828
  try {
794
829
  const body = parseDataOption(opts.data);
@@ -799,6 +834,29 @@ function registerAgentCommand(program2) {
799
834
  process.exit(toExitCode(err));
800
835
  }
801
836
  });
837
+ agent.command("fork").description("\u590D\u5236 Agent\uFF1B\u540C\u7A7A\u95F4\u9700\u5F53\u524D\u7528\u6237\u62E5\u6709\u8BE5\u7A7A\u95F4\uFF0C\u8DE8\u7A7A\u95F4\u4EC5\u7CFB\u7EDF\u7BA1\u7406\u5458\u53EF\u7528").requiredOption("--source-customer-config-id <id>", "\u6765\u6E90 Agent \u914D\u7F6E ID\uFF08\u5FC5\u987B\u660E\u786E\u6307\u5B9A\uFF09").requiredOption("--target-workspace-id <id>", "\u76EE\u6807\u5DE5\u4F5C\u7A7A\u95F4 ID\uFF08\u5FC5\u987B\u660E\u786E\u6307\u5B9A\uFF09").option("--no-copy-product-data", "\u4E0D\u590D\u5236\u5546\u54C1\u6570\u636E").option("--no-copy-activity-data", "\u4E0D\u590D\u5236\u6D3B\u52A8\u6570\u636E").option("--shop-id <id>", "\u76EE\u6807\u5E97\u94FA ID\uFF1B\u4E0D\u4F20\u5219\u7531\u670D\u52A1\u7AEF\u81EA\u52A8\u751F\u6210").option(
838
+ "--clone-tools",
839
+ "\u590D\u5236\u6765\u6E90 Agent \u5F53\u524D\u7ED1\u5B9A\u7684\u5168\u90E8\u5DE5\u5177\uFF08\u542B\u7CFB\u7EDF\u5DE5\u5177\u548C\u81EA\u5B9A\u4E49\u5DE5\u4F5C\u6D41\u5DE5\u5177\uFF1B\u591A\u6570\u573A\u666F\u5EFA\u8BAE\u5F00\u542F\uFF09"
840
+ ).option(
841
+ "--copy-authorized-shop-id",
842
+ "\u590D\u5236\u5DE5\u5177\u65F6\u6CBF\u7528\u6765\u6E90\u5DE5\u5177\u7684\u5E97\u94FA\u6388\u6743 authorized_shop_id\uFF08\u9ED8\u8BA4\u6E05\u7A7A\uFF1B\u4EC5\u4E0E --clone-tools \u540C\u7528\u751F\u6548\uFF0C\u7528\u6237\u660E\u786E\u8981\u6C42\u5168\u91CF\u590D\u5236\u5DE5\u5177\u65F6\u624D\u5F00\u542F\uFF09"
843
+ ).action(async (opts) => {
844
+ try {
845
+ const data = await forkAgent({
846
+ sourceCustomerConfigId: opts.sourceCustomerConfigId,
847
+ targetWorkspaceId: opts.targetWorkspaceId,
848
+ copyProductData: opts.copyProductData,
849
+ copyActivityData: opts.copyActivityData,
850
+ shopId: opts.shopId,
851
+ cloneTools: opts.cloneTools,
852
+ copyAuthorizedShopId: opts.copyAuthorizedShopId
853
+ });
854
+ formatOutput({ success: true, data }, program2.opts().table);
855
+ } catch (err) {
856
+ reportCaughtError(err);
857
+ process.exit(toExitCode(err));
858
+ }
859
+ });
802
860
  }
803
861
 
804
862
  // src/client/auth-api.ts
@@ -1746,6 +1804,9 @@ function csvToRows(csv) {
1746
1804
  return obj;
1747
1805
  });
1748
1806
  }
1807
+ function toPublicOssDownloadUrl(url) {
1808
+ return url.replace("-internal.aliyuncs.com", ".aliyuncs.com");
1809
+ }
1749
1810
  async function exportShopData(opts) {
1750
1811
  const request = createRequest();
1751
1812
  const body = {
@@ -1759,10 +1820,11 @@ async function exportShopData(opts) {
1759
1820
  `${DASHBOARD_PREFIX}/export`,
1760
1821
  { method: "POST", body }
1761
1822
  );
1762
- const downloadUrl = data?.download_url;
1763
- if (!downloadUrl) {
1823
+ const rawDownloadUrl = data?.download_url;
1824
+ if (!rawDownloadUrl) {
1764
1825
  throw new APIError(1, "\u5BFC\u51FA\u63A5\u53E3\u672A\u8FD4\u56DE download_url");
1765
1826
  }
1827
+ const downloadUrl = toPublicOssDownloadUrl(rawDownloadUrl);
1766
1828
  const timeoutMs = getRuntimeRequestTimeoutMs() ?? 6e4;
1767
1829
  const resp = await fetch(downloadUrl, { signal: AbortSignal.timeout(timeoutMs) });
1768
1830
  if (!resp.ok) {
@@ -2193,6 +2255,24 @@ async function syncSingleTaobaoProduct(opts) {
2193
2255
  }
2194
2256
  });
2195
2257
  }
2258
+ async function identifyProductImage(opts) {
2259
+ const request = createRequest();
2260
+ return request(
2261
+ getCustomerServiceUrl(),
2262
+ "/v1/knowledge/product/image/identify",
2263
+ {
2264
+ method: "POST",
2265
+ body: {
2266
+ customer_agent_config_id: opts.agentConfigId,
2267
+ product_id: opts.productId,
2268
+ image_type: opts.imageType,
2269
+ image_index: opts.imageIndex,
2270
+ ...opts.prompt !== void 0 ? { prompt: opts.prompt } : {},
2271
+ dry_run: opts.dryRun ?? false
2272
+ }
2273
+ }
2274
+ );
2275
+ }
2196
2276
  async function identifyAllProducts(opts) {
2197
2277
  const request = createRequest();
2198
2278
  return request(getCustomerServiceUrl(), "/v1/knowledge/products/identify/all", {
@@ -5571,6 +5651,40 @@ function registerOpsAgentCommand(program2) {
5571
5651
  // src/commands/product.ts
5572
5652
  import fs10 from "fs";
5573
5653
 
5654
+ // src/lib/product-image.ts
5655
+ function applyImageRecognition(list, index0, value, imageUrl) {
5656
+ const next = Array.isArray(list) ? list.map((item) => ({ ...item })) : [];
5657
+ while (next.length <= index0) {
5658
+ const fillIdx = next.length;
5659
+ next.push({
5660
+ [`image_url${fillIdx + 1}`]: "",
5661
+ [`image_desc${fillIdx + 1}`]: ""
5662
+ });
5663
+ }
5664
+ const urlKey = `image_url${index0 + 1}`;
5665
+ const descKey = `image_desc${index0 + 1}`;
5666
+ const existingUrl = next[index0]?.[urlKey] || "";
5667
+ next[index0] = {
5668
+ [urlKey]: existingUrl || imageUrl || "",
5669
+ [descKey]: value
5670
+ };
5671
+ return next;
5672
+ }
5673
+
5674
+ // src/lib/product-shelf.ts
5675
+ var SHELF_STATUSES = ["\u4E0A\u67B6", "\u4E0B\u67B6"];
5676
+ function normalizeShelfStatus(raw) {
5677
+ return String(raw ?? "").trim() === "\u4E0B\u67B6" ? "\u4E0B\u67B6" : "\u4E0A\u67B6";
5678
+ }
5679
+ function parseShelfStatusOption(value) {
5680
+ const normalized = String(value ?? "").trim();
5681
+ const matched = SHELF_STATUSES.find((status) => status === normalized);
5682
+ if (!matched) {
5683
+ throw new Error(`--status \u53EA\u80FD\u662F ${SHELF_STATUSES.join(" \u6216 ")}\uFF0C\u6536\u5230: ${value}`);
5684
+ }
5685
+ return matched;
5686
+ }
5687
+
5574
5688
  // src/lib/supplement.ts
5575
5689
  function parseSupplementField(value) {
5576
5690
  let current = value;
@@ -5660,6 +5774,57 @@ async function findProduct(agentConfigId, productId) {
5660
5774
  }
5661
5775
  }
5662
5776
  }
5777
+ async function buildProductIndex(agentConfigId) {
5778
+ const pageSize = 100;
5779
+ const index = /* @__PURE__ */ new Map();
5780
+ for (let page = 1; ; page += 1) {
5781
+ const result = await listProducts({ agentConfigId, page, pageSize });
5782
+ const items = result.items ?? [];
5783
+ for (const item of items) {
5784
+ if (!index.has(item.product_id)) index.set(item.product_id, item.knowledge_id);
5785
+ }
5786
+ const fetched = page * pageSize;
5787
+ const exhausted = items.length < pageSize || typeof result.total === "number" && fetched >= result.total;
5788
+ if (exhausted) return index;
5789
+ }
5790
+ }
5791
+ function resolveImageTarget(opts) {
5792
+ const hasMain = opts.main !== void 0;
5793
+ const hasDetail = opts.detail !== void 0;
5794
+ if (hasMain === hasDetail) {
5795
+ throw new Error("\u8BF7\u4E14\u4EC5\u6307\u5B9A --main <n> \u6216 --detail <n> \u5176\u4E2D\u4E4B\u4E00");
5796
+ }
5797
+ const rawValue = hasMain ? opts.main : opts.detail;
5798
+ const imageIndex = Number.parseInt(rawValue, 10);
5799
+ if (!Number.isInteger(imageIndex) || imageIndex < 1) {
5800
+ throw new Error("\u56FE\u7247\u5E8F\u53F7\u5FC5\u987B\u662F\u4ECE 1 \u5F00\u59CB\u7684\u6B63\u6574\u6570");
5801
+ }
5802
+ return { imageType: hasMain ? "main" : "detail", imageIndex };
5803
+ }
5804
+ function readPromptText(value) {
5805
+ if (value.startsWith("@")) {
5806
+ const filePath = value.slice(1);
5807
+ if (!filePath) {
5808
+ throw new Error("@ \u540E\u7684\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
5809
+ }
5810
+ return fs10.readFileSync(filePath, "utf-8");
5811
+ }
5812
+ return value;
5813
+ }
5814
+ function parseRecognitionField(value) {
5815
+ if (Array.isArray(value)) {
5816
+ return value;
5817
+ }
5818
+ if (typeof value === "string" && value.trim()) {
5819
+ try {
5820
+ const parsed = JSON.parse(value);
5821
+ return Array.isArray(parsed) ? parsed : [];
5822
+ } catch {
5823
+ return [];
5824
+ }
5825
+ }
5826
+ return [];
5827
+ }
5663
5828
  async function loadProductSupplement(agentConfigId, productId) {
5664
5829
  const [dbId, matched] = await Promise.all([
5665
5830
  getDbId(agentConfigId),
@@ -5771,6 +5936,131 @@ function registerProductCommand(program2) {
5771
5936
  process.exit(toExitCode(err));
5772
5937
  }
5773
5938
  });
5939
+ product.command("image-identify").description(
5940
+ "\u5355\u56FE\u91CD\u65B0\u8BC6\u522B\uFF1A\u6309\u81EA\u5B9A\u4E49\u63D0\u793A\u8BCD\u53EA\u91CD\u8BC6\u522B\u67D0\u4E00\u5F20\u4E3B\u56FE/\u8BE6\u60C5\u56FE\u3002\u9ED8\u8BA4\u53EA\u6253\u5370\u65B0\u65E7\u5BF9\u7167\uFF0C--apply \u624D\u5199\u56DE\u8BC6\u522B\u6587\u5B57\u3002--show-prompt \u53EA\u770B\u5F53\u524D\u751F\u6548\u63D0\u793A\u8BCD"
5941
+ ).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").option("--main <n>", "\u7B2C\u51E0\u5F20\u4E3B\u56FE\uFF08\u4ECE 1 \u5F00\u59CB\uFF09\uFF0C\u4E0E --detail \u4E8C\u9009\u4E00").option("--detail <n>", "\u7B2C\u51E0\u5F20\u8BE6\u60C5\u56FE\uFF08\u4ECE 1 \u5F00\u59CB\uFF09\uFF0C\u4E0E --main \u4E8C\u9009\u4E00").option("--prompt <text|@file>", "\u81EA\u5B9A\u4E49\u63D0\u793A\u8BCD\uFF0C@\u524D\u7F00\u8BFB\u6587\u4EF6\uFF1B\u7559\u7A7A\u56DE\u9000\u5E73\u53F0\u9ED8\u8BA4").option("--show-prompt", "\u53EA\u6253\u5370\u5F53\u524D\u751F\u6548\u63D0\u793A\u8BCD\u4E0E\u89E3\u6790\u51FA\u7684\u56FE\u7247 URL\uFF0C\u4E0D\u8BC6\u522B\u3001\u4E0D\u5199\u56DE", false).option("--apply", "\u628A\u8BC6\u522B\u7ED3\u679C\u5199\u56DE\u5546\u54C1\u8BC6\u522B\u6587\u5B57\uFF08\u9ED8\u8BA4\u53EA\u6253\u5370\u5BF9\u7167\uFF09", false).action(async (opts) => {
5942
+ try {
5943
+ const { imageType, imageIndex } = resolveImageTarget(opts);
5944
+ const prompt = opts.prompt !== void 0 ? readPromptText(opts.prompt) : void 0;
5945
+ if (opts.showPrompt) {
5946
+ const data = await identifyProductImage({
5947
+ agentConfigId: opts.agent,
5948
+ productId: opts.productId,
5949
+ imageType,
5950
+ imageIndex,
5951
+ prompt,
5952
+ dryRun: true
5953
+ });
5954
+ formatOutput({ success: true, data }, program2.opts().table);
5955
+ return;
5956
+ }
5957
+ const result = await identifyProductImage({
5958
+ agentConfigId: opts.agent,
5959
+ productId: opts.productId,
5960
+ imageType,
5961
+ imageIndex,
5962
+ prompt
5963
+ });
5964
+ if (!opts.apply) {
5965
+ formatOutput({ success: true, data: { ...result, applied: false } }, program2.opts().table);
5966
+ return;
5967
+ }
5968
+ const [dbId, matched] = await Promise.all([
5969
+ getDbId(opts.agent),
5970
+ findProduct(opts.agent, opts.productId)
5971
+ ]);
5972
+ const detail = await getProductDetail({
5973
+ agentConfigId: opts.agent,
5974
+ dbId,
5975
+ productId: opts.productId
5976
+ });
5977
+ const field = imageType === "main" ? "\u8F6E\u64AD\u56FE\u8BC6\u522B\u7ED3\u679C" : "\u5546\u54C1\u8BE6\u60C5\u9875\u8BC6\u522B\u7ED3\u679C";
5978
+ const nextList = applyImageRecognition(
5979
+ parseRecognitionField(detail[field]),
5980
+ imageIndex - 1,
5981
+ result.identify_result,
5982
+ result.image_url
5983
+ );
5984
+ await updateProduct({
5985
+ dataId: detail.id,
5986
+ knowledgeId: matched.knowledge_id,
5987
+ dbId,
5988
+ updateDict: { [field]: JSON.stringify(nextList) }
5989
+ });
5990
+ formatOutput({ success: true, data: { ...result, applied: true, field } }, program2.opts().table);
5991
+ } catch (err) {
5992
+ reportCaughtError(err);
5993
+ process.exit(toExitCode(err));
5994
+ }
5995
+ });
5996
+ product.command("shelf").description(
5997
+ "\u6279\u91CF\u8BBE\u7F6E\u5546\u54C1\u4E0A\u4E0B\u67B6\u72B6\u6001\uFF08\u5199 BTY\u300C\u5546\u54C1\u72B6\u6001\u300D\u5217\uFF0C\u4E0E\u5546\u54C1\u8BE6\u60C5\u9875\u7684\u4E0A\u67B6/\u4E0B\u67B6\u4E0B\u62C9\u6846\u540C\u4E00\u63A5\u53E3\uFF09\u3002\u6807\u4E3A\u300C\u4E0B\u67B6\u300D\u540E\u5546\u54C1\u63A8\u8350\u7C7B\u5DE5\u5177\u4E0D\u518D\u9009\u8BE5\u5546\u54C1\u3002\u6BCF\u6761\u5199\u5B8C\u56DE\u8BFB\u6821\u9A8C\uFF0C\u5DF2\u662F\u76EE\u6807\u72B6\u6001\u7684\u8DF3\u8FC7"
5998
+ ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption("--status <status>", "\u76EE\u6807\u72B6\u6001: \u4E0A\u67B6 | \u4E0B\u67B6").option("--ids <ids>", "\u5546\u54C1 ID\uFF0C\u9017\u53F7\u5206\u9694\uFF08\u4E0E --ids-file \u4E8C\u9009\u4E00\uFF09").option("--ids-file <path>", "\u5546\u54C1 ID \u6587\u4EF6\uFF0C\u4E00\u884C\u4E00\u4E2A\uFF1B\u7A7A\u884C\u4E0E # \u5F00\u5934\u884C\u5FFD\u7565").option("--concurrency <n>", "\u5E76\u53D1\u6570\uFF08\u9ED8\u8BA4 5\uFF09", "5").option("--yes", "\u8DF3\u8FC7 20 \u6761\u4EE5\u4E0A\u7684\u4E8C\u6B21\u786E\u8BA4\uFF08CI / \u811A\u672C\uFF09").action(async (opts) => {
5999
+ let batchReport = null;
6000
+ try {
6001
+ const status = parseShelfStatusOption(opts.status);
6002
+ const ids = parseIdsInput({ ids: opts.ids, idsFile: opts.idsFile });
6003
+ const concurrency = Number(opts.concurrency);
6004
+ if (!Number.isFinite(concurrency) || concurrency < 1) {
6005
+ throw new Error(`--concurrency \u5FC5\u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u6536\u5230: ${opts.concurrency}`);
6006
+ }
6007
+ await confirmBatch({
6008
+ count: ids.length,
6009
+ action: `\u5C06\u5546\u54C1\u72B6\u6001\u6539\u4E3A\u300C${status}\u300D`,
6010
+ yes: opts.yes
6011
+ });
6012
+ const [dbId, index] = await Promise.all([
6013
+ getDbId(opts.agent),
6014
+ buildProductIndex(opts.agent)
6015
+ ]);
6016
+ batchReport = await runBatch({
6017
+ ids,
6018
+ concurrency,
6019
+ perItem: async (id) => {
6020
+ const knowledgeId = index.get(id);
6021
+ if (!knowledgeId) {
6022
+ throw new Error(`\u672A\u627E\u5230\u5546\u54C1 product_id=${id}\uFF08\u4E0D\u5728\u8BE5 Agent \u7684\u5546\u54C1\u5E93\u91CC\uFF09`);
6023
+ }
6024
+ const detail = await getProductDetail({
6025
+ agentConfigId: opts.agent,
6026
+ dbId,
6027
+ productId: id
6028
+ });
6029
+ if (normalizeShelfStatus(detail.\u5546\u54C1\u72B6\u6001) === status) {
6030
+ return { status: "skipped", id, reason: `\u5DF2\u662F\u300C${status}\u300D` };
6031
+ }
6032
+ await updateProduct({
6033
+ dataId: detail.id,
6034
+ knowledgeId,
6035
+ dbId,
6036
+ updateDict: { \u5546\u54C1\u72B6\u6001: status }
6037
+ });
6038
+ const after = await getProductDetail({
6039
+ agentConfigId: opts.agent,
6040
+ dbId,
6041
+ productId: id
6042
+ });
6043
+ const actual = normalizeShelfStatus(after.\u5546\u54C1\u72B6\u6001);
6044
+ if (actual !== status) {
6045
+ throw new Error(`\u56DE\u8BFB\u6821\u9A8C\u5931\u8D25\uFF1A\u671F\u671B\u300C${status}\u300D\uFF0C\u5B9E\u9645\u300C${actual}\u300D`);
6046
+ }
6047
+ return { status: "success", id };
6048
+ }
6049
+ });
6050
+ formatOutput(
6051
+ { success: batchReport.failed_count === 0, data: batchReport },
6052
+ program2.opts().table
6053
+ );
6054
+ } catch (err) {
6055
+ reportCaughtError(err);
6056
+ process.exit(toExitCode(err));
6057
+ return;
6058
+ }
6059
+ if (batchReport !== null) {
6060
+ const code = reportToExitCode(batchReport);
6061
+ if (code !== 0) process.exit(code);
6062
+ }
6063
+ });
5774
6064
  product.command("learn").description(
5775
6065
  "\u901A\u8FC7\u5546\u54C1 URL \u8BA9 Agent \u5B66\u4E60\u5546\u54C1\uFF08\u5F02\u6B65\u4E0A\u4F20\u8BC6\u522B\uFF09\uFF0C\u9ED8\u8BA4 source_type=manual / identify=true\u3002--no-identify \u53EF\u5173\u95ED\u81EA\u52A8\u8BC6\u522B"
5776
6066
  ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").requiredOption("--url <urls...>", "\u5546\u54C1 URL \u5217\u8868\uFF08\u53EF\u591A\u4E2A\uFF0C\u7A7A\u683C\u5206\u9694\uFF09").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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/customer-service-cli",
3
- "version": "0.6.12",
3
+ "version": "0.6.18",
4
4
  "description": "AI Customer Service CLI - Agent friendly",
5
5
  "type": "module",
6
6
  "main": "./dist/bin.js",