@bty/customer-service-cli 0.3.2 → 0.4.1

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 +57 -17
  2. package/dist/bin.js +247 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -177,9 +177,20 @@ cs-cli --workspace ws-other agent list # ❌ 报错:CS_WORKSPACE_ID 已被
177
177
  ### 工作空间 (`workspace`)
178
178
 
179
179
 
180
- | 命令 | 说明 |
181
- | ---------------- | -------- |
180
+ | 命令 | 说明 |
181
+ | --- | --- |
182
182
  | `workspace list` | 列出所有工作空间 |
183
+ | `workspace points-consumes-daily [--start <YYYY-MM-DD>] [--end <YYYY-MM-DD>]` | 按天查询工作空间积分消耗(默认最近 30 天) |
184
+
185
+ `workspace points-consumes-daily` 需要可用的工作空间上下文(`--workspace`、本地 `.cs-cli.json` 或全局 `defaultWorkspaceId`)。
186
+
187
+ ```bash
188
+ # 查询最近 30 天(默认)
189
+ cs-cli workspace points-consumes-daily
190
+
191
+ # 指定日期范围(含 start/end)
192
+ cs-cli workspace points-consumes-daily --start 2026-04-01 --end 2026-04-23
193
+ ```
183
194
 
184
195
 
185
196
  ### Agent 管理 (`agent`)
@@ -303,33 +314,27 @@ Agent 关联的通用规则知识库(`suffix_type=_common`),与 FAQ / 商
303
314
  | `debug ask --agent <id> --text <消息> [--user <用户名>] [--url <图片URL>] [--conversation <会话ID>] [--timeout <秒>]` | 向 Agent 发送消息并等待回复 |
304
315
  | `debug reproduce <record_id> [--agent <id>] [--timeout <秒>] [--dry-run]` | 根据 record_id 复现 Agent 回复(自动获取上下文) |
305
316
  | `debug record <record_id>` | 获取记录的调试信息(flow_info),返回中包含 `trace_id` |
306
- | `debug trace <trace_id>` | 根据 Langfuse Trace ID 获取完整 Trace 详情 |
317
+ | `debug trace <trace_id> [--env <dev\|prod>]` | 根据 Langfuse Trace ID 获取完整 Trace 详情(经服务端代理,默认 `prod`) |
307
318
 
308
319
 
309
320
  `debug ask` 会自动创建调试会话、发送消息、轮询等待 Agent 回复,默认最大等待 30 秒。支持 `--messages` 传入完整消息列表(JSON 或 @文件)重放 issue 对话。
310
321
 
311
322
  `debug reproduce` 根据一条回复记录自动复现 Agent 回复:通过 recordId 获取关联会话和 Agent 配置,拉取完整会话记录并截取目标 record 之前的上下文,使用原始用户名创建新 debug 会话并发送。`--agent` 可指定另一个 Agent 复现同一段上下文(A/B 对比),`--dry-run` 仅输出上下文不发送。
312
323
 
313
- `debug trace` 直接调用 Langfuse REST API(`GET /api/public/traces/:traceId`),使用 Basic Auth 认证。Trace ID 从 `debug record` 返回的 `trace_id` 字段获取。
324
+ `debug trace`(v0.4.0 起,**行为变更**)不再由 CLI 直连 Langfuse REST API,改为请求 customer-servhub-api 的 `GET /v1/debug/langfuse/trace/{trace_id}?env=<env>`,走 CLI 标准 Bearer 鉴权,由服务端按 env 选择 Langfuse 凭据并代理拉取 Trace。Trace ID 从 `debug record` 返回的 `trace_id` 字段获取。
314
325
 
315
- #### Langfuse 配置
316
-
317
- Langfuse 已内置默认连接信息,通常无需配置。如需覆盖,支持环境变量和配置文件两种方式:
318
-
319
- **环境变量(优先级最高):**
326
+ - `--env <dev|prod>`:指定 Langfuse 环境,默认 `prod`。只接受 `dev` / `prod`,传其它值命令直接报错并以非 0 退出(不发请求)。
320
327
 
321
328
  ```bash
322
- export LANGFUSE_BASE_URL="http://192.168.40.10:3000"
323
- export LANGFUSE_PUBLIC_KEY="pk-lf-xxx"
324
- export LANGFUSE_SECRET_KEY="sk-lf-xxx"
325
- ```
326
-
327
- **配置文件(`~/.cs-cli/config.json`):**
329
+ # 默认 prod 环境
330
+ cs-cli debug trace <trace_id>
328
331
 
329
- ```bash
330
- cs-cli config set --langfuse-host <url> --langfuse-public-key <key> --langfuse-secret-key <key>
332
+ # 指定 dev 环境
333
+ cs-cli debug trace <trace_id> --env dev
331
334
  ```
332
335
 
336
+ > **迁移(v0.4.0 BREAKING)**:`config set --langfuse-host / --langfuse-public-key / --langfuse-secret-key` 及 `CLIConfig.langfuseHost / langfusePublicKey / langfuseSecretKey` 已移除;环境变量 `LANGFUSE_BASE_URL / LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY` 在 CLI 侧不再被读取。`~/.cs-cli/config.json` 中相关字段可直接删除。Langfuse 连接信息(含 dev/prod 双环境凭据)现由 customer-servhub-api 服务端的 `system_config` 表 `langfuse_config` 行统一管理(不再读服务端环境变量 / 内置常量),CLI 侧无需也无法再配置。
337
+
333
338
  ### 修复记录 (`repair-record`)
334
339
 
335
340
  | 命令 | 说明 |
@@ -383,6 +388,41 @@ cs-cli config set --langfuse-host <url> --langfuse-public-key <key> --langfuse-s
383
388
 
384
389
  创建记录时 `operator_id` 和 `operator_name` 自动从当前登录用户获取,无需手动指定。
385
390
 
391
+ ### 商品变更事件消费 (`change-consumer`)
392
+
393
+ 客服运维助手订阅并消费商品变更事件(**pull 模式**)。后端走 `/v1/change-consumers/*`,CLI 标准 Bearer 鉴权。默认 `consumer_key=ops_agent_assistant`。
394
+
395
+ 标准工作流:`sub create` 创建订阅 → `delivery list` 轮询待消费项 → `delivery claim` 认领 → 处理 → `delivery complete` 回写。
396
+
397
+ | 命令 | 说明 |
398
+ | --- | --- |
399
+ | `change-consumer sub create --workspace-id <id> [--agent-config <id>] [--events <list>] [--resource-type product] [--consumer-key <key>] [--consumer-name <名称>] [--disabled] [--data <json\|@file>]` | 创建订阅(最简只需 `--workspace-id`;`--agent-config` 缺省表示该工作空间全部商品事件) |
400
+ | `change-consumer sub list [--workspace-id <id>] [--agent-config <id>] [--consumer-key <key>] [--enabled <true\|false>] [--page N] [--page-size N]` | 查询订阅列表 |
401
+ | `change-consumer sub update <subscription_id> --data <json\|@file>` | 更新订阅(`enabled` / `events` / `filters` / `consumer_name`) |
402
+ | `change-consumer delivery list [--workspace-id <id>] [--agent-config <id>] [--consumer-key <key>] [--statuses <list>] [--cursor-created-at <iso>] [--cursor-delivery-id <id>] [--limit N]` | 查询待消费项(不传 `--statuses` 时后端默认返回 pending/claimed/failed;`--cursor-*` 做增量拉取) |
403
+ | `change-consumer delivery claim <delivery_id> --claimed-by <id> [--consumer-key <key>] [--lease-seconds N]` | 认领待消费项(已被其他实例认领且未过期时认领失败) |
404
+ | `change-consumer delivery complete <delivery_id> --status <completed\|ignored\|failed> [--result-action <action>] [--result-note <note>] [--last-error <error>] [--consumer-key <key>] [--data <json\|@file>]` | 回写处理结果 |
405
+
406
+ - `sub create` 默认 `--events product.updated,product.deleted`、`--resource-type product`;`--disabled` 创建为停用态(默认启用)。
407
+ - `delivery complete` 的 `--status` 仅接受 `completed` / `ignored` / `failed`,其它值直接报错退出;不要长时间停留在 `claimed` 不回写。
408
+ - `--result-action` 建议值:`supplemental_knowledge` / `product_association` / `rerun_product_study` / `refresh_recommended_qa` / `ignore_change` / `manual_follow_up`。
409
+ - 各子命令 `--data <json|@file>` 为高级用法,提供时覆盖对应结构化选项。
410
+
411
+ ```bash
412
+ # 1. 创建订阅(该工作空间全部商品 更新/删除 事件)
413
+ cs-cli change-consumer sub create --workspace-id <ws> --events product.updated,product.deleted
414
+
415
+ # 2. 轮询待消费项
416
+ cs-cli change-consumer delivery list --workspace-id <ws> --statuses pending
417
+
418
+ # 3. 认领
419
+ cs-cli change-consumer delivery claim <delivery_id> --claimed-by ops-assistant-1
420
+
421
+ # 4. 处理后回写
422
+ cs-cli change-consumer delivery complete <delivery_id> --status completed \
423
+ --result-action supplemental_knowledge --result-note "已补充商品知识"
424
+ ```
425
+
386
426
  ### 运营监控 (`monitor`)
387
427
 
388
428
  直接调用 `/v1/operation` 底层接口,返回原始数据,由调用方决定如何聚合统计。
package/dist/bin.js CHANGED
@@ -437,9 +437,6 @@ function isTokenExpired(expiresAt) {
437
437
  var DEFAULT_CS_API_URL = "https://customer-servhub-api.betteryeah.com";
438
438
  var DEFAULT_AI_API_URL = "https://ai-api.betteryeah.com";
439
439
  var DEFAULT_CUSTOMER_AGENT_API_URL = "https://customer-agent.bantouyan.com";
440
- var DEFAULT_LANGFUSE_HOST = "http://192.168.40.10:3000";
441
- var DEFAULT_LANGFUSE_PUBLIC_KEY = "pk-lf-1b3aece4-021a-4a81-be4a-abd6334c5929";
442
- var DEFAULT_LANGFUSE_SECRET_KEY = "sk-lf-edc22ac8-5d0a-4f72-b285-6556adfa8f71";
443
440
  function getCustomerServiceUrl() {
444
441
  const envUrl = process.env.CS_CS_API_URL;
445
442
  if (envUrl) return envUrl;
@@ -473,14 +470,6 @@ function getWorkspaceId(overrideWorkspaceId) {
473
470
  if (globalConfig?.defaultWorkspaceId) return globalConfig.defaultWorkspaceId;
474
471
  throw new APIError(1, "\u672A\u8BBE\u7F6E\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u8BF7\u8FD0\u884C: cs-cli config set-workspace <id>");
475
472
  }
476
- function getLangfuseConfig() {
477
- const config = readConfig();
478
- return {
479
- host: process.env.LANGFUSE_BASE_URL ?? config?.langfuseHost ?? DEFAULT_LANGFUSE_HOST,
480
- publicKey: process.env.LANGFUSE_PUBLIC_KEY ?? config?.langfusePublicKey ?? DEFAULT_LANGFUSE_PUBLIC_KEY,
481
- secretKey: process.env.LANGFUSE_SECRET_KEY ?? config?.langfuseSecretKey ?? DEFAULT_LANGFUSE_SECRET_KEY
482
- };
483
- }
484
473
  function buildCookieHeaders() {
485
474
  const creds = readCredentials();
486
475
  if (!creds) throw new APIError(2, "\u672A\u767B\u5F55\uFF0C\u8BF7\u8FD0\u884C: cs-cli auth login");
@@ -696,6 +685,195 @@ function registerAuthCommand(program2) {
696
685
  });
697
686
  }
698
687
 
688
+ // src/client/change-consumer-api.ts
689
+ var SUBSCRIPTIONS_PATH = "/v1/change-consumers/subscriptions";
690
+ var DEFAULT_CONSUMER_KEY = "ops_agent_assistant";
691
+ async function createSubscription(body) {
692
+ const request = createRequest();
693
+ return request(getCustomerServiceUrl(), SUBSCRIPTIONS_PATH, {
694
+ method: "POST",
695
+ body
696
+ });
697
+ }
698
+ async function listSubscriptions(opts) {
699
+ const request = createRequest();
700
+ const query = {};
701
+ if (opts.workspaceId) query.workspace_id = opts.workspaceId;
702
+ if (opts.customerAgentConfigId) query.customer_agent_config_id = opts.customerAgentConfigId;
703
+ if (opts.consumerKey) query.consumer_key = opts.consumerKey;
704
+ if (opts.enabled !== void 0) query.enabled = opts.enabled;
705
+ if (opts.page) query.page = opts.page;
706
+ if (opts.pageSize) query.page_size = opts.pageSize;
707
+ return request(getCustomerServiceUrl(), SUBSCRIPTIONS_PATH, {
708
+ method: "GET",
709
+ query
710
+ });
711
+ }
712
+ async function updateSubscription(subscriptionId, body) {
713
+ const request = createRequest();
714
+ return request(
715
+ getCustomerServiceUrl(),
716
+ `${SUBSCRIPTIONS_PATH}/${subscriptionId}`,
717
+ { method: "PUT", body }
718
+ );
719
+ }
720
+ async function listDeliveries(consumerKey, opts) {
721
+ const request = createRequest();
722
+ const query = {};
723
+ if (opts.workspaceId) query.workspace_id = opts.workspaceId;
724
+ if (opts.customerAgentConfigId) query.customer_agent_config_id = opts.customerAgentConfigId;
725
+ if (opts.statuses) query.statuses = opts.statuses;
726
+ if (opts.cursorCreatedAt) query.cursor_created_at = opts.cursorCreatedAt;
727
+ if (opts.cursorDeliveryId) query.cursor_delivery_id = opts.cursorDeliveryId;
728
+ if (opts.limit) query.limit = opts.limit;
729
+ return request(
730
+ getCustomerServiceUrl(),
731
+ `/v1/change-consumers/${encodeURIComponent(consumerKey)}/deliveries`,
732
+ { method: "GET", query }
733
+ );
734
+ }
735
+ async function claimDelivery(deliveryId, body) {
736
+ const request = createRequest();
737
+ return request(
738
+ getCustomerServiceUrl(),
739
+ `/v1/change-consumers/deliveries/${deliveryId}/claim`,
740
+ { method: "POST", body }
741
+ );
742
+ }
743
+ async function completeDelivery(deliveryId, body) {
744
+ const request = createRequest();
745
+ return request(
746
+ getCustomerServiceUrl(),
747
+ `/v1/change-consumers/deliveries/${deliveryId}/complete`,
748
+ { method: "POST", body }
749
+ );
750
+ }
751
+
752
+ // src/commands/change-consumer.ts
753
+ function registerChangeConsumerCommand(program2) {
754
+ const cc = program2.command("change-consumer").description(
755
+ "\u5546\u54C1\u53D8\u66F4\u4E8B\u4EF6\u6D88\u8D39 \u2014\u2014 \u5BA2\u670D\u8FD0\u7EF4\u52A9\u624B\u8BA2\u9605\u5E76\u6D88\u8D39\u5546\u54C1\u53D8\u66F4\uFF08pull \u6A21\u5F0F\uFF09\u3002\u5DE5\u4F5C\u6D41\uFF1Asub create \u521B\u5EFA\u8BA2\u9605 \u2192 delivery list \u8F6E\u8BE2\u5F85\u6D88\u8D39\u9879 \u2192 delivery claim \u8BA4\u9886 \u2192 \u63D0\u793A\u7528\u6237\u5E76\u5904\u7406 \u2192 delivery complete \u56DE\u5199\u3002\u9ED8\u8BA4 consumer_key=" + DEFAULT_CONSUMER_KEY
756
+ );
757
+ const sub = cc.command("sub").description("\u8BA2\u9605\u7BA1\u7406\uFF08subscription\uFF09");
758
+ sub.command("create").description(
759
+ "\u521B\u5EFA\u8BA2\u9605\u3002\u6700\u7B80\u914D\u7F6E\u53EA\u9700 workspace-id + events\uFF1Bcustomer-agent-config-id \u4E0D\u4F20\u8868\u793A\u8BE5 Agent \u5168\u90E8\u5546\u54C1\u4E8B\u4EF6"
760
+ ).option("--consumer-key <key>", "consumer_key", DEFAULT_CONSUMER_KEY).option("--consumer-name <name>", "consumer \u663E\u793A\u540D", "\u5BA2\u670D\u8FD0\u7EF4\u52A9\u624B").requiredOption("--workspace-id <workspace_id>", "\u5DE5\u4F5C\u7A7A\u95F4 ID").option("--agent-config <id>", "customer_agent_config_id\uFF08\u53EF\u9009\uFF0C\u7F3A\u7701\u8868\u793A\u5168\u90E8\uFF09").option("--resource-type <type>", "\u8D44\u6E90\u7C7B\u578B", "product").option(
761
+ "--events <list>",
762
+ "\u9017\u53F7\u5206\u9694\u4E8B\u4EF6\u5217\u8868\uFF0C\u5982 product.updated,product.deleted",
763
+ "product.updated,product.deleted"
764
+ ).option("--disabled", "\u521B\u5EFA\u4E3A\u505C\u7528\u72B6\u6001\uFF08\u9ED8\u8BA4\u542F\u7528\uFF09", false).option("--data <json>", "\u9AD8\u7EA7\uFF1A\u5B8C\u6574\u8BA2\u9605 JSON \u6216 @\u6587\u4EF6\uFF0C\u63D0\u4F9B\u65F6\u8986\u76D6\u4E0A\u8FF0\u9009\u9879").action(async (opts) => {
765
+ try {
766
+ const body = opts.data ? parseDataOption(opts.data) : {
767
+ consumer_key: opts.consumerKey,
768
+ consumer_name: opts.consumerName,
769
+ mode: "pull",
770
+ workspace_id: opts.workspaceId,
771
+ customer_agent_config_id: opts.agentConfig,
772
+ resource_type: opts.resourceType,
773
+ enabled: !opts.disabled,
774
+ events: String(opts.events).split(",").map((e) => e.trim()).filter(Boolean)
775
+ };
776
+ const data = await createSubscription(body);
777
+ formatOutput({ success: true, data }, program2.opts().table);
778
+ } catch (err) {
779
+ reportCaughtError(err);
780
+ process.exit(toExitCode(err));
781
+ }
782
+ });
783
+ sub.command("list").description("\u67E5\u8BE2\u8BA2\u9605\u5217\u8868").option("--workspace-id <workspace_id>", "\u6309\u5DE5\u4F5C\u7A7A\u95F4\u7B5B\u9009").option("--agent-config <id>", "\u6309 customer_agent_config_id \u7B5B\u9009").option("--consumer-key <key>", "\u6309 consumer_key \u7B5B\u9009", DEFAULT_CONSUMER_KEY).option("--enabled <bool>", "\u6309\u542F\u7528\u72B6\u6001\u7B5B\u9009 true/false").option("--page <number>", "\u9875\u7801", "1").option("--page-size <number>", "\u6BCF\u9875\u6570\u91CF", "20").action(async (opts) => {
784
+ try {
785
+ const data = await listSubscriptions({
786
+ workspaceId: opts.workspaceId,
787
+ customerAgentConfigId: opts.agentConfig,
788
+ consumerKey: opts.consumerKey,
789
+ enabled: opts.enabled === void 0 ? void 0 : opts.enabled === "true",
790
+ page: Number(opts.page),
791
+ pageSize: Number(opts.pageSize)
792
+ });
793
+ formatOutput({ success: true, data }, program2.opts().table);
794
+ } catch (err) {
795
+ reportCaughtError(err);
796
+ process.exit(toExitCode(err));
797
+ }
798
+ });
799
+ sub.command("update").description("\u66F4\u65B0\u8BA2\u9605\u3002\u53EF\u66F4\u65B0 enabled / events / filters / consumer_name").argument("<subscription_id>", "\u8BA2\u9605 ID\uFF08\u4ECE sub list \u83B7\u53D6\uFF09").requiredOption(
800
+ "--data <json>",
801
+ 'JSON \u6216 @\u6587\u4EF6\uFF0C\u5982 {"enabled":true,"filters":{"changed_fields_any":["sku"]}}'
802
+ ).action(async (subscriptionId, opts) => {
803
+ try {
804
+ const body = parseDataOption(opts.data);
805
+ const data = await updateSubscription(subscriptionId, body);
806
+ formatOutput({ success: true, data }, program2.opts().table);
807
+ } catch (err) {
808
+ reportCaughtError(err);
809
+ process.exit(toExitCode(err));
810
+ }
811
+ });
812
+ const delivery = cc.command("delivery").description("\u5F85\u6D88\u8D39\u9879\u7BA1\u7406\uFF08delivery\uFF09");
813
+ delivery.command("list").description(
814
+ "\u67E5\u8BE2\u5F85\u6D88\u8D39\u9879\u3002\u4E0D\u4F20 --statuses \u65F6\u540E\u7AEF\u9ED8\u8BA4\u8FD4\u56DE pending/claimed/failed\uFF1B\u7528 --cursor-* \u505A\u589E\u91CF\u62C9\u53D6"
815
+ ).option("--consumer-key <key>", "consumer_key", DEFAULT_CONSUMER_KEY).option("--workspace-id <workspace_id>", "\u6309\u5DE5\u4F5C\u7A7A\u95F4\u7B5B\u9009").option("--agent-config <id>", "\u6309 customer_agent_config_id \u7B5B\u9009").option("--statuses <list>", "\u72B6\u6001\u8FC7\u6EE4\uFF0C\u9017\u53F7\u5206\u9694\uFF0C\u5982 pending,claimed").option("--cursor-created-at <iso>", "\u589E\u91CF\u6E38\u6807\uFF1A\u4E0A\u4E00\u9875 next_cursor.cursor_created_at").option("--cursor-delivery-id <id>", "\u589E\u91CF\u6E38\u6807\uFF1A\u4E0A\u4E00\u9875 next_cursor.cursor_delivery_id").option("--limit <number>", "\u5355\u6B21\u62C9\u53D6\u6761\u6570", "20").action(async (opts) => {
816
+ try {
817
+ const data = await listDeliveries(opts.consumerKey, {
818
+ workspaceId: opts.workspaceId,
819
+ customerAgentConfigId: opts.agentConfig,
820
+ statuses: opts.statuses,
821
+ cursorCreatedAt: opts.cursorCreatedAt,
822
+ cursorDeliveryId: opts.cursorDeliveryId,
823
+ limit: Number(opts.limit)
824
+ });
825
+ formatOutput({ success: true, data }, program2.opts().table);
826
+ } catch (err) {
827
+ reportCaughtError(err);
828
+ process.exit(toExitCode(err));
829
+ }
830
+ });
831
+ delivery.command("claim").description("\u8BA4\u9886\u5F85\u6D88\u8D39\u9879\u3002\u5DF2\u88AB\u5176\u4ED6\u5B9E\u4F8B\u8BA4\u9886\u4E14\u672A\u8FC7\u671F\u65F6\u8BA4\u9886\u4F1A\u5931\u8D25").argument("<delivery_id>", "\u5F85\u6D88\u8D39\u9879 ID\uFF08\u4ECE delivery list \u83B7\u53D6\uFF09").option("--consumer-key <key>", "consumer_key", DEFAULT_CONSUMER_KEY).requiredOption("--claimed-by <id>", "\u8BA4\u9886\u8005\u6807\u8BC6\uFF0C\u5EFA\u8BAE\u4F20\u8FD0\u7EF4\u52A9\u624B\u5B9E\u4F8B\u6807\u8BC6").option("--lease-seconds <number>", "\u8BA4\u9886\u6709\u6548\u671F\uFF08\u79D2\uFF09", "300").action(async (deliveryId, opts) => {
832
+ try {
833
+ const data = await claimDelivery(deliveryId, {
834
+ consumer_key: opts.consumerKey,
835
+ claimed_by: opts.claimedBy,
836
+ lease_seconds: Number(opts.leaseSeconds)
837
+ });
838
+ formatOutput({ success: true, data }, program2.opts().table);
839
+ } catch (err) {
840
+ reportCaughtError(err);
841
+ process.exit(toExitCode(err));
842
+ }
843
+ });
844
+ delivery.command("complete").description(
845
+ "\u56DE\u5199\u5904\u7406\u7ED3\u679C\u3002status \u53D6 completed/ignored/failed\uFF1B\u4E0D\u8981\u957F\u65F6\u95F4\u505C\u7559\u5728 claimed \u4E0D\u56DE\u5199"
846
+ ).argument("<delivery_id>", "\u5F85\u6D88\u8D39\u9879 ID\uFF08\u4ECE delivery list \u83B7\u53D6\uFF09").option("--consumer-key <key>", "consumer_key", DEFAULT_CONSUMER_KEY).requiredOption(
847
+ "--status <status>",
848
+ "completed | ignored | failed"
849
+ ).option(
850
+ "--result-action <action>",
851
+ "\u5EFA\u8BAE\u503C: supplemental_knowledge / product_association / rerun_product_study / refresh_recommended_qa / ignore_change / manual_follow_up"
852
+ ).option("--result-note <note>", "\u5904\u7406\u8BF4\u660E").option("--last-error <error>", "status=failed \u65F6\u7684\u9519\u8BEF\u63CF\u8FF0").option("--data <json>", "\u9AD8\u7EA7\uFF1A\u5B8C\u6574\u56DE\u5199 JSON \u6216 @\u6587\u4EF6\uFF0C\u63D0\u4F9B\u65F6\u8986\u76D6\u4E0A\u8FF0\u9009\u9879").action(async (deliveryId, opts) => {
853
+ try {
854
+ const status = opts.status;
855
+ if (!["completed", "ignored", "failed"].includes(status)) {
856
+ reportCaughtError(
857
+ new Error("--status \u53EA\u80FD\u662F completed / ignored / failed")
858
+ );
859
+ process.exit(1);
860
+ }
861
+ const body = opts.data ? parseDataOption(opts.data) : {
862
+ consumer_key: opts.consumerKey,
863
+ status,
864
+ result_action: opts.resultAction,
865
+ result_note: opts.resultNote,
866
+ last_error: opts.lastError
867
+ };
868
+ const data = await completeDelivery(deliveryId, body);
869
+ formatOutput({ success: true, data }, program2.opts().table);
870
+ } catch (err) {
871
+ reportCaughtError(err);
872
+ process.exit(toExitCode(err));
873
+ }
874
+ });
875
+ }
876
+
699
877
  // src/client/chat-history-api.ts
700
878
  async function listChatHistory(opts) {
701
879
  const request = createRequest();
@@ -733,20 +911,28 @@ async function listWorkspaces() {
733
911
  const request = createRequest();
734
912
  return request(getCustomerServiceUrl(), "/v1/client/workspaces", { method: "GET" });
735
913
  }
914
+ async function getDailyDeduction(opts) {
915
+ const request = createRequest();
916
+ return request(
917
+ getCustomerServiceUrl(),
918
+ "/v1/workspace/points_consumes/daily_deduction",
919
+ {
920
+ method: "POST",
921
+ body: { start_time: opts.startTime, end_time: opts.endTime }
922
+ }
923
+ );
924
+ }
736
925
 
737
926
  // src/commands/config.ts
738
927
  function registerConfigCommand(program2) {
739
928
  const config = program2.command("config").description(
740
929
  "\u914D\u7F6E\u7BA1\u7406 \u2014\u2014 API \u5730\u5740\u3001\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u7B49\u6301\u4E45\u5316\u8BBE\u7F6E\u3002\u652F\u6301\u5168\u5C40\u914D\u7F6E\u548C\u76EE\u5F55\u7EA7\u672C\u5730\u914D\u7F6E\uFF08.cs-cli.json\uFF09"
741
930
  );
742
- config.command("set").description("\u8BBE\u7F6E API \u5730\u5740").option("--cs-api <url>", "\u5BA2\u670D API \u5730\u5740").option("--ai-api <url>", "AI API \u5730\u5740").option("--agent-api <url>", "Customer Agent API \u5730\u5740").option("--langfuse-host <url>", "Langfuse \u5730\u5740").option("--langfuse-public-key <key>", "Langfuse Public Key").option("--langfuse-secret-key <key>", "Langfuse Secret Key").action((opts) => {
931
+ config.command("set").description("\u8BBE\u7F6E API \u5730\u5740").option("--cs-api <url>", "\u5BA2\u670D API \u5730\u5740").option("--ai-api <url>", "AI API \u5730\u5740").option("--agent-api <url>", "Customer Agent API \u5730\u5740").action((opts) => {
743
932
  const updates = {};
744
933
  if (opts.csApi) updates.customerServiceApiUrl = opts.csApi;
745
934
  if (opts.aiApi) updates.aiApiUrl = opts.aiApi;
746
935
  if (opts.agentApi) updates.customerAgentApiUrl = opts.agentApi;
747
- if (opts.langfuseHost) updates.langfuseHost = opts.langfuseHost;
748
- if (opts.langfusePublicKey) updates.langfusePublicKey = opts.langfusePublicKey;
749
- if (opts.langfuseSecretKey) updates.langfuseSecretKey = opts.langfuseSecretKey;
750
936
  writeConfig(updates);
751
937
  formatOutput({ success: true, data: readConfig() }, program2.opts().table);
752
938
  });
@@ -1092,22 +1278,16 @@ async function getFlowExecuteLog(opts) {
1092
1278
  const log = await logRes.json();
1093
1279
  return { executeLog, log };
1094
1280
  }
1095
- async function getLangfuseTrace(traceId) {
1096
- const { host, publicKey, secretKey } = getLangfuseConfig();
1097
- const url = `${host}/api/public/traces/${traceId}`;
1098
- const basicAuth = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
1099
- const res = await fetch(url, {
1100
- method: "GET",
1101
- headers: {
1102
- Authorization: `Basic ${basicAuth}`,
1103
- "Content-Type": "application/json"
1281
+ async function getLangfuseTrace(traceId, env) {
1282
+ const request = createRequest();
1283
+ return request(
1284
+ getCustomerServiceUrl(),
1285
+ `/v1/debug/langfuse/trace/${encodeURIComponent(traceId)}`,
1286
+ {
1287
+ method: "GET",
1288
+ query: { env }
1104
1289
  }
1105
- });
1106
- if (!res.ok) {
1107
- const text = await res.text().catch(() => "");
1108
- throw new Error(`Langfuse API error ${res.status}: ${text}`);
1109
- }
1110
- return res.json();
1290
+ );
1111
1291
  }
1112
1292
 
1113
1293
  // src/commands/debug.ts
@@ -1281,9 +1461,13 @@ function registerDebugCommand(program2) {
1281
1461
  });
1282
1462
  debug.command("trace").description(
1283
1463
  "\u6839\u636E Langfuse Trace ID \u83B7\u53D6\u5B8C\u6574\u7684 Trace \u8BE6\u60C5\uFF08\u4ECE debug record \u8FD4\u56DE\u7684 flow_info \u4E2D\u63D0\u53D6 traceId\uFF09"
1284
- ).argument("<trace_id>", "Langfuse Trace ID\uFF08\u4ECE flow_info \u4E2D\u83B7\u53D6\uFF09").action(async (traceId) => {
1464
+ ).argument("<trace_id>", "Langfuse Trace ID\uFF08\u4ECE flow_info \u4E2D\u83B7\u53D6\uFF09").option("--env <env>", "langfuse \u73AF\u5883 dev|prod", "prod").action(async (traceId, opts) => {
1285
1465
  try {
1286
- const data = await getLangfuseTrace(traceId);
1466
+ if (opts.env !== "dev" && opts.env !== "prod") {
1467
+ outputError(1, "--env \u4EC5\u652F\u6301 dev \u6216 prod");
1468
+ process.exit(1);
1469
+ }
1470
+ const data = await getLangfuseTrace(traceId, opts.env);
1287
1471
  formatOutput({ success: true, data }, program2.opts().table);
1288
1472
  } catch (err) {
1289
1473
  reportCaughtError(err);
@@ -3066,6 +3250,13 @@ function registerSACommand(program2) {
3066
3250
  }
3067
3251
 
3068
3252
  // src/commands/workspace.ts
3253
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
3254
+ function toISODate(d) {
3255
+ const yyyy = d.getFullYear();
3256
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
3257
+ const dd = String(d.getDate()).padStart(2, "0");
3258
+ return `${yyyy}-${mm}-${dd}`;
3259
+ }
3069
3260
  function registerWorkspaceCommand(program2) {
3070
3261
  const workspace = program2.command("workspace").description("\u5DE5\u4F5C\u7A7A\u95F4\u7BA1\u7406 \u2014\u2014 \u4E00\u4E2A\u5DE5\u4F5C\u7A7A\u95F4\u5BF9\u5E94\u4E00\u4E2A\u5BA2\u6237/\u5E97\u94FA\uFF0C\u5305\u542B\u8BE5\u5BA2\u6237\u7684\u6240\u6709 Agent");
3071
3262
  workspace.command("list").description("\u5217\u51FA\u5F53\u524D\u8D26\u53F7\u53EF\u8BBF\u95EE\u7684\u6240\u6709\u5DE5\u4F5C\u7A7A\u95F4\u3002\u8FD4\u56DE id \u548C name\uFF0C\u7528\u4E8E config set-workspace").action(async () => {
@@ -3077,6 +3268,28 @@ function registerWorkspaceCommand(program2) {
3077
3268
  process.exit(toExitCode(err));
3078
3269
  }
3079
3270
  });
3271
+ workspace.command("points-consumes-daily").description(
3272
+ "\u6309\u5929\u67E5\u8BE2\u5F53\u524D\u5DE5\u4F5C\u7A7A\u95F4\u7684\u79EF\u5206\u6D88\u8017\u3002\u9ED8\u8BA4\u6700\u8FD1 30 \u5929\uFF08end=\u4ECA\u5929\uFF0Cstart=\u4ECA\u5929-29d\uFF09\uFF0C\u8FD4\u56DE consume_date / total_consume_points \u5217\u8868"
3273
+ ).option("--start <YYYY-MM-DD>", "\u8D77\u59CB\u65E5\u671F\uFF08\u542B\uFF09\uFF0C\u9ED8\u8BA4\u4ECA\u5929-29d").option("--end <YYYY-MM-DD>", "\u7ED3\u675F\u65E5\u671F\uFF08\u542B\uFF09\uFF0C\u9ED8\u8BA4\u4ECA\u5929").action(async (opts) => {
3274
+ try {
3275
+ const now = /* @__PURE__ */ new Date();
3276
+ const defaultEnd = toISODate(now);
3277
+ const defaultStart = toISODate(new Date(now.getTime() - 29 * 24 * 60 * 60 * 1e3));
3278
+ const startTime = opts.start ?? defaultStart;
3279
+ const endTime = opts.end ?? defaultEnd;
3280
+ if (!DATE_RE.test(startTime) || !DATE_RE.test(endTime)) {
3281
+ throw new Error("--start / --end \u9700\u4E3A YYYY-MM-DD \u683C\u5F0F");
3282
+ }
3283
+ if (startTime > endTime) {
3284
+ throw new Error("--start \u4E0D\u80FD\u665A\u4E8E --end");
3285
+ }
3286
+ const data = await getDailyDeduction({ startTime, endTime });
3287
+ formatOutput({ success: true, data }, program2.opts().table);
3288
+ } catch (err) {
3289
+ reportCaughtError(err);
3290
+ process.exit(toExitCode(err));
3291
+ }
3292
+ });
3080
3293
  }
3081
3294
 
3082
3295
  // src/bin.ts
@@ -3084,7 +3297,7 @@ var require2 = createRequire(import.meta.url);
3084
3297
  var { version } = require2("../package.json");
3085
3298
  var program = new Command();
3086
3299
  program.name("cs-cli").description(
3087
- "BetterYeah AI \u5BA2\u670D\u5E73\u53F0 CLI\u3002\u6838\u5FC3\u6982\u5FF5\uFF1Aworkspace\uFF08\u5DE5\u4F5C\u7A7A\u95F4\uFF09\u2192 agent\uFF08AI \u5BA2\u670D\u673A\u5668\u4EBA\uFF09\u2192 SA/product/FAQ\uFF08\u77E5\u8BC6\u914D\u7F6E\uFF09\u2192 issue\uFF08\u5DE5\u5355\uFF09\u2192 debug\uFF08\u8C03\u8BD5\u9A8C\u8BC1\uFF09\u2192 monitor\uFF08\u8FD0\u8425\u76D1\u63A7\uFF09\u2192 repair-record\uFF08\u4FEE\u590D\u5BA1\u8BA1\uFF09\u3002\u6240\u6709\u547D\u4EE4\u9ED8\u8BA4\u8F93\u51FA JSON\uFF0C\u8FFD\u52A0 --table \u53EF\u5207\u6362\u4E3A\u4EBA\u7C7B\u53EF\u8BFB\u8868\u683C"
3300
+ "BetterYeah AI \u5BA2\u670D\u5E73\u53F0 CLI\u3002\u6838\u5FC3\u6982\u5FF5\uFF1Aworkspace\uFF08\u5DE5\u4F5C\u7A7A\u95F4\uFF09\u2192 agent\uFF08AI \u5BA2\u670D\u673A\u5668\u4EBA\uFF09\u2192 SA/product/FAQ\uFF08\u77E5\u8BC6\u914D\u7F6E\uFF09\u2192 issue\uFF08\u5DE5\u5355\uFF09\u2192 debug\uFF08\u8C03\u8BD5\u9A8C\u8BC1\uFF09\u2192 monitor\uFF08\u8FD0\u8425\u76D1\u63A7\uFF09\u2192 repair-record\uFF08\u4FEE\u590D\u5BA1\u8BA1\uFF09\u2192 change-consumer\uFF08\u5546\u54C1\u53D8\u66F4\u4E8B\u4EF6\u6D88\u8D39\uFF09\u3002\u6240\u6709\u547D\u4EE4\u9ED8\u8BA4\u8F93\u51FA JSON\uFF0C\u8FFD\u52A0 --table \u53EF\u5207\u6362\u4E3A\u4EBA\u7C7B\u53EF\u8BFB\u8868\u683C"
3088
3301
  ).version(version).option("--table", "\u4EE5\u8868\u683C\u5F62\u5F0F\u8F93\u51FA\uFF08\u9ED8\u8BA4 JSON\uFF09\u3002\u4EBA\u5DE5\u67E5\u770B\u65F6\u4F7F\u7528", false).option("--workspace <id>", "\u4E34\u65F6\u8986\u76D6\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4 ID\uFF0C\u4E0D\u4FEE\u6539\u6301\u4E45\u5316\u914D\u7F6E").option(
3089
3302
  "--request-timeout <ms>",
3090
3303
  "\u5355\u4E2A HTTP \u8BF7\u6C42\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09\uFF0C\u590D\u6742 Agent \u56DE\u590D\u5EFA\u8BAE\u8C03\u9AD8\u3002\u4E0D\u8981\u5199 --timeout\uFF0C\u90A3\u662F\u5B50\u547D\u4EE4\u7684\u8F6E\u8BE2\u7A97\u53E3\uFF08\u79D2\uFF09",
@@ -3121,6 +3334,7 @@ registerDebugCommand(program);
3121
3334
  registerMonitorCommand(program);
3122
3335
  registerRepairRecordCommand(program);
3123
3336
  registerOperationsRecordCommand(program);
3337
+ registerChangeConsumerCommand(program);
3124
3338
  process.on("uncaughtException", (err) => {
3125
3339
  outputError(3, err.message);
3126
3340
  process.exit(3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/customer-service-cli",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "AI Customer Service CLI - Agent friendly",
5
5
  "type": "module",
6
6
  "main": "./dist/bin.js",