@bty/customer-service-cli 0.5.3 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.5 (2026-06-07)
4
+
5
+ - 修复 `debug ask --messages` 重放多轮对话时,历史消息时间戳晚于当前时间导致本次 Agent 回复插入历史消息中间、review 页面消息乱序的问题。
6
+ - 发送前将整段历史消息的时间戳统一平移,使最晚消息对齐当前时间;保留消息原有相对顺序与时间间隔,且不修改调用方传入的原始消息对象。
7
+
8
+ ## 0.5.4 (2026-06-03)
9
+
10
+ - `ops-agent` 新增 `conversations` 子命令:`ops-agent conversations [--agent-id <id>] [--customer-id <id>] [--page N] [--page-size N]`,对接 `GET /v1/ops-agent/conversations`,按当前 workspace 查询客服助手会话列表,返回 `workspace_id`、`customer_id`、`title`、`agent_id`、`created_at`、`updated_at`。
11
+ - 补充 `ops-agent` 命令注册测试与 API 客户端测试,覆盖会话列表查询命令注册、请求路径、workspace 透传与筛选参数。
12
+ - `product` 新增 `identify-all` 子命令:`product identify-all --agent <id>`,对接 `POST /v1/knowledge/products/identify/all`,为当前 Agent 一键提交所有待增强商品进入增强学习队列。
13
+ - 补充 `product` CLI 测试与 API 客户端测试,覆盖增强学习命令注册、请求路径与请求体。
14
+ - `testset` 新增 `create` 子命令:`testset create --customer-agent-config-id <id> --file <cases.xlsx> [--name <text>] [--description <text>] [--use-type <text>]`,对接 `POST /v1/test_sets/file/upload`,通过 xlsx 模板上传创建测试集。
15
+
3
16
  ## 0.5.3 (2026-05-28)
4
17
 
5
18
  - 新增 `activity` 主语,覆盖活动 V2(Issue #34)增删改查 + per-tenant 总开关。对接 `customer-servhub-api /v1/activity*`,契约详情参见 [`docs/devkit/specs/2026-05-28-activity-v2-api-reference.md`](../../docs/devkit/specs/2026-05-28-activity-v2-api-reference.md):
package/README.md CHANGED
@@ -224,6 +224,7 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
224
224
  | `product update-sku --agent <id> (--sku <SKU名称> \| --sku-id <id>) --update <json\|@file>` | 更新 SKU 信息(`--sku-id` 优先于 `--sku`) |
225
225
  | `product add --agent <id> --data <json\|@file> [--source <type>] [--no-identify]` | 直接传入结构化商品数据(JSON 数组)异步学习(manual / identify 默认开启) |
226
226
  | `product learn --agent <id> --url <URL...> [--source <type>] [--no-identify]` | 通过商品 URL 异步学习(manual / identify 默认开启) |
227
+ | `product identify-all --agent <id>` | 一键增强学习当前 Agent 下所有待增强商品 |
227
228
  | `product sync-taobao --agent <id> [--skip-hash-check] [--sync-type <full\|incremental>]` | 触发淘宝店铺商品同步 |
228
229
  | `product sync-taobao-item --agent <id> --product-id <商品ID>` | 触发单个淘宝商品同步 |
229
230
 
@@ -233,6 +234,8 @@ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
233
234
 
234
235
  `product learn` 提交商品详情页 URL 进入异步学习队列(`/v1/knowledge/products/upload`),返回 `flow_id` 和 `knowledge_ids`。`--url` 可多值,用空格分隔;`--source` 默认 `manual`;传 `--no-identify` 可关闭自动识别。
235
236
 
237
+ `product identify-all` 对接 `POST /v1/knowledge/products/identify/all`,会为当前 Agent 一键提交所有“已可用但尚未增强”的商品进入增强学习队列。它不是全量重学;当没有待增强商品时,服务端会返回“暂无需要识图的商品”。
238
+
236
239
  `product sync-taobao` / `product sync-taobao-item` 调用 customer-servhub-api 的授权店铺同步接口,要求 `--agent` 已唯一绑定一个淘宝授权店铺。前者支持 `--sync-type full|incremental`,默认 `full`;后者按 `product_id` 补同步单个淘宝商品。
237
240
 
238
241
  注意:这两个命令仅对已开通“高级工具”的店铺 Agent 生效。未开通高级工具时,即使 `--agent` 已绑定淘宝授权店铺,请求也可能在服务端失败。
@@ -578,6 +581,7 @@ cs-cli change-consumer delivery complete <delivery_id> --status completed \
578
581
  | 命令 | 说明 |
579
582
  | --- | --- |
580
583
  | `testset list --customer-agent-config-id <id>` | 列出测试集(分页,Agent ID 必填) |
584
+ | `testset create --customer-agent-config-id <id> --file <cases.xlsx> [--name ...] [--description ...] [--use-type ...]` | 通过 xlsx 模板上传创建测试集 |
581
585
  | `testset show <id>` | 测试集详情 |
582
586
  | `testset update <id> [--name ...] [--description ...] [--data @file.json]` | 更新测试集元数据(至少一项) |
583
587
  | `testset delete <id>` | 软删除测试集 |
@@ -827,3 +831,19 @@ cs-cli ops-record update <record_id> --data '{"remark":"已完成配置更新","
827
831
  # 删除运维操作记录
828
832
  cs-cli ops-record delete <record_id>
829
833
  ```
834
+
835
+ ### Ops Agent (`ops-agent`)
836
+
837
+ | 命令 | 说明 |
838
+ | --- | --- |
839
+ | `ops-agent conversations [--agent-id <id>] [--customer-id <id>] [--page N] [--page-size N]` | 查询客服助手会话列表 |
840
+
841
+ `ops-agent conversations` 对接 `customer-servhub-api /v1/ops-agent/conversations`,返回字段固定为 `workspace_id`、`customer_id`、`title`、`agent_id`、`created_at`、`updated_at`。命令会自动带当前 `workspace-id`;`--agent-id` 对应 Ops Agent 配置 ID(`customer_agent_config_id`),`--customer-id` 可选。
842
+
843
+ ```bash
844
+ # 查询当前 workspace 下某个客服运维助手的会话
845
+ cs-cli ops-agent conversations --agent-id <customer_agent_config_id>
846
+
847
+ # 按 customer 进一步过滤
848
+ cs-cli ops-agent conversations --agent-id <customer_agent_config_id> --customer-id <customer_id>
849
+ ```
package/dist/bin.js CHANGED
@@ -276,7 +276,7 @@ function toExitCode(err) {
276
276
  return 1;
277
277
  }
278
278
  function createRequest(globalTimeout) {
279
- return async function request(baseUrl, path5, options) {
279
+ return async function request(baseUrl, path6, options) {
280
280
  const headers = {
281
281
  "Content-Type": "application/json",
282
282
  ...options.headers
@@ -304,7 +304,7 @@ function createRequest(globalTimeout) {
304
304
  if (workspaceId) {
305
305
  headers["workspace-id"] = workspaceId;
306
306
  }
307
- let url = `${baseUrl}${path5}`;
307
+ let url = `${baseUrl}${path6}`;
308
308
  if (options.query) {
309
309
  const params = new URLSearchParams();
310
310
  for (const [key, value] of Object.entries(options.query)) {
@@ -1343,22 +1343,22 @@ function registerConversationCommand(program2) {
1343
1343
  var DASHBOARD_PREFIX = "/v1/dashboard";
1344
1344
  async function getDashboardSummary(opts) {
1345
1345
  const request = createRequest();
1346
- const path5 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/summary` : `${DASHBOARD_PREFIX}/summary`;
1346
+ const path6 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/summary` : `${DASHBOARD_PREFIX}/summary`;
1347
1347
  const query = {};
1348
1348
  if (opts.startDate) query.start_date = opts.startDate;
1349
1349
  if (opts.endDate) query.end_date = opts.endDate;
1350
1350
  if (opts.channel) query.channel = opts.channel;
1351
- return request(getCustomerServiceUrl(), path5, { method: "GET", query });
1351
+ return request(getCustomerServiceUrl(), path6, { method: "GET", query });
1352
1352
  }
1353
1353
  async function getDashboardTrend(opts) {
1354
1354
  const request = createRequest();
1355
- const path5 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/trend` : `${DASHBOARD_PREFIX}/trend`;
1355
+ const path6 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/trend` : `${DASHBOARD_PREFIX}/trend`;
1356
1356
  const query = {};
1357
1357
  if (opts.startDate) query.start_date = opts.startDate;
1358
1358
  if (opts.endDate) query.end_date = opts.endDate;
1359
1359
  if (opts.granularity) query.granularity = opts.granularity;
1360
1360
  if (opts.channel) query.channel = opts.channel;
1361
- return request(getCustomerServiceUrl(), path5, { method: "GET", query });
1361
+ return request(getCustomerServiceUrl(), path6, { method: "GET", query });
1362
1362
  }
1363
1363
  async function listShopStatistics(opts) {
1364
1364
  const request = createRequest();
@@ -1578,12 +1578,12 @@ function registerDashboardCommand(program2) {
1578
1578
  channel: opts.channel
1579
1579
  });
1580
1580
  if (opts.out) {
1581
- const { path: path5, bytes } = await writeBinaryToFile(
1581
+ const { path: path6, bytes } = await writeBinaryToFile(
1582
1582
  opts.out,
1583
1583
  Buffer.from(result.csv, "utf-8")
1584
1584
  );
1585
1585
  formatOutput(
1586
- { success: true, data: { path: path5, bytes, count: result.rows.length } },
1586
+ { success: true, data: { path: path6, bytes, count: result.rows.length } },
1587
1587
  program2.opts().table
1588
1588
  );
1589
1589
  } else {
@@ -1628,6 +1628,23 @@ function registerDashboardCommand(program2) {
1628
1628
 
1629
1629
  // src/client/debug-api.ts
1630
1630
  var DEFAULT_FLOW_WORKSPACE_ID = "531c14d1ece047cbaec22914e1f1364d";
1631
+ function toTimestampSeconds(value) {
1632
+ const timestamp = typeof value === "string" && Number.isNaN(Number(value)) ? Math.floor(new Date(value).getTime() / 1e3) : Math.floor(Number(value));
1633
+ if (!Number.isFinite(timestamp)) return null;
1634
+ return timestamp > 9999999999 ? Math.floor(timestamp / 1e3) : timestamp;
1635
+ }
1636
+ function rebaseMsgListTimestamps(msgList) {
1637
+ const timestamps = msgList.map(
1638
+ (message) => typeof message === "object" && message !== null ? toTimestampSeconds(message.timestamp) : null
1639
+ ).filter((timestamp) => timestamp !== null);
1640
+ if (timestamps.length === 0) return msgList;
1641
+ const offset = Math.floor(Date.now() / 1e3) - Math.max(...timestamps);
1642
+ return msgList.map((message) => {
1643
+ if (typeof message !== "object" || message === null) return message;
1644
+ const timestamp = toTimestampSeconds(message.timestamp);
1645
+ return timestamp === null ? message : { ...message, timestamp: timestamp + offset };
1646
+ });
1647
+ }
1631
1648
  async function createDebugConversation(opts) {
1632
1649
  const request = createRequest();
1633
1650
  return request(getCustomerServiceUrl(), "/v1/chat/conversation", {
@@ -1642,7 +1659,7 @@ async function createDebugConversation(opts) {
1642
1659
  async function sendAgentMessage(opts) {
1643
1660
  let msgList;
1644
1661
  if (opts.msgList) {
1645
- msgList = opts.msgList;
1662
+ msgList = rebaseMsgListTimestamps(opts.msgList);
1646
1663
  } else {
1647
1664
  if (opts.text == null) {
1648
1665
  throw new Error("sendAgentMessage: either msgList or text must be provided");
@@ -3769,6 +3786,44 @@ function registerMonitorCommand(program2) {
3769
3786
  });
3770
3787
  }
3771
3788
 
3789
+ // src/client/ops-agent-api.ts
3790
+ async function listOpsAgentConversations(opts) {
3791
+ const workspaceId = getWorkspaceId(opts.workspaceId);
3792
+ const request = createRequest();
3793
+ const query = {
3794
+ page: opts.page ?? 1,
3795
+ page_size: opts.pageSize ?? 20
3796
+ };
3797
+ if (opts.customerId) query.customer_id = opts.customerId;
3798
+ if (opts.agentId) query.agent_id = opts.agentId;
3799
+ return request(getCustomerServiceUrl(), "/v1/ops-agent/conversations", {
3800
+ method: "GET",
3801
+ query,
3802
+ headers: { "workspace-id": workspaceId }
3803
+ });
3804
+ }
3805
+
3806
+ // src/commands/ops-agent.ts
3807
+ function registerOpsAgentCommand(program2) {
3808
+ const opsAgent = program2.command("ops-agent").description("Ops Agent\uFF08\u5BA2\u670D\u52A9\u624B\uFF09\u67E5\u8BE2\u547D\u4EE4");
3809
+ opsAgent.command("conversations").description(
3810
+ "\u67E5\u8BE2\u5BA2\u670D\u52A9\u624B\u4F1A\u8BDD\u5217\u8868\u3002\u8FD4\u56DE workspace_id\u3001customer_id\u3001title\u3001agent_id\u3001created_at\u3001updated_at"
3811
+ ).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) => {
3812
+ try {
3813
+ const data = await listOpsAgentConversations({
3814
+ customerId: opts.customerId,
3815
+ agentId: opts.agentId,
3816
+ page: Number(opts.page),
3817
+ pageSize: Number(opts.pageSize)
3818
+ });
3819
+ formatOutput({ success: true, data }, program2.opts().table);
3820
+ } catch (err) {
3821
+ reportCaughtError(err);
3822
+ process.exit(toExitCode(err));
3823
+ }
3824
+ });
3825
+ }
3826
+
3772
3827
  // src/client/operations-record-api.ts
3773
3828
  var PATH_PREFIX2 = "/v1/agent_operations_records";
3774
3829
  async function listOperationsRecords(opts) {
@@ -3981,6 +4036,15 @@ async function syncSingleTaobaoProduct(opts) {
3981
4036
  }
3982
4037
  });
3983
4038
  }
4039
+ async function identifyAllProducts(opts) {
4040
+ const request = createRequest();
4041
+ return request(getCustomerServiceUrl(), "/v1/knowledge/products/identify/all", {
4042
+ method: "POST",
4043
+ body: {
4044
+ customer_agent_config_id: opts.agentConfigId
4045
+ }
4046
+ });
4047
+ }
3984
4048
 
3985
4049
  // src/commands/product.ts
3986
4050
  async function getDbId(agentConfigId) {
@@ -4118,6 +4182,19 @@ function registerProductCommand(program2) {
4118
4182
  process.exit(toExitCode(err));
4119
4183
  }
4120
4184
  });
4185
+ product.command("identify-all").description(
4186
+ "\u4E00\u952E\u589E\u5F3A\u5B66\u4E60\u5F53\u524D Agent \u4E0B\u6240\u6709\u5F85\u589E\u5F3A\u5546\u54C1\u3002\u5BF9\u63A5 POST /v1/knowledge/products/identify/all\uFF0C\u5F02\u6B65\u63D0\u4EA4\u5546\u54C1\u589E\u5F3A\u5B66\u4E60\u4EFB\u52A1"
4187
+ ).requiredOption("--agent <config_id>", "Agent \u914D\u7F6E ID\uFF08\u4ECE agent list \u83B7\u53D6\uFF09").action(async (opts) => {
4188
+ try {
4189
+ const data = await identifyAllProducts({
4190
+ agentConfigId: opts.agent
4191
+ });
4192
+ formatOutput({ success: true, data }, program2.opts().table);
4193
+ } catch (err) {
4194
+ reportCaughtError(err);
4195
+ process.exit(toExitCode(err));
4196
+ }
4197
+ });
4121
4198
  product.command("sync-taobao").description(
4122
4199
  "\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"
4123
4200
  ).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) => {
@@ -4467,10 +4544,132 @@ function registerSACommand(program2) {
4467
4544
  });
4468
4545
  }
4469
4546
 
4470
- // src/commands/testset.ts
4547
+ // src/commands/special-project.ts
4471
4548
  import fs9 from "fs";
4472
4549
 
4550
+ // src/client/special-project-api.ts
4551
+ async function createSpecialProject(input) {
4552
+ const request = createRequest();
4553
+ return request(getCsAdminUrl(), "/api/special-projects", {
4554
+ method: "POST",
4555
+ body: input
4556
+ });
4557
+ }
4558
+ async function listSpecialProjects(opts = {}) {
4559
+ const request = createRequest();
4560
+ const query = {};
4561
+ if (opts.ownerUserId !== void 0) query.ownerUserId = opts.ownerUserId;
4562
+ if (opts.status) query.status = opts.status;
4563
+ const result = await request(getCsAdminUrl(), "/api/special-projects", {
4564
+ method: "GET",
4565
+ query
4566
+ });
4567
+ return result.rows ?? [];
4568
+ }
4569
+ async function updateSpecialProject(projectId, patch) {
4570
+ const request = createRequest();
4571
+ return request(
4572
+ getCsAdminUrl(),
4573
+ `/api/special-projects/${projectId}`,
4574
+ { method: "PATCH", body: patch }
4575
+ );
4576
+ }
4577
+ async function closeSpecialProject(projectId) {
4578
+ return updateSpecialProject(projectId, { status: "\u5DF2\u5B8C\u6210" });
4579
+ }
4580
+ async function upsertSpecialProjects(items, apply) {
4581
+ const request = createRequest();
4582
+ return request(getCsAdminUrl(), "/api/special-projects/batch", {
4583
+ method: "POST",
4584
+ body: { items, apply }
4585
+ });
4586
+ }
4587
+
4588
+ // src/commands/special-project.ts
4589
+ function readJsonArg(value) {
4590
+ if (value.startsWith("@")) {
4591
+ return fs9.readFileSync(value.slice(1), "utf-8");
4592
+ }
4593
+ return value;
4594
+ }
4595
+ function registerSpecialProjectCommand(program2) {
4596
+ const sp = program2.command("special-project").description(
4597
+ "\u4E13\u9879\uFF08special_project\uFF09\u7BA1\u7406 \u2014\u2014 \u5DE5\u4F5C\u8D1F\u8F7D\u770B\u677F\u7684\u4E13\u9879\u6570\u636E\u3002\u4E3B\u529B\u5F55\u5165\u901A\u9053\uFF1A\u65E9\u4F1A\u9010\u5B57\u7A3F \u2192 Agent \u89E3\u6790 \u2192 upsert\uFF08\u5148\u770B diff\uFF0C\u786E\u8BA4\u540E --apply\uFF09"
4598
+ );
4599
+ sp.command("create").description("\u521B\u5EFA\u5355\u4E2A\u4E13\u9879").requiredOption("--name <name>", "\u4E13\u9879\u540D").requiredOption("--owner <id>", "\u627F\u63A5\u5DE5\u7A0B\u5E08 cs_admin_user_id").requiredOption("--daily-effort <personDays>", "\u9884\u4F30\u6BCF\u65E5\u6295\u5165\uFF08\u4EBA\u5929\uFF0C\u5982 0.5\uFF09").requiredOption("--start-date <YYYY-MM-DD>", "\u5F00\u59CB\u65E5\u671F").requiredOption("--duration-days <n>", "\u9884\u4F30\u6709\u6548\u6295\u5165\u5929\u6570").option("--workspace <id>", "\u5173\u8054\u5BA2\u6237 workspace_id\uFF08\u53EF\u7A7A\uFF0C\u5185\u90E8\u4E13\u9879\u4E0D\u586B\uFF09").option("--description <text>", "\u63CF\u8FF0").option("--source-ref <ref>", "\u7A33\u5B9A\u5916\u90E8\u952E\uFF08\u9010\u5B57\u7A3F\u6765\u6E90 id\uFF09\uFF0Cupsert \u5E42\u7B49\u7528").action(async (opts) => {
4600
+ try {
4601
+ const data = await createSpecialProject({
4602
+ name: opts.name,
4603
+ ownerUserId: Number(opts.owner),
4604
+ dailyEffort: Number(opts.dailyEffort),
4605
+ startDate: opts.startDate,
4606
+ durationDays: Number(opts.durationDays),
4607
+ workspaceId: opts.workspace,
4608
+ description: opts.description,
4609
+ sourceRef: opts.sourceRef
4610
+ });
4611
+ formatOutput({ success: true, data }, program2.opts().table);
4612
+ } catch (err) {
4613
+ reportCaughtError(err);
4614
+ process.exit(toExitCode(err));
4615
+ }
4616
+ });
4617
+ sp.command("list").description("\u5217\u51FA\u4E13\u9879").option("--owner <id>", "\u6309\u627F\u63A5\u5DE5\u7A0B\u5E08 cs_admin_user_id \u8FC7\u6EE4").option("--status <status>", "\u6309\u72B6\u6001\u8FC7\u6EE4\uFF08\u8FDB\u884C\u4E2D/\u6682\u505C/\u9700\u786E\u8BA4/\u5DF2\u5B8C\u6210\uFF09").action(async (opts) => {
4618
+ try {
4619
+ const data = await listSpecialProjects({
4620
+ ownerUserId: opts.owner !== void 0 ? Number(opts.owner) : void 0,
4621
+ status: opts.status
4622
+ });
4623
+ formatOutput({ success: true, data }, program2.opts().table);
4624
+ } catch (err) {
4625
+ reportCaughtError(err);
4626
+ process.exit(toExitCode(err));
4627
+ }
4628
+ });
4629
+ sp.command("update <id>").description("\u66F4\u65B0\u4E13\u9879\uFF08\u4EFB\u610F\u5B50\u96C6\uFF1B\u72B6\u6001\u8FC1\u79FB\u53D7\u670D\u52A1\u7AEF\u72B6\u6001\u673A\u7EA6\u675F\uFF09").option("--progress <n>", "\u8FDB\u5EA6 0-100").option("--status <status>", "\u72B6\u6001\uFF08\u8FDB\u884C\u4E2D/\u6682\u505C/\u9700\u786E\u8BA4/\u5DF2\u5B8C\u6210\uFF09").option("--daily-effort <personDays>", "\u6BCF\u65E5\u6295\u5165\uFF08\u4EBA\u5929\uFF09").option("--duration-days <n>", "\u6709\u6548\u6295\u5165\u5929\u6570").option("--description <text>", "\u63CF\u8FF0").action(async (id, opts) => {
4630
+ try {
4631
+ const patch = {};
4632
+ if (opts.progress !== void 0) patch.progress = Number(opts.progress);
4633
+ if (opts.status !== void 0) patch.status = opts.status;
4634
+ if (opts.dailyEffort !== void 0) patch.dailyEffort = Number(opts.dailyEffort);
4635
+ if (opts.durationDays !== void 0) patch.durationDays = Number(opts.durationDays);
4636
+ if (opts.description !== void 0) patch.description = opts.description;
4637
+ const data = await updateSpecialProject(Number(id), patch);
4638
+ formatOutput({ success: true, data }, program2.opts().table);
4639
+ } catch (err) {
4640
+ reportCaughtError(err);
4641
+ process.exit(toExitCode(err));
4642
+ }
4643
+ });
4644
+ sp.command("close <id>").description("\u5173\u95ED\u4E13\u9879\uFF08\u7F6E\u4E3A\u5DF2\u5B8C\u6210\uFF09").action(async (id) => {
4645
+ try {
4646
+ const data = await closeSpecialProject(Number(id));
4647
+ formatOutput({ success: true, data }, program2.opts().table);
4648
+ } catch (err) {
4649
+ reportCaughtError(err);
4650
+ process.exit(toExitCode(err));
4651
+ }
4652
+ });
4653
+ sp.command("upsert").description(
4654
+ "\u6279\u91CF upsert\uFF08\u9010\u5B57\u7A3F\u540C\u6B65\uFF09\u3002--items \u63A5 JSON \u6570\u7EC4\u6216 @\u6587\u4EF6\u3002\u9ED8\u8BA4\u53EA\u6253\u5370 diff \u4E0D\u5199\u5E93\uFF1B\u52A0 --apply \u624D\u843D\u5E93"
4655
+ ).requiredOption("--items <json|@file>", "items JSON \u6570\u7EC4\uFF0C\u6216 @path \u8BFB\u6587\u4EF6").option("--apply", "\u843D\u5E93\uFF08\u7F3A\u7701\u53EA\u7B97 diff \u4E0D\u5199\uFF09", false).action(async (opts) => {
4656
+ try {
4657
+ const items = JSON.parse(readJsonArg(opts.items));
4658
+ const data = await upsertSpecialProjects(items, opts.apply === true);
4659
+ formatOutput({ success: true, data }, program2.opts().table);
4660
+ } catch (err) {
4661
+ reportCaughtError(err);
4662
+ process.exit(toExitCode(err));
4663
+ }
4664
+ });
4665
+ }
4666
+
4667
+ // src/commands/testset.ts
4668
+ import fs11 from "fs";
4669
+
4473
4670
  // src/client/testset-api.ts
4671
+ import fs10 from "fs";
4672
+ import path5 from "path";
4474
4673
  function unwrapPaginated(raw, fallbackPageSize) {
4475
4674
  return {
4476
4675
  items: Array.isArray(raw?.data) ? raw?.data : [],
@@ -4496,6 +4695,28 @@ async function listTestSets(query) {
4496
4695
  );
4497
4696
  return unwrapPaginated(raw, query.pageSize ?? 20);
4498
4697
  }
4698
+ async function createTestSetFromFile(input) {
4699
+ const request = createRequest();
4700
+ const fileBytes = fs10.readFileSync(input.filePath);
4701
+ const formData = new FormData();
4702
+ formData.append(
4703
+ "file",
4704
+ new Blob([fileBytes], {
4705
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
4706
+ }),
4707
+ path5.basename(input.filePath)
4708
+ );
4709
+ return request(getCustomerServiceUrl(), "/v1/test_sets/file/upload", {
4710
+ method: "POST",
4711
+ query: {
4712
+ customer_agent_config_id: input.customerAgentConfigId,
4713
+ test_set_name: input.testSetName,
4714
+ test_set_description: input.testSetDescription,
4715
+ use_type: input.useType
4716
+ },
4717
+ body: formData
4718
+ });
4719
+ }
4499
4720
  async function getTestSet(id) {
4500
4721
  const request = createRequest();
4501
4722
  return request(getCustomerServiceUrl(), `/v1/test_sets/${id}`, {
@@ -4980,7 +5201,7 @@ function readPromptInput(value) {
4980
5201
  if (!filePath) {
4981
5202
  throw new Error("File path cannot be empty after @");
4982
5203
  }
4983
- return fs9.readFileSync(filePath, "utf-8");
5204
+ return fs11.readFileSync(filePath, "utf-8");
4984
5205
  }
4985
5206
  return value;
4986
5207
  }
@@ -5013,6 +5234,25 @@ function registerTestsetCommand(program2) {
5013
5234
  process.exit(toExitCode(err));
5014
5235
  }
5015
5236
  });
5237
+ 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) => {
5238
+ try {
5239
+ if (!fs11.existsSync(opts.file)) {
5240
+ outputError(1, `\u6587\u4EF6\u4E0D\u5B58\u5728: ${opts.file}`);
5241
+ process.exit(1);
5242
+ }
5243
+ const data = await createTestSetFromFile({
5244
+ customerAgentConfigId: opts.customerAgentConfigId,
5245
+ filePath: opts.file,
5246
+ testSetName: opts.name,
5247
+ testSetDescription: opts.description,
5248
+ useType: opts.useType
5249
+ });
5250
+ formatOutput({ success: true, data }, program2.opts().table);
5251
+ } catch (err) {
5252
+ reportCaughtError(err);
5253
+ process.exit(toExitCode(err));
5254
+ }
5255
+ });
5016
5256
  testset.command("show").description("\u67E5\u770B\u5355\u4E2A\u6D4B\u8BD5\u96C6\u8BE6\u60C5").argument("<id>", "\u6D4B\u8BD5\u96C6 ID").action(async (id) => {
5017
5257
  try {
5018
5258
  const data = await getTestSet(id);
@@ -5165,6 +5405,7 @@ registerAuthCommand(program);
5165
5405
  registerConfigCommand(program);
5166
5406
  registerWorkspaceCommand(program);
5167
5407
  registerAgentCommand(program);
5408
+ registerOpsAgentCommand(program);
5168
5409
  registerSACommand(program);
5169
5410
  registerProductCommand(program);
5170
5411
  registerKnowledgeCommand(program);
@@ -5180,6 +5421,7 @@ registerRepairRecordCommand(program);
5180
5421
  registerOperationsRecordCommand(program);
5181
5422
  registerChangeConsumerCommand(program);
5182
5423
  registerTestsetCommand(program);
5424
+ registerSpecialProjectCommand(program);
5183
5425
  process.on("uncaughtException", (err) => {
5184
5426
  outputError(3, err.message);
5185
5427
  process.exit(3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/customer-service-cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "AI Customer Service CLI - Agent friendly",
5
5
  "type": "module",
6
6
  "main": "./dist/bin.js",