@bty/customer-service-cli 0.5.0 → 0.5.2

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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 (2026-05-28)
4
+
5
+ **修复:`repair-record` 命令 404 问题**
6
+
7
+ - `repair-record-api` 三处请求从 `getCustomerAgentUrl()`(`customer-agent.bantouyan.com`)改为 `getCsAdminUrl()`(`customer-service-admin.betteryeah.com`)。服务端路由部署在 `apps/cs-admin`,之前打错了 host。
8
+ - 鉴权方式从 Cookie(`auth_token`)改为标准 Bearer token,与 cs-admin headless 调用规范一致。
9
+
10
+ Issue #65
11
+
12
+ ## 0.5.1 (2026-05-28)
13
+
14
+ **新增:数据看板(`dashboard`)子命令**
15
+
16
+ - 对接后端 `/v1/dashboard` 看板接口,只产结构化数据,运营报告交由上层(LLM)按场景加工。
17
+ - `dashboard summary [--config-id]`:全局 / 单店 KPI 汇总 + 环比(接待客户数、转人工率、AI 独立承接率)。
18
+ - `dashboard trend [--config-id] [--granularity day/week/month]`:接待量 / 转人工趋势。
19
+ - `dashboard shops`:全部店铺统计列表(默认按转人工客户数降序),横向对比。
20
+ - `dashboard export --start --end [--config-id ...] [--out <path>]`:店铺维度每日明细。两段式(POST 拿 OSS `download_url` → 拉取 CSV),默认解析为 JSON 行(稳定英文 key + 计数转 number),`--out` 落盘原始 CSV。
21
+ - `dashboard intent --config-id`:单店各意图消息级转人工统计。
22
+ - `dashboard intent-conversations --config-id --event-one --event-two`:某意图下会话列表下钻。
23
+ - 复用现有 `request`(`Bearer` + `workspace-id`)/ `getCustomerServiceUrl` / 输出与退出码体系,无额外鉴权逻辑。
24
+ - 口径备注:`接待客户数(customers)` 按客户去重 ≈ 看板"会话/接待量";`AI回复消息数(ai_reply_messages)` 是真实回复条数;`对话轮次 ≈ ai_reply_messages / customers`。**数据 T+1**(`--end` ≤ 昨天),`--start`/`--end` 闭区间。
25
+
3
26
  ## 0.5.0 (2026-05-28)
4
27
 
5
28
  **Breaking change**
package/README.md CHANGED
@@ -582,6 +582,26 @@ cs-cli change-consumer delivery complete <delivery_id> --status completed \
582
582
 
583
583
  **xlsx 二进制输出**:`export --output <path>` 必填路径,stdout 仅承载 JSON 报告 `{success:true, data:{path, bytes}}`,不支持 stdout pipe(避免与 JSON-by-default 冲突)。
584
584
 
585
+ ### 数据看板 (`dashboard`)
586
+
587
+ 转人工率 / 接待量 / AI 回复量等运营指标。**数据 T+1**(`--end` 不能晚于昨天,否则后端返回 400);`--start` / `--end` 为**闭区间**。渠道枚举:`qianniu`(千牛/淘宝)/ `jingdong` / `pinduoduo` / `douyin`。CLI 只产结构化数据,运营报告由上层(如 LLM)按场景加工。
588
+
589
+ | 命令 | 说明 |
590
+ | --- | --- |
591
+ | `dashboard summary [--config-id <id>] [--start <d>] [--end <d>] [--channel <c>]` | 全局或单店 KPI 汇总 + 环比(不传 `--config-id` 为全店铺聚合) |
592
+ | `dashboard trend [--config-id <id>] [--start <d>] [--end <d>] [--granularity day/week/month] [--channel <c>]` | 趋势(每日/周/月接待客户数、转人工数、转人工率),`--granularity` 默认 `day` |
593
+ | `dashboard shops [--start <d>] [--end <d>] [--channel <c>]` | 全部店铺统计列表(默认按转人工客户数降序),用于横向对比 |
594
+ | `dashboard export --start <d> --end <d> [--config-id <id> ...] [--channel <c>] [--out <path>]` | 店铺维度每日明细。默认解析为 JSON 行;`--out` 落盘原始 CSV。`--config-id` 可重复传,不传=全部店铺 |
595
+ | `dashboard intent --config-id <id> [--start <d>] [--end <d>]` | 单店各意图(一级/二级标签)消息级转人工统计,默认按转人工消息数降序 |
596
+ | `dashboard intent-conversations --config-id <id> --event-one <l> --event-two <l> [--start <d>] [--end <d>] [--page <n>] [--page-size <n>]` | 某意图下会话列表下钻 |
597
+
598
+ **口径要点**:
599
+
600
+ - `summary` / `trend` / `shops` / `intent` 直接透传后端 JSON(`total_customers` / `transfer_rate` / `avg_*` 等英文 key)。
601
+ - `export` 把 CSV 表头映射为稳定英文 key:`date / shop_name / channel / customers / ai_independent_customers / ai_independent_rate / transfer_customers / transfer_rate / ai_reply_messages`;计数字段转 number,比率字段保留原始字符串(如 `"46.0%"`)。
602
+ - **接待客户数(customers)** 按客户去重,是看板口径的"会话/接待量";**AI 回复消息数(ai_reply_messages)** 是真实 AI 回复条数;`对话轮次 ≈ ai_reply_messages / customers`。
603
+ - `dashboard export --out <path>` 落盘原始 CSV 时,stdout 仅返回 `{path, bytes, count}`。
604
+
585
605
  ## 输出格式
586
606
 
587
607
  默认输出 JSON:
package/dist/bin.js CHANGED
@@ -1164,6 +1164,293 @@ function registerConversationCommand(program2) {
1164
1164
  });
1165
1165
  }
1166
1166
 
1167
+ // src/client/dashboard-api.ts
1168
+ var DASHBOARD_PREFIX = "/v1/dashboard";
1169
+ async function getDashboardSummary(opts) {
1170
+ const request = createRequest();
1171
+ const path5 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/summary` : `${DASHBOARD_PREFIX}/summary`;
1172
+ const query = {};
1173
+ if (opts.startDate) query.start_date = opts.startDate;
1174
+ if (opts.endDate) query.end_date = opts.endDate;
1175
+ if (opts.channel) query.channel = opts.channel;
1176
+ return request(getCustomerServiceUrl(), path5, { method: "GET", query });
1177
+ }
1178
+ async function getDashboardTrend(opts) {
1179
+ const request = createRequest();
1180
+ const path5 = opts.configId ? `${DASHBOARD_PREFIX}/${opts.configId}/trend` : `${DASHBOARD_PREFIX}/trend`;
1181
+ const query = {};
1182
+ if (opts.startDate) query.start_date = opts.startDate;
1183
+ if (opts.endDate) query.end_date = opts.endDate;
1184
+ if (opts.granularity) query.granularity = opts.granularity;
1185
+ if (opts.channel) query.channel = opts.channel;
1186
+ return request(getCustomerServiceUrl(), path5, { method: "GET", query });
1187
+ }
1188
+ async function listShopStatistics(opts) {
1189
+ const request = createRequest();
1190
+ const query = {};
1191
+ if (opts.startDate) query.start_date = opts.startDate;
1192
+ if (opts.endDate) query.end_date = opts.endDate;
1193
+ if (opts.channel) query.channel = opts.channel;
1194
+ return request(getCustomerServiceUrl(), `${DASHBOARD_PREFIX}/shop-statistics`, {
1195
+ method: "GET",
1196
+ query
1197
+ });
1198
+ }
1199
+ async function getIntentStatistics(opts) {
1200
+ const request = createRequest();
1201
+ const query = {};
1202
+ if (opts.startDate) query.start_date = opts.startDate;
1203
+ if (opts.endDate) query.end_date = opts.endDate;
1204
+ return request(
1205
+ getCustomerServiceUrl(),
1206
+ `${DASHBOARD_PREFIX}/${opts.configId}/intent-statistics`,
1207
+ { method: "GET", query }
1208
+ );
1209
+ }
1210
+ async function listIntentConversations(opts) {
1211
+ const request = createRequest();
1212
+ const query = {
1213
+ event_one: opts.eventOne,
1214
+ event_two: opts.eventTwo
1215
+ };
1216
+ if (opts.startDate) query.start_date = opts.startDate;
1217
+ if (opts.endDate) query.end_date = opts.endDate;
1218
+ if (opts.page) query.page = opts.page;
1219
+ if (opts.pageSize) query.page_size = opts.pageSize;
1220
+ return request(
1221
+ getCustomerServiceUrl(),
1222
+ `${DASHBOARD_PREFIX}/${opts.configId}/intent-conversations`,
1223
+ { method: "GET", query }
1224
+ );
1225
+ }
1226
+ var EXPORT_HEADER_MAP = {
1227
+ \u65E5\u671F: "date",
1228
+ \u5E97\u94FA\u540D\u79F0: "shop_name",
1229
+ \u6E20\u9053: "channel",
1230
+ \u63A5\u5F85\u5BA2\u6237\u6570: "customers",
1231
+ AI\u72EC\u7ACB\u627F\u63A5\u5BA2\u6237\u6570: "ai_independent_customers",
1232
+ AI\u72EC\u7ACB\u627F\u63A5\u7387: "ai_independent_rate",
1233
+ \u8F6C\u4EBA\u5DE5\u5BA2\u6237\u6570: "transfer_customers",
1234
+ \u8F6C\u4EBA\u5DE5\u7387: "transfer_rate",
1235
+ AI\u56DE\u590D\u6D88\u606F\u6570: "ai_reply_messages"
1236
+ };
1237
+ var EXPORT_NUMERIC_KEYS = /* @__PURE__ */ new Set([
1238
+ "customers",
1239
+ "ai_independent_customers",
1240
+ "transfer_customers",
1241
+ "ai_reply_messages"
1242
+ ]);
1243
+ function parseCsv(text) {
1244
+ const s = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
1245
+ const rows = [];
1246
+ let row = [];
1247
+ let field = "";
1248
+ let inQuotes = false;
1249
+ for (let i = 0; i < s.length; i++) {
1250
+ const c = s[i];
1251
+ if (inQuotes) {
1252
+ if (c === '"') {
1253
+ if (s[i + 1] === '"') {
1254
+ field += '"';
1255
+ i++;
1256
+ } else {
1257
+ inQuotes = false;
1258
+ }
1259
+ } else {
1260
+ field += c;
1261
+ }
1262
+ } else if (c === '"') {
1263
+ inQuotes = true;
1264
+ } else if (c === ",") {
1265
+ row.push(field);
1266
+ field = "";
1267
+ } else if (c === "\n") {
1268
+ row.push(field);
1269
+ rows.push(row);
1270
+ row = [];
1271
+ field = "";
1272
+ } else if (c !== "\r") {
1273
+ field += c;
1274
+ }
1275
+ }
1276
+ if (field.length > 0 || row.length > 0) {
1277
+ row.push(field);
1278
+ rows.push(row);
1279
+ }
1280
+ return rows;
1281
+ }
1282
+ function csvToRows(csv) {
1283
+ const table = parseCsv(csv).filter((r) => r.some((cell) => cell.trim() !== ""));
1284
+ if (table.length === 0) return [];
1285
+ const headers = table[0].map((h) => EXPORT_HEADER_MAP[h.trim()] ?? h.trim());
1286
+ return table.slice(1).map((cells) => {
1287
+ const obj = {};
1288
+ headers.forEach((key, idx) => {
1289
+ const raw = cells[idx] ?? "";
1290
+ if (EXPORT_NUMERIC_KEYS.has(key)) {
1291
+ const n = Number(raw);
1292
+ obj[key] = Number.isNaN(n) ? raw : n;
1293
+ } else {
1294
+ obj[key] = raw;
1295
+ }
1296
+ });
1297
+ return obj;
1298
+ });
1299
+ }
1300
+ async function exportShopData(opts) {
1301
+ const request = createRequest();
1302
+ const body = {
1303
+ start_date: opts.startDate,
1304
+ end_date: opts.endDate
1305
+ };
1306
+ if (opts.configIds && opts.configIds.length > 0) body.config_ids = opts.configIds;
1307
+ if (opts.channel) body.channel = opts.channel;
1308
+ const data = await request(
1309
+ getCustomerServiceUrl(),
1310
+ `${DASHBOARD_PREFIX}/export`,
1311
+ { method: "POST", body }
1312
+ );
1313
+ const downloadUrl = data?.download_url;
1314
+ if (!downloadUrl) {
1315
+ throw new APIError(1, "\u5BFC\u51FA\u63A5\u53E3\u672A\u8FD4\u56DE download_url");
1316
+ }
1317
+ const timeoutMs = getRuntimeRequestTimeoutMs() ?? 6e4;
1318
+ const resp = await fetch(downloadUrl, { signal: AbortSignal.timeout(timeoutMs) });
1319
+ if (!resp.ok) {
1320
+ throw new APIError(resp.status, `\u4E0B\u8F7D\u5BFC\u51FA CSV \u5931\u8D25 HTTP ${resp.status}`);
1321
+ }
1322
+ const csv = await resp.text();
1323
+ return { downloadUrl, csv, rows: csvToRows(csv) };
1324
+ }
1325
+
1326
+ // src/utils/file-output.ts
1327
+ import fs5 from "fs";
1328
+ import path4 from "path";
1329
+ async function writeBinaryToFile(filePath, buffer) {
1330
+ try {
1331
+ const dir = path4.dirname(filePath);
1332
+ fs5.mkdirSync(dir, { recursive: true });
1333
+ fs5.writeFileSync(filePath, buffer);
1334
+ } catch (err) {
1335
+ const msg = err instanceof Error ? err.message : String(err);
1336
+ throw new APIError(1, `\u6587\u4EF6\u5199\u5165\u5931\u8D25: ${msg}`);
1337
+ }
1338
+ return { path: filePath, bytes: buffer.length };
1339
+ }
1340
+
1341
+ // src/commands/dashboard.ts
1342
+ function collect(value, previous) {
1343
+ return previous.concat([value]);
1344
+ }
1345
+ function registerDashboardCommand(program2) {
1346
+ const dashboard = program2.command("dashboard").description(
1347
+ "\u6570\u636E\u770B\u677F \u2014\u2014 \u8F6C\u4EBA\u5DE5\u7387/\u63A5\u5F85\u91CF/AI\u56DE\u590D\u91CF\u7B49\u8FD0\u8425\u6307\u6807\uFF08\u6570\u636E T+1\uFF0Cend \u2264 \u6628\u5929\uFF1Bstart/end \u4E3A\u95ED\u533A\u95F4\uFF09\u3002\u53EA\u4EA7\u7ED3\u6784\u5316\u6570\u636E\uFF0C\u62A5\u544A\u7531\u4E0A\u5C42\u6309\u573A\u666F\u52A0\u5DE5"
1348
+ );
1349
+ dashboard.command("summary").description(
1350
+ "\u5168\u5C40\u6216\u5355\u5E97\u94FA KPI \u6C47\u603B + \u73AF\u6BD4\uFF08\u63A5\u5F85\u5BA2\u6237\u6570\u3001\u8F6C\u4EBA\u5DE5\u7387\u3001AI\u72EC\u7ACB\u627F\u63A5\u7387\uFF09\u3002\u4F20 --config-id \u67E5\u5355\u5E97"
1351
+ ).option("--config-id <id>", "\u5E97\u94FA\u914D\u7F6E ID\uFF08\u4E0D\u4F20=\u5168\u5E97\u94FA\u805A\u5408\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5").option("--end <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5\uFF0C\u4E0D\u80FD\u665A\u4E8E\u6628\u5929").option("--channel <channel>", "\u6E20\u9053\u7B5B\u9009: qianniu | jingdong | pinduoduo | douyin").action(async (opts) => {
1352
+ try {
1353
+ const data = await getDashboardSummary({
1354
+ configId: opts.configId,
1355
+ startDate: opts.start,
1356
+ endDate: opts.end,
1357
+ channel: opts.channel
1358
+ });
1359
+ formatOutput({ success: true, data }, program2.opts().table);
1360
+ } catch (err) {
1361
+ reportCaughtError(err);
1362
+ process.exit(toExitCode(err));
1363
+ }
1364
+ });
1365
+ dashboard.command("trend").description(
1366
+ "\u5168\u5C40\u6216\u5355\u5E97\u94FA\u8D8B\u52BF\uFF08\u6BCF\u65E5/\u5468/\u6708\u63A5\u5F85\u5BA2\u6237\u6570\u3001\u8F6C\u4EBA\u5DE5\u6570\u3001\u8F6C\u4EBA\u5DE5\u7387\uFF09\u3002\u4F20 --config-id \u67E5\u5355\u5E97"
1367
+ ).option("--config-id <id>", "\u5E97\u94FA\u914D\u7F6E ID\uFF08\u4E0D\u4F20=\u5168\u5E97\u94FA\u805A\u5408\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5").option("--end <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5\uFF0C\u4E0D\u80FD\u665A\u4E8E\u6628\u5929").option("--granularity <g>", "\u7C92\u5EA6: day | week | month", "day").option("--channel <channel>", "\u6E20\u9053\u7B5B\u9009: qianniu | jingdong | pinduoduo | douyin").action(async (opts) => {
1368
+ try {
1369
+ const data = await getDashboardTrend({
1370
+ configId: opts.configId,
1371
+ startDate: opts.start,
1372
+ endDate: opts.end,
1373
+ granularity: opts.granularity,
1374
+ channel: opts.channel
1375
+ });
1376
+ formatOutput({ success: true, data }, program2.opts().table);
1377
+ } catch (err) {
1378
+ reportCaughtError(err);
1379
+ process.exit(toExitCode(err));
1380
+ }
1381
+ });
1382
+ dashboard.command("shops").description("\u5168\u90E8\u5E97\u94FA\u7EDF\u8BA1\u5217\u8868\uFF08\u9ED8\u8BA4\u6309\u8F6C\u4EBA\u5DE5\u5BA2\u6237\u6570\u964D\u5E8F\uFF09\uFF0C\u7528\u4E8E\u6A2A\u5411\u5BF9\u6BD4").option("--start <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5").option("--end <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5\uFF0C\u4E0D\u80FD\u665A\u4E8E\u6628\u5929").option("--channel <channel>", "\u6E20\u9053\u7B5B\u9009: qianniu | jingdong | pinduoduo | douyin").action(async (opts) => {
1383
+ try {
1384
+ const data = await listShopStatistics({
1385
+ startDate: opts.start,
1386
+ endDate: opts.end,
1387
+ channel: opts.channel
1388
+ });
1389
+ formatOutput({ success: true, data }, program2.opts().table);
1390
+ } catch (err) {
1391
+ reportCaughtError(err);
1392
+ process.exit(toExitCode(err));
1393
+ }
1394
+ });
1395
+ dashboard.command("export").description(
1396
+ "\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"
1397
+ ).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) => {
1398
+ try {
1399
+ const result = await exportShopData({
1400
+ startDate: opts.start,
1401
+ endDate: opts.end,
1402
+ configIds: opts.configId,
1403
+ channel: opts.channel
1404
+ });
1405
+ if (opts.out) {
1406
+ const { path: path5, bytes } = await writeBinaryToFile(
1407
+ opts.out,
1408
+ Buffer.from(result.csv, "utf-8")
1409
+ );
1410
+ formatOutput(
1411
+ { success: true, data: { path: path5, bytes, count: result.rows.length } },
1412
+ program2.opts().table
1413
+ );
1414
+ } else {
1415
+ formatOutput({ success: true, data: result.rows }, program2.opts().table);
1416
+ }
1417
+ } catch (err) {
1418
+ reportCaughtError(err);
1419
+ process.exit(toExitCode(err));
1420
+ }
1421
+ });
1422
+ dashboard.command("intent").description("\u5355\u5E97\u94FA\u5404\u610F\u56FE\uFF08\u4E00\u7EA7/\u4E8C\u7EA7\u6807\u7B7E\uFF09\u7684\u6D88\u606F\u7EA7\u8F6C\u4EBA\u5DE5\u7EDF\u8BA1\uFF0C\u9ED8\u8BA4\u6309\u8F6C\u4EBA\u5DE5\u6D88\u606F\u6570\u964D\u5E8F").requiredOption("--config-id <id>", "\u5E97\u94FA\u914D\u7F6E ID\uFF08\u5FC5\u586B\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5").option("--end <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u6628\u65E5\uFF0C\u4E0D\u80FD\u665A\u4E8E\u6628\u5929").action(async (opts) => {
1423
+ try {
1424
+ const data = await getIntentStatistics({
1425
+ configId: opts.configId,
1426
+ startDate: opts.start,
1427
+ endDate: opts.end
1428
+ });
1429
+ formatOutput({ success: true, data }, program2.opts().table);
1430
+ } catch (err) {
1431
+ reportCaughtError(err);
1432
+ process.exit(toExitCode(err));
1433
+ }
1434
+ });
1435
+ dashboard.command("intent-conversations").description("\u67D0\u610F\u56FE\u4E0B\u7684\u4F1A\u8BDD\u5217\u8868\u4E0B\u94BB\uFF08\u542B\u8BE5\u610F\u56FE\u5728\u4F1A\u8BDD\u4E2D\u7684\u8F6C\u4EBA\u5DE5\u6B21\u6570\uFF09").requiredOption("--config-id <id>", "\u5E97\u94FA\u914D\u7F6E ID\uFF08\u5FC5\u586B\uFF09").requiredOption("--event-one <label>", "\u4E00\u7EA7\u610F\u56FE\uFF08\u5FC5\u586B\uFF09").requiredOption("--event-two <label>", "\u4E8C\u7EA7\u610F\u56FE\uFF08\u5FC5\u586B\uFF09").option("--start <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)").option("--end <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)").option("--page <number>", "\u9875\u7801", "1").option("--page-size <number>", "\u6BCF\u9875\u6570\u91CF\uFF08\u6700\u5927 100\uFF09", "20").action(async (opts) => {
1436
+ try {
1437
+ const data = await listIntentConversations({
1438
+ configId: opts.configId,
1439
+ eventOne: opts.eventOne,
1440
+ eventTwo: opts.eventTwo,
1441
+ startDate: opts.start,
1442
+ endDate: opts.end,
1443
+ page: Number(opts.page),
1444
+ pageSize: Number(opts.pageSize)
1445
+ });
1446
+ formatOutput({ success: true, data }, program2.opts().table);
1447
+ } catch (err) {
1448
+ reportCaughtError(err);
1449
+ process.exit(toExitCode(err));
1450
+ }
1451
+ });
1452
+ }
1453
+
1167
1454
  // src/client/debug-api.ts
1168
1455
  var DEFAULT_FLOW_WORKSPACE_ID = "531c14d1ece047cbaec22914e1f1364d";
1169
1456
  async function createDebugConversation(opts) {
@@ -1801,7 +2088,7 @@ async function batchReassignOwner(params) {
1801
2088
  }
1802
2089
 
1803
2090
  // src/utils/batch-input.ts
1804
- import fs5 from "fs";
2091
+ import fs6 from "fs";
1805
2092
  function parseIdsInput(opts) {
1806
2093
  const hasIds = opts.ids !== void 0 && opts.ids.trim() !== "";
1807
2094
  const hasFile = opts.idsFile !== void 0 && opts.idsFile.trim() !== "";
@@ -1818,7 +2105,7 @@ function parseIdsInput(opts) {
1818
2105
  const filePath = opts.idsFile;
1819
2106
  let content;
1820
2107
  try {
1821
- content = fs5.readFileSync(filePath, "utf8");
2108
+ content = fs6.readFileSync(filePath, "utf8");
1822
2109
  } catch (err) {
1823
2110
  const msg = err instanceof Error ? err.message : String(err);
1824
2111
  throw new Error(`\u8BFB\u53D6 --ids-file \u5931\u8D25: ${msg}`);
@@ -1840,7 +2127,7 @@ function parseIdsInput(opts) {
1840
2127
  }
1841
2128
 
1842
2129
  // src/utils/issue-batch-input.ts
1843
- import fs6 from "fs";
2130
+ import fs7 from "fs";
1844
2131
  var PRIORITY_VALUES = ["critical", "high", "medium", "low"];
1845
2132
  function readSource(opts) {
1846
2133
  const hasFile = typeof opts.file === "string" && opts.file.trim() !== "";
@@ -1853,7 +2140,7 @@ function readSource(opts) {
1853
2140
  }
1854
2141
  if (hasFile) {
1855
2142
  try {
1856
- return fs6.readFileSync(opts.file, "utf8");
2143
+ return fs7.readFileSync(opts.file, "utf8");
1857
2144
  } catch (err) {
1858
2145
  const msg = err instanceof Error ? err.message : String(err);
1859
2146
  throw new Error(`\u8BFB\u53D6 --file \u5931\u8D25: ${msg}`);
@@ -2485,7 +2772,7 @@ async function resolveSessionsForIssues(issues, workspaceId) {
2485
2772
  }
2486
2773
 
2487
2774
  // src/commands/knowledge.ts
2488
- import fs7 from "fs";
2775
+ import fs8 from "fs";
2489
2776
 
2490
2777
  // src/client/knowledge-api.ts
2491
2778
  async function listExternalKnowledge(opts) {
@@ -2591,7 +2878,7 @@ async function deleteKnowledgeContent(opts) {
2591
2878
  // src/commands/knowledge.ts
2592
2879
  function readContentArg(value) {
2593
2880
  if (value.startsWith("@")) {
2594
- return fs7.readFileSync(value.slice(1), "utf-8");
2881
+ return fs8.readFileSync(value.slice(1), "utf-8");
2595
2882
  }
2596
2883
  return value;
2597
2884
  }
@@ -3720,29 +4007,23 @@ async function listRepairRecords(opts) {
3720
4007
  if (opts.workspaceId) query.workspace_id = opts.workspaceId;
3721
4008
  if (opts.page) query.page = opts.page;
3722
4009
  if (opts.pageSize) query.page_size = opts.pageSize;
3723
- return request(getCustomerAgentUrl(), PATH_PREFIX2, {
4010
+ return request(getCsAdminUrl(), PATH_PREFIX2, {
3724
4011
  method: "GET",
3725
- query,
3726
- headers: buildCookieHeaders(),
3727
- skipAuth: true
4012
+ query
3728
4013
  });
3729
4014
  }
3730
4015
  async function createRepairRecord(data) {
3731
4016
  const request = createRequest();
3732
- return request(getCustomerAgentUrl(), PATH_PREFIX2, {
4017
+ return request(getCsAdminUrl(), PATH_PREFIX2, {
3733
4018
  method: "POST",
3734
- body: data,
3735
- headers: buildCookieHeaders(),
3736
- skipAuth: true
4019
+ body: data
3737
4020
  });
3738
4021
  }
3739
4022
  async function updateRepairRecord(recordId, data) {
3740
4023
  const request = createRequest();
3741
- return request(getCustomerAgentUrl(), `${PATH_PREFIX2}/${recordId}`, {
4024
+ return request(getCsAdminUrl(), `${PATH_PREFIX2}/${recordId}`, {
3742
4025
  method: "PUT",
3743
- body: data,
3744
- headers: buildCookieHeaders(),
3745
- skipAuth: true
4026
+ body: data
3746
4027
  });
3747
4028
  }
3748
4029
 
@@ -4014,21 +4295,6 @@ function registerSACommand(program2) {
4014
4295
  // src/commands/testset.ts
4015
4296
  import fs9 from "fs";
4016
4297
 
4017
- // src/utils/file-output.ts
4018
- import fs8 from "fs";
4019
- import path4 from "path";
4020
- async function writeBinaryToFile(filePath, buffer) {
4021
- try {
4022
- const dir = path4.dirname(filePath);
4023
- fs8.mkdirSync(dir, { recursive: true });
4024
- fs8.writeFileSync(filePath, buffer);
4025
- } catch (err) {
4026
- const msg = err instanceof Error ? err.message : String(err);
4027
- throw new APIError(1, `\u6587\u4EF6\u5199\u5165\u5931\u8D25: ${msg}`);
4028
- }
4029
- return { path: filePath, bytes: buffer.length };
4030
- }
4031
-
4032
4298
  // src/client/testset-api.ts
4033
4299
  function unwrapPaginated(raw, fallbackPageSize) {
4034
4300
  return {
@@ -4698,7 +4964,7 @@ var require2 = createRequire(import.meta.url);
4698
4964
  var { version } = require2("../package.json");
4699
4965
  var program = new Command();
4700
4966
  program.name("cs-cli").description(
4701
- "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"
4967
+ "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 dashboard\uFF08\u6570\u636E\u770B\u677F\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"
4702
4968
  ).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(
4703
4969
  "--request-timeout <ms>",
4704
4970
  "\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",
@@ -4733,6 +4999,7 @@ registerFaqCommand(program);
4733
4999
  registerConversationCommand(program);
4734
5000
  registerDebugCommand(program);
4735
5001
  registerMonitorCommand(program);
5002
+ registerDashboardCommand(program);
4736
5003
  registerRepairRecordCommand(program);
4737
5004
  registerOperationsRecordCommand(program);
4738
5005
  registerChangeConsumerCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bty/customer-service-cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "AI Customer Service CLI - Agent friendly",
5
5
  "type": "module",
6
6
  "main": "./dist/bin.js",