@bty/customer-service-cli 0.6.8 → 0.6.9

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,7 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.6.9 (2026-08-24)
4
4
 
5
+ - 新增 `channel-metric` 命令组,查询千牛等渠道店铺客服的每日排行指标(`channel_metric_snapshot`):
6
+ - `channel-metric query --workspace-id <ws> --start <date> --end <date> [--agent <id>] [--platform <channel>]`:按 workspace + 日期区间(+ 可选 agent / 渠道)查询指标,按日期×渠道分组输出。
7
+ - `channel-metric backfill --channel <channel> --start <date> --end <date> [--workspace-id <ws>...]`:手动补数据,采集指定日期区间的渠道指标;`--workspace-id` 可重复传,缺省采集所有已开启配置的 workspace。
5
8
  - 新增 `media upload` 命令与 `product supplement` 子命令组,支持商品「补充知识」图片/视频维护,与前端上传行为一致:
6
9
  - `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
10
  - `product supplement list --agent <id> --product-id <id>`:读取并解析商品「补充知识」条目(容错解析数组 / JSON 字符串 / 多层编码)。
package/dist/bin.js CHANGED
@@ -1095,6 +1095,115 @@ function registerChangeConsumerCommand(program2) {
1095
1095
  });
1096
1096
  }
1097
1097
 
1098
+ // src/commands/channel-metric.ts
1099
+ import Table2 from "cli-table3";
1100
+
1101
+ // src/client/channel-metric-api.ts
1102
+ var CHANNEL_METRIC_PREFIX = "/v1/channel-metric";
1103
+ async function backfillChannelMetrics(opts) {
1104
+ const request = createRequest();
1105
+ const body = {
1106
+ channel: opts.channel,
1107
+ start_date: opts.startDate,
1108
+ end_date: opts.endDate
1109
+ };
1110
+ if (opts.workspaceIds && opts.workspaceIds.length > 0) {
1111
+ body.workspace_ids = opts.workspaceIds;
1112
+ }
1113
+ return request(getObservabilityUrl(), `${CHANNEL_METRIC_PREFIX}/backfill`, {
1114
+ method: "POST",
1115
+ body
1116
+ });
1117
+ }
1118
+ async function queryChannelMetrics(opts) {
1119
+ const request = createRequest();
1120
+ const query = {
1121
+ workspace_id: opts.workspaceId,
1122
+ start_date: opts.startDate,
1123
+ end_date: opts.endDate
1124
+ };
1125
+ if (opts.configId) query.config_id = opts.configId;
1126
+ if (opts.channel) query.channel = opts.channel;
1127
+ return request(getObservabilityUrl(), `${CHANNEL_METRIC_PREFIX}/metrics`, {
1128
+ method: "GET",
1129
+ query
1130
+ });
1131
+ }
1132
+
1133
+ // src/commands/channel-metric.ts
1134
+ function collect(value, previous) {
1135
+ return previous.concat([value]);
1136
+ }
1137
+ function renderGroupedTable(groups) {
1138
+ let total = 0;
1139
+ for (const group of groups) {
1140
+ process.stdout.write(`
1141
+ === ${group.stat_date} \xB7 ${group.channel}\uFF08${group.items.length} \u4E2A\u8D26\u53F7\uFF09===
1142
+ `);
1143
+ const first = group.items[0];
1144
+ const headers = (first?.metrics_json ?? []).map((field) => field.label);
1145
+ const table = new Table2({ head: ["account_id", ...headers] });
1146
+ for (const row of group.items) {
1147
+ table.push([
1148
+ String(row.account_id),
1149
+ ...row.metrics_json.map(
1150
+ (field) => field.value === void 0 || field.value === null ? "-" : String(field.value)
1151
+ )
1152
+ ]);
1153
+ }
1154
+ process.stdout.write(`${table.toString()}
1155
+ `);
1156
+ total += group.items.length;
1157
+ }
1158
+ process.stdout.write(`
1159
+ \u5171 ${total} \u6761\u8BB0\u5F55
1160
+ `);
1161
+ }
1162
+ function registerChannelMetricCommand(program2) {
1163
+ const channelMetric = program2.command("channel-metric").description("\u6E20\u9053\u76D1\u63A7\u6307\u6807\u67E5\u8BE2 \u2014\u2014 \u67E5\u8BE2\u5343\u725B\u7B49\u6E20\u9053\u5E97\u94FA\u5BA2\u670D\u7684\u6BCF\u65E5\u6392\u884C\u6307\u6807\uFF08channel_metric_snapshot\uFF09");
1164
+ channelMetric.command("query").description(
1165
+ "\u6309 workspace + \u65E5\u671F\u533A\u95F4\uFF08+ \u53EF\u9009 agent / \u6E20\u9053\uFF09\u67E5\u8BE2\u6307\u6807\uFF0C\u6309\u65E5\u671F\xD7\u6E20\u9053\u5206\u7EC4\u8F93\u51FA\u3002\u4E0D\u4F20 --agent \u8F93\u51FA\u8BE5 workspace \u4E0B\u5168\u90E8 agent\uFF0C\u4E0D\u4F20 --platform \u8F93\u51FA\u5168\u90E8\u6E20\u9053"
1166
+ ).requiredOption("--workspace-id <workspace_id>", "\u5DE5\u4F5C\u7A7A\u95F4 ID\uFF08\u5FC5\u4F20\uFF0C\u4E0E\u670D\u52A1\u7AEF\u5F52\u5C5E\u6821\u9A8C\u4E00\u81F4\uFF09").option("--agent <agent_id>", "Agent \u914D\u7F6E ID\uFF1B\u7F3A\u7701\u67E5\u8BE2\u8BE5 workspace \u4E0B\u5168\u90E8 agent").requiredOption("--start <date>", "\u8D77\u59CB\u65E5\u671F\uFF08YYYY-MM-DD\uFF09").requiredOption("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF09").option("--platform <channel>", "\u6E20\u9053\uFF08platform\uFF09\uFF0C\u5982 qianniu\uFF1B\u7F3A\u7701\u67E5\u8BE2\u5168\u90E8\u6E20\u9053").action(async (opts) => {
1167
+ try {
1168
+ setRuntimeWorkspaceId(opts.workspaceId);
1169
+ const data = await queryChannelMetrics({
1170
+ workspaceId: opts.workspaceId,
1171
+ configId: opts.agent,
1172
+ startDate: opts.start,
1173
+ endDate: opts.end,
1174
+ channel: opts.platform
1175
+ });
1176
+ const groups = data?.groups ?? [];
1177
+ if (groups.length === 0) {
1178
+ process.stdout.write("(empty)\n");
1179
+ return;
1180
+ }
1181
+ renderGroupedTable(groups);
1182
+ } catch (err) {
1183
+ reportCaughtError(err);
1184
+ process.exit(toExitCode(err));
1185
+ }
1186
+ });
1187
+ channelMetric.command("backfill").description(
1188
+ "\u624B\u52A8\u8865\u6570\u636E\uFF1A\u91C7\u96C6\u6307\u5B9A\u65E5\u671F\u533A\u95F4\u7684\u6E20\u9053\u6307\u6807\u3002\u53EF\u6307\u5B9A workspace\uFF08\u53EF\u91CD\u590D\u4F20\uFF09\uFF1B\u4E0D\u4F20 --workspace-id = \u91C7\u96C6\u6240\u6709\u5DF2\u5F00\u542F\u914D\u7F6E\u7684 workspace"
1189
+ ).requiredOption("--channel <channel>", "\u6E20\u9053\uFF08platform\uFF09\uFF0C\u5982 qianniu").requiredOption("--start <date>", "\u8D77\u59CB\u65E5\u671F\uFF08YYYY-MM-DD\uFF09").requiredOption("--end <date>", "\u7ED3\u675F\u65E5\u671F\uFF08YYYY-MM-DD\uFF09").option("--workspace-id <workspace_id>", "\u5DE5\u4F5C\u7A7A\u95F4 ID\uFF08\u53EF\u91CD\u590D\u4F20\uFF1B\u7F3A\u7701 = \u91C7\u96C6\u6240\u6709\u5DF2\u5F00\u542F\u914D\u7F6E\u7684 ws\uFF09", collect).action(async (opts) => {
1190
+ try {
1191
+ const workspaceIds = opts.workspaceId?.length ? opts.workspaceId : void 0;
1192
+ setRuntimeWorkspaceId(workspaceIds?.[0]);
1193
+ const data = await backfillChannelMetrics({
1194
+ channel: opts.channel,
1195
+ startDate: opts.start,
1196
+ endDate: opts.end,
1197
+ workspaceIds
1198
+ });
1199
+ formatOutput({ success: true, data }, program2.opts().table);
1200
+ } catch (err) {
1201
+ reportCaughtError(err);
1202
+ process.exit(toExitCode(err));
1203
+ }
1204
+ });
1205
+ }
1206
+
1098
1207
  // src/client/chat-history-api.ts
1099
1208
  async function listChatHistory(opts) {
1100
1209
  const request = createRequest();
@@ -1608,7 +1717,7 @@ async function writeBinaryToFile(filePath, buffer) {
1608
1717
  }
1609
1718
 
1610
1719
  // src/commands/dashboard.ts
1611
- function collect(value, previous) {
1720
+ function collect2(value, previous) {
1612
1721
  return previous.concat([value]);
1613
1722
  }
1614
1723
  function registerDashboardCommand(program2) {
@@ -1663,7 +1772,7 @@ function registerDashboardCommand(program2) {
1663
1772
  });
1664
1773
  dashboard.command("export").description(
1665
1774
  "\u5BFC\u51FA\u5E97\u94FA\u7EF4\u5EA6\u6BCF\u65E5\u660E\u7EC6\uFF08\u63A5\u5F85\u5BA2\u6237\u6570/AI\u72EC\u7ACB\u627F\u63A5/\u8F6C\u4EBA\u5DE5/AI\u56DE\u590D\u6D88\u606F\u6570\uFF09\u3002\u9ED8\u8BA4\u89E3\u6790\u4E3A JSON \u884C\uFF1B--out \u843D\u76D8\u539F\u59CB CSV\u3002\u53EF\u91CD\u590D --config-id \u9650\u5B9A\u5E97\u94FA"
1666
- ).requiredOption("--start <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u5FC5\u586B").requiredOption("--end <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u5FC5\u586B\uFF0C\u4E0D\u80FD\u665A\u4E8E\u6628\u5929").option("--config-id <id>", "\u5E97\u94FA\u914D\u7F6E ID\uFF08\u53EF\u91CD\u590D\u4F20\uFF1B\u4E0D\u4F20=\u5168\u90E8\u5E97\u94FA\uFF09", collect, []).option("--channel <channel>", "\u6E20\u9053\u7B5B\u9009: qianniu | jingdong | pinduoduo | douyin").option("--out <path>", "\u843D\u76D8\u539F\u59CB CSV \u8DEF\u5F84\uFF08\u7236\u76EE\u5F55\u81EA\u52A8 mkdir -p\uFF09").action(async (opts) => {
1775
+ ).requiredOption("--start <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u5FC5\u586B").requiredOption("--end <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u5FC5\u586B\uFF0C\u4E0D\u80FD\u665A\u4E8E\u6628\u5929").option("--config-id <id>", "\u5E97\u94FA\u914D\u7F6E ID\uFF08\u53EF\u91CD\u590D\u4F20\uFF1B\u4E0D\u4F20=\u5168\u90E8\u5E97\u94FA\uFF09", collect2, []).option("--channel <channel>", "\u6E20\u9053\u7B5B\u9009: qianniu | jingdong | pinduoduo | douyin").option("--out <path>", "\u843D\u76D8\u539F\u59CB CSV \u8DEF\u5F84\uFF08\u7236\u76EE\u5F55\u81EA\u52A8 mkdir -p\uFF09").action(async (opts) => {
1667
1776
  try {
1668
1777
  const result = await exportShopData({
1669
1778
  startDate: opts.start,
@@ -7470,6 +7579,7 @@ registerMediaCommand(program);
7470
7579
  registerKnowledgeCommand(program);
7471
7580
  registerActivityCommand(program);
7472
7581
  registerIssueCommand(program);
7582
+ registerChannelMetricCommand(program);
7473
7583
  registerChatHistoryCommand(program);
7474
7584
  registerFaqCommand(program);
7475
7585
  registerConversationCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/customer-service-cli",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "AI Customer Service CLI - Agent friendly",
5
5
  "type": "module",
6
6
  "main": "./dist/bin.js",