@bty/customer-service-cli 0.4.8 → 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 +309 -0
- package/README.md +103 -4
- package/dist/bin.js +905 -74
- package/package.json +3 -2
package/dist/bin.js
CHANGED
|
@@ -180,10 +180,10 @@ function readCache() {
|
|
|
180
180
|
return null;
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
-
function writeCache(
|
|
183
|
+
function writeCache(cache3) {
|
|
184
184
|
const dir = getConfigDir();
|
|
185
185
|
fs2.mkdirSync(dir, { recursive: true });
|
|
186
|
-
fs2.writeFileSync(getCachePath(), JSON.stringify(
|
|
186
|
+
fs2.writeFileSync(getCachePath(), JSON.stringify(cache3, null, 2));
|
|
187
187
|
}
|
|
188
188
|
function compareSemver(current, latest) {
|
|
189
189
|
const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
|
|
@@ -235,10 +235,10 @@ async function checkForUpdate(currentVersion) {
|
|
|
235
235
|
return null;
|
|
236
236
|
}
|
|
237
237
|
try {
|
|
238
|
-
const
|
|
238
|
+
const cache3 = readCache();
|
|
239
239
|
const now = Date.now();
|
|
240
|
-
if (
|
|
241
|
-
return compareSemver(currentVersion,
|
|
240
|
+
if (cache3 && now - cache3.lastCheckedAt < CHECK_INTERVAL_MS) {
|
|
241
|
+
return compareSemver(currentVersion, cache3.latestVersion) < 0 ? formatUpdateMessage(currentVersion, cache3.latestVersion) : null;
|
|
242
242
|
}
|
|
243
243
|
const latestVersion = await fetchLatestVersion();
|
|
244
244
|
writeCache({ latestVersion, lastCheckedAt: now });
|
|
@@ -437,6 +437,7 @@ 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_CS_ADMIN_URL = "https://customer-service-admin.betteryeah.com";
|
|
440
441
|
function getCustomerServiceUrl() {
|
|
441
442
|
const envUrl = process.env.CS_CS_API_URL;
|
|
442
443
|
if (envUrl) return envUrl;
|
|
@@ -455,6 +456,12 @@ function getCustomerAgentUrl() {
|
|
|
455
456
|
const config = readConfig();
|
|
456
457
|
return config?.customerAgentApiUrl ?? DEFAULT_CUSTOMER_AGENT_API_URL;
|
|
457
458
|
}
|
|
459
|
+
function getCsAdminUrl() {
|
|
460
|
+
const envUrl = process.env.CS_CS_ADMIN_URL;
|
|
461
|
+
if (envUrl) return envUrl;
|
|
462
|
+
const config = readConfig();
|
|
463
|
+
return config?.csAdminUrl ?? DEFAULT_CS_ADMIN_URL;
|
|
464
|
+
}
|
|
458
465
|
function getWorkspaceId(overrideWorkspaceId) {
|
|
459
466
|
const lockState = readEnvLockState();
|
|
460
467
|
if (lockState.workspaceId) {
|
|
@@ -1157,6 +1164,293 @@ function registerConversationCommand(program2) {
|
|
|
1157
1164
|
});
|
|
1158
1165
|
}
|
|
1159
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
|
+
|
|
1160
1454
|
// src/client/debug-api.ts
|
|
1161
1455
|
var DEFAULT_FLOW_WORKSPACE_ID = "531c14d1ece047cbaec22914e1f1364d";
|
|
1162
1456
|
async function createDebugConversation(opts) {
|
|
@@ -1784,7 +2078,387 @@ async function getIssueStats(opts) {
|
|
|
1784
2078
|
return result.data ?? result;
|
|
1785
2079
|
}
|
|
1786
2080
|
|
|
2081
|
+
// src/client/workbench-issues-api.ts
|
|
2082
|
+
async function batchReassignOwner(params) {
|
|
2083
|
+
const request = createRequest();
|
|
2084
|
+
return request(getCsAdminUrl(), "/api/workbench/issues/batch-owner", {
|
|
2085
|
+
method: "POST",
|
|
2086
|
+
body: { issueIds: params.issueIds, ownerUserId: params.ownerUserId }
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// src/utils/batch-input.ts
|
|
2091
|
+
import fs6 from "fs";
|
|
2092
|
+
function parseIdsInput(opts) {
|
|
2093
|
+
const hasIds = opts.ids !== void 0 && opts.ids.trim() !== "";
|
|
2094
|
+
const hasFile = opts.idsFile !== void 0 && opts.idsFile.trim() !== "";
|
|
2095
|
+
if (hasIds && hasFile) {
|
|
2096
|
+
throw new Error("--ids \u4E0E --ids-file \u4E92\u65A5\uFF0C\u8BF7\u53EA\u63D0\u4F9B\u5176\u4E00");
|
|
2097
|
+
}
|
|
2098
|
+
if (!hasIds && !hasFile) {
|
|
2099
|
+
throw new Error("\u9700\u8981\u63D0\u4F9B --ids \u6216 --ids-file \u4E4B\u4E00");
|
|
2100
|
+
}
|
|
2101
|
+
let raw;
|
|
2102
|
+
if (hasIds) {
|
|
2103
|
+
raw = opts.ids.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2104
|
+
} else {
|
|
2105
|
+
const filePath = opts.idsFile;
|
|
2106
|
+
let content;
|
|
2107
|
+
try {
|
|
2108
|
+
content = fs6.readFileSync(filePath, "utf8");
|
|
2109
|
+
} catch (err) {
|
|
2110
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2111
|
+
throw new Error(`\u8BFB\u53D6 --ids-file \u5931\u8D25: ${msg}`);
|
|
2112
|
+
}
|
|
2113
|
+
raw = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
2114
|
+
}
|
|
2115
|
+
if (raw.length === 0) {
|
|
2116
|
+
throw new Error("--ids / --ids-file \u672A\u89E3\u6790\u5230\u4EFB\u4F55 id");
|
|
2117
|
+
}
|
|
2118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2119
|
+
const ids = [];
|
|
2120
|
+
for (const id of raw) {
|
|
2121
|
+
if (!seen.has(id)) {
|
|
2122
|
+
seen.add(id);
|
|
2123
|
+
ids.push(id);
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return ids;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/utils/issue-batch-input.ts
|
|
2130
|
+
import fs7 from "fs";
|
|
2131
|
+
var PRIORITY_VALUES = ["critical", "high", "medium", "low"];
|
|
2132
|
+
function readSource(opts) {
|
|
2133
|
+
const hasFile = typeof opts.file === "string" && opts.file.trim() !== "";
|
|
2134
|
+
const hasStdin = typeof opts.stdin === "string";
|
|
2135
|
+
if (hasFile && hasStdin) {
|
|
2136
|
+
throw new Error("--file \u4E0E --stdin \u4E92\u65A5\uFF0C\u8BF7\u53EA\u63D0\u4F9B\u5176\u4E00");
|
|
2137
|
+
}
|
|
2138
|
+
if (!hasFile && !hasStdin) {
|
|
2139
|
+
throw new Error("\u9700\u8981\u63D0\u4F9B --file <path> \u6216\u901A\u8FC7 stdin \u4F20\u5165 JSON");
|
|
2140
|
+
}
|
|
2141
|
+
if (hasFile) {
|
|
2142
|
+
try {
|
|
2143
|
+
return fs7.readFileSync(opts.file, "utf8");
|
|
2144
|
+
} catch (err) {
|
|
2145
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2146
|
+
throw new Error(`\u8BFB\u53D6 --file \u5931\u8D25: ${msg}`);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
return opts.stdin;
|
|
2150
|
+
}
|
|
2151
|
+
function parseIssueBatchInput(opts) {
|
|
2152
|
+
const raw = readSource(opts).trim();
|
|
2153
|
+
if (!raw) {
|
|
2154
|
+
throw new Error("\u8F93\u5165 JSON \u4E3A\u7A7A");
|
|
2155
|
+
}
|
|
2156
|
+
let parsed;
|
|
2157
|
+
try {
|
|
2158
|
+
parsed = JSON.parse(raw);
|
|
2159
|
+
} catch (err) {
|
|
2160
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2161
|
+
throw new Error(`JSON \u89E3\u6790\u5931\u8D25: ${msg}`);
|
|
2162
|
+
}
|
|
2163
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2164
|
+
throw new Error("\u9876\u5C42\u5FC5\u987B\u662F JSON object\uFF08\u542B version + issues[]\uFF09");
|
|
2165
|
+
}
|
|
2166
|
+
const root = parsed;
|
|
2167
|
+
const version2 = typeof root.version === "string" ? root.version : "1";
|
|
2168
|
+
const issuesRaw = root.issues;
|
|
2169
|
+
if (!Array.isArray(issuesRaw)) {
|
|
2170
|
+
throw new Error("\u9876\u5C42\u7F3A\u5C11\u6570\u7EC4\u5B57\u6BB5 issues[]");
|
|
2171
|
+
}
|
|
2172
|
+
if (issuesRaw.length === 0) {
|
|
2173
|
+
throw new Error("issues[] \u4E3A\u7A7A\uFF0C\u65E0\u53EF\u521B\u5EFA");
|
|
2174
|
+
}
|
|
2175
|
+
const workspaceId = typeof root.workspace_id === "string" ? root.workspace_id : void 0;
|
|
2176
|
+
const issues = issuesRaw.map((it, idx) => validateIssue(it, idx));
|
|
2177
|
+
return { version: version2, workspace_id: workspaceId, issues };
|
|
2178
|
+
}
|
|
2179
|
+
function validateIssue(value, idx) {
|
|
2180
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2181
|
+
throw new Error(`issues[${idx}] \u4E0D\u662F object`);
|
|
2182
|
+
}
|
|
2183
|
+
const v = value;
|
|
2184
|
+
const where = `issues[${idx}]`;
|
|
2185
|
+
const title = requireString(v.title, `${where}.title`);
|
|
2186
|
+
const priority = requirePriority(v.priority, `${where}.priority`);
|
|
2187
|
+
const expected = requireString(v.expected, `${where}.expected`);
|
|
2188
|
+
const conversation_id = optionalString(v.conversation_id, `${where}.conversation_id`);
|
|
2189
|
+
const user_name = optionalString(v.user_name, `${where}.user_name`);
|
|
2190
|
+
if (!conversation_id && !user_name) {
|
|
2191
|
+
throw new Error(`${where} \u5FC5\u987B\u63D0\u4F9B conversation_id \u6216 user_name \u4E4B\u4E00`);
|
|
2192
|
+
}
|
|
2193
|
+
const description = optionalString(v.description, `${where}.description`);
|
|
2194
|
+
let tags;
|
|
2195
|
+
if (v.tags !== void 0) {
|
|
2196
|
+
if (!Array.isArray(v.tags) || !v.tags.every((t) => typeof t === "string")) {
|
|
2197
|
+
throw new Error(`${where}.tags \u5FC5\u987B\u662F string[]`);
|
|
2198
|
+
}
|
|
2199
|
+
tags = v.tags;
|
|
2200
|
+
}
|
|
2201
|
+
const source_type = optionalString(v.source_type, `${where}.source_type`);
|
|
2202
|
+
let source_data;
|
|
2203
|
+
if (v.source_data !== void 0) {
|
|
2204
|
+
if (!v.source_data || typeof v.source_data !== "object" || Array.isArray(v.source_data)) {
|
|
2205
|
+
throw new Error(`${where}.source_data \u5FC5\u987B\u662F object`);
|
|
2206
|
+
}
|
|
2207
|
+
source_data = v.source_data;
|
|
2208
|
+
}
|
|
2209
|
+
return {
|
|
2210
|
+
title,
|
|
2211
|
+
priority,
|
|
2212
|
+
expected,
|
|
2213
|
+
description,
|
|
2214
|
+
conversation_id,
|
|
2215
|
+
user_name,
|
|
2216
|
+
tags,
|
|
2217
|
+
source_type,
|
|
2218
|
+
source_data
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
function requireString(v, where) {
|
|
2222
|
+
if (typeof v !== "string" || v.trim() === "") {
|
|
2223
|
+
throw new Error(`${where} \u5FC5\u586B\uFF0C\u4E14\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
|
|
2224
|
+
}
|
|
2225
|
+
return v;
|
|
2226
|
+
}
|
|
2227
|
+
function optionalString(v, where) {
|
|
2228
|
+
if (v === void 0 || v === null) return void 0;
|
|
2229
|
+
if (typeof v !== "string") {
|
|
2230
|
+
throw new Error(`${where} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
2231
|
+
}
|
|
2232
|
+
return v.trim() === "" ? void 0 : v;
|
|
2233
|
+
}
|
|
2234
|
+
function requirePriority(v, where) {
|
|
2235
|
+
if (typeof v !== "string" || !PRIORITY_VALUES.includes(v)) {
|
|
2236
|
+
throw new Error(
|
|
2237
|
+
`${where} \u5FC5\u987B\u662F ${PRIORITY_VALUES.join("|")} \u4E4B\u4E00\uFF0C\u6536\u5230: ${JSON.stringify(v)}`
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
return v;
|
|
2241
|
+
}
|
|
2242
|
+
function buildContent(issue) {
|
|
2243
|
+
const head = issue.description?.trim();
|
|
2244
|
+
if (head) {
|
|
2245
|
+
return `${head}
|
|
2246
|
+
|
|
2247
|
+
## Expected
|
|
2248
|
+
${issue.expected}`;
|
|
2249
|
+
}
|
|
2250
|
+
return `## Expected
|
|
2251
|
+
${issue.expected}`;
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// src/client/account-roles-api.ts
|
|
2255
|
+
async function listAccountRoles(opts = {}) {
|
|
2256
|
+
const request = createRequest();
|
|
2257
|
+
const query = {};
|
|
2258
|
+
if (opts.role) query.role = opts.role;
|
|
2259
|
+
if (opts.includeInactive) query.includeInactive = "1";
|
|
2260
|
+
const result = await request(getCsAdminUrl(), "/api/account-roles", {
|
|
2261
|
+
method: "GET",
|
|
2262
|
+
query
|
|
2263
|
+
});
|
|
2264
|
+
return result.rows ?? [];
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
// src/utils/resolve-owner.ts
|
|
2268
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2269
|
+
var cache = null;
|
|
2270
|
+
async function getEngineers() {
|
|
2271
|
+
const now = Date.now();
|
|
2272
|
+
if (cache && cache.expireAt > now) return cache.engineers;
|
|
2273
|
+
const [ka, smb] = await Promise.all([
|
|
2274
|
+
listAccountRoles({ role: "KA_ENGINEER" }),
|
|
2275
|
+
listAccountRoles({ role: "SMB_ENGINEER" })
|
|
2276
|
+
]);
|
|
2277
|
+
const merged = mergeAndDedupe(ka, smb);
|
|
2278
|
+
cache = { engineers: merged, expireAt: now + CACHE_TTL_MS };
|
|
2279
|
+
return merged;
|
|
2280
|
+
}
|
|
2281
|
+
function mergeAndDedupe(...lists) {
|
|
2282
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2283
|
+
for (const list of lists) {
|
|
2284
|
+
for (const row of list) {
|
|
2285
|
+
const existing = seen.get(row.csAdminUserId);
|
|
2286
|
+
if (!existing) {
|
|
2287
|
+
seen.set(row.csAdminUserId, { ...row });
|
|
2288
|
+
} else {
|
|
2289
|
+
const roles = /* @__PURE__ */ new Set([...existing.roles, ...row.roles]);
|
|
2290
|
+
existing.roles = [...roles];
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
return [...seen.values()].sort((a, b) => a.engineerName.localeCompare(b.engineerName));
|
|
2295
|
+
}
|
|
2296
|
+
async function resolveOwner(input) {
|
|
2297
|
+
const trimmed = input.trim();
|
|
2298
|
+
if (!trimmed) throw new Error("--owner \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2299
|
+
if (/^\d+$/.test(trimmed)) {
|
|
2300
|
+
if (trimmed.length === 11) {
|
|
2301
|
+
throw new Error("--owner \u4E0D\u518D\u63A5\u53D7\u624B\u673A\u53F7\uFF08\u5DF2\u5E9F\u5F03\uFF09\u3002\u8BF7\u4F20 user_id \u6216\u82B1\u540D");
|
|
2302
|
+
}
|
|
2303
|
+
const n = Number(trimmed);
|
|
2304
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
2305
|
+
throw new Error(`--owner \u6570\u5B57\u89E3\u6790\u5931\u8D25: ${input}`);
|
|
2306
|
+
}
|
|
2307
|
+
return n;
|
|
2308
|
+
}
|
|
2309
|
+
const engineers = await getEngineers();
|
|
2310
|
+
const exact = engineers.filter((e) => e.engineerName === trimmed);
|
|
2311
|
+
if (exact.length === 1) return exact[0].csAdminUserId;
|
|
2312
|
+
if (exact.length > 1) {
|
|
2313
|
+
const list = exact.map((e) => `${e.engineerName}(${e.csAdminUserId})`).join(", ");
|
|
2314
|
+
throw new Error(`--owner "${input}" \u540C\u540D\u591A\u4EBA\uFF1A${list}\u3002\u8BF7\u6539\u4F20 user_id`);
|
|
2315
|
+
}
|
|
2316
|
+
const partial = engineers.filter(
|
|
2317
|
+
(e) => e.engineerName.toLowerCase().includes(trimmed.toLowerCase())
|
|
2318
|
+
);
|
|
2319
|
+
if (partial.length === 1) return partial[0].csAdminUserId;
|
|
2320
|
+
if (partial.length > 1) {
|
|
2321
|
+
const list = partial.map((e) => `${e.engineerName}(${e.csAdminUserId})`).join(", ");
|
|
2322
|
+
throw new Error(`--owner "${input}" \u6A21\u7CCA\u5339\u914D\u591A\u4EBA\uFF1A${list}\u3002\u8BF7\u6539\u7528\u7CBE\u786E\u82B1\u540D\u6216 user_id`);
|
|
2323
|
+
}
|
|
2324
|
+
throw new Error(`--owner "${input}" \u672A\u627E\u5230\u5339\u914D\u7684\u5DE5\u7A0B\u5E08\uFF08\u5171 ${engineers.length} \u4EBA\u5728\u518C\uFF09`);
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// src/utils/resolve-session.ts
|
|
2328
|
+
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
2329
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
2330
|
+
async function resolveSession(opts) {
|
|
2331
|
+
const userName = opts.userName.trim();
|
|
2332
|
+
if (!userName) throw new Error("user_name \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2333
|
+
const cacheKey = `${opts.workspaceId ?? ""}|${userName}`;
|
|
2334
|
+
const now = Date.now();
|
|
2335
|
+
const hit = cache2.get(cacheKey);
|
|
2336
|
+
if (hit && hit.expireAt > now) return hit.conversationId;
|
|
2337
|
+
const resp = await listConversations({
|
|
2338
|
+
userName,
|
|
2339
|
+
workspaceId: opts.workspaceId,
|
|
2340
|
+
pageSize: opts.pageSize ?? 5
|
|
2341
|
+
});
|
|
2342
|
+
const list = (resp.list ?? []).filter((c) => typeof c.conversation_id === "string");
|
|
2343
|
+
if (list.length === 0) {
|
|
2344
|
+
throw new Error(`user_name "${userName}" \u672A\u5339\u914D\u5230\u4EFB\u4F55\u4F1A\u8BDD\uFF08NOT_FOUND\uFF09`);
|
|
2345
|
+
}
|
|
2346
|
+
if (list.length > 1) {
|
|
2347
|
+
const preview = list.slice(0, 3).map((c) => c.conversation_id).join(", ");
|
|
2348
|
+
throw new Error(
|
|
2349
|
+
`user_name "${userName}" \u547D\u4E2D ${list.length} \u6761\u4F1A\u8BDD\uFF08AMBIGUOUS\uFF09\uFF1A${preview}${list.length > 3 ? " ..." : ""}\u3002\u8BF7\u6539\u4F20 conversation_id`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
const conversationId = list[0].conversation_id;
|
|
2353
|
+
cache2.set(cacheKey, { conversationId, expireAt: now + CACHE_TTL_MS2 });
|
|
2354
|
+
return conversationId;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
// src/utils/run-batch.ts
|
|
2358
|
+
import readline from "readline";
|
|
2359
|
+
var DEFAULT_CONCURRENCY = 5;
|
|
2360
|
+
async function runBatch(opts) {
|
|
2361
|
+
const concurrency = Math.max(1, opts.concurrency ?? DEFAULT_CONCURRENCY);
|
|
2362
|
+
const results = [];
|
|
2363
|
+
let aborted = false;
|
|
2364
|
+
const sigintHandler = () => {
|
|
2365
|
+
if (!aborted) {
|
|
2366
|
+
aborted = true;
|
|
2367
|
+
outputInfo("\u6536\u5230 SIGINT\uFF0C\u7B49\u5F85\u98DE\u884C\u4E2D\u8BF7\u6C42\u7ED3\u675F\u540E\u9000\u51FA...");
|
|
2368
|
+
}
|
|
2369
|
+
};
|
|
2370
|
+
process.on("SIGINT", sigintHandler);
|
|
2371
|
+
try {
|
|
2372
|
+
for (let i = 0; i < opts.ids.length; i += concurrency) {
|
|
2373
|
+
if (aborted) break;
|
|
2374
|
+
const chunk = opts.ids.slice(i, i + concurrency);
|
|
2375
|
+
const settled = await Promise.all(
|
|
2376
|
+
chunk.map(async (id) => {
|
|
2377
|
+
try {
|
|
2378
|
+
return await opts.perItem(id);
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
return mapErrorToFailure(id, err);
|
|
2381
|
+
}
|
|
2382
|
+
})
|
|
2383
|
+
);
|
|
2384
|
+
results.push(...settled);
|
|
2385
|
+
}
|
|
2386
|
+
} finally {
|
|
2387
|
+
process.off("SIGINT", sigintHandler);
|
|
2388
|
+
}
|
|
2389
|
+
return buildReport(results, aborted);
|
|
2390
|
+
}
|
|
2391
|
+
function mapErrorToFailure(id, err) {
|
|
2392
|
+
if (err instanceof APIError) {
|
|
2393
|
+
return { status: "failed", id, code: `HTTP_${err.statusCode}`, msg: err.message };
|
|
2394
|
+
}
|
|
2395
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2396
|
+
return { status: "failed", id, code: "INTERNAL", msg };
|
|
2397
|
+
}
|
|
2398
|
+
function buildReport(results, aborted) {
|
|
2399
|
+
const report = {
|
|
2400
|
+
version: "1",
|
|
2401
|
+
total: results.length,
|
|
2402
|
+
success_count: 0,
|
|
2403
|
+
failed_count: 0,
|
|
2404
|
+
skipped_count: 0,
|
|
2405
|
+
aborted,
|
|
2406
|
+
success_ids: [],
|
|
2407
|
+
failed: [],
|
|
2408
|
+
skipped: []
|
|
2409
|
+
};
|
|
2410
|
+
for (const r of results) {
|
|
2411
|
+
if (r.status === "success") {
|
|
2412
|
+
report.success_count++;
|
|
2413
|
+
report.success_ids.push(r.id);
|
|
2414
|
+
} else if (r.status === "failed") {
|
|
2415
|
+
report.failed_count++;
|
|
2416
|
+
report.failed.push({ id: r.id, code: r.code, msg: r.msg });
|
|
2417
|
+
} else {
|
|
2418
|
+
report.skipped_count++;
|
|
2419
|
+
report.skipped.push({ id: r.id, reason: r.reason });
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
return report;
|
|
2423
|
+
}
|
|
2424
|
+
function reportToExitCode(report) {
|
|
2425
|
+
if (report.aborted) return 130;
|
|
2426
|
+
if (report.failed_count === 0) return 0;
|
|
2427
|
+
if (report.failed_count === report.total) return 1;
|
|
2428
|
+
return 2;
|
|
2429
|
+
}
|
|
2430
|
+
var DEFAULT_CONFIRM_THRESHOLD = 20;
|
|
2431
|
+
async function confirmBatch(opts) {
|
|
2432
|
+
const threshold = opts.threshold ?? DEFAULT_CONFIRM_THRESHOLD;
|
|
2433
|
+
if (opts.count < threshold) return;
|
|
2434
|
+
if (opts.yes) return;
|
|
2435
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2436
|
+
throw new Error(`\u975E TTY \u73AF\u5883\u4E0B\u8981 ${opts.action} ${opts.count} \u6761\uFF0C\u9700\u663E\u5F0F --yes \u624D\u80FD\u6267\u884C`);
|
|
2437
|
+
}
|
|
2438
|
+
const rl = readline.createInterface({
|
|
2439
|
+
input: process.stdin,
|
|
2440
|
+
output: process.stderr
|
|
2441
|
+
});
|
|
2442
|
+
try {
|
|
2443
|
+
const answer = await new Promise((resolve) => {
|
|
2444
|
+
rl.question(`\u5373\u5C06${opts.action} ${opts.count} \u6761\u8BB0\u5F55\u3002\u7EE7\u7EED\uFF1F\u8F93\u5165 yes \u786E\u8BA4: `, resolve);
|
|
2445
|
+
});
|
|
2446
|
+
if (answer.trim().toLowerCase() !== "yes") {
|
|
2447
|
+
throw new Error("\u5DF2\u53D6\u6D88");
|
|
2448
|
+
}
|
|
2449
|
+
} finally {
|
|
2450
|
+
rl.close();
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
|
|
1787
2454
|
// src/commands/issue.ts
|
|
2455
|
+
async function readAllStdin() {
|
|
2456
|
+
const chunks = [];
|
|
2457
|
+
for await (const chunk of process.stdin) {
|
|
2458
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
2459
|
+
}
|
|
2460
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2461
|
+
}
|
|
1788
2462
|
function registerIssueCommand(program2) {
|
|
1789
2463
|
const issue = program2.command("issue").description(
|
|
1790
2464
|
"\u5DE5\u5355\uFF08Issue\uFF09\u7BA1\u7406 \u2014\u2014 \u8BB0\u5F55 Agent \u56DE\u590D\u5F02\u5E38\u3001\u5BA2\u6237\u6295\u8BC9\u7B49\u5F85\u5904\u7406\u95EE\u9898\u3002\u5DE5\u5355\u662F AI \u4FEE\u590D\u6D41\u7A0B\uFF08issue-repair\uFF09\u7684\u8F93\u5165\u6E90"
|
|
@@ -1825,63 +2499,219 @@ function registerIssueCommand(program2) {
|
|
|
1825
2499
|
process.exit(toExitCode(err));
|
|
1826
2500
|
}
|
|
1827
2501
|
});
|
|
1828
|
-
issue.command("create").description(
|
|
2502
|
+
issue.command("create").description(
|
|
2503
|
+
"\u521B\u5EFA\u5DE5\u5355\u3002\u5355\u6761\uFF1A--title + --content\uFF08+ \u53EF\u9009\u5B57\u6BB5\uFF09\u3002\u6279\u91CF\uFF1A--file <path> \u6216 --stdin \u8BFB JSON\uFF08\u542B workspace_id + issues[]\uFF0C\u6BCF\u6761\u5FC5\u586B title/priority/expected \u548C conversation_id|user_name \u4E8C\u9009\u4E00\uFF09\u3002\u6279\u91CF\u6A21\u5F0F N>20 \u4E14 TTY \u9700\u8981\u4E8C\u6B21\u786E\u8BA4\uFF1B\u975E TTY \u5FC5\u987B\u663E\u5F0F --yes"
|
|
2504
|
+
).option("--title <title>", "[\u5355\u6761] \u5DE5\u5355\u6807\u9898\uFF08\u7B80\u8981\u63CF\u8FF0\u95EE\u9898\uFF09").option("--content <content>", "[\u5355\u6761] \u5DE5\u5355\u5185\u5BB9\uFF08\u8BE6\u7EC6\u63CF\u8FF0\u95EE\u9898\u73B0\u8C61\u548C\u671F\u671B\u7ED3\u679C\uFF09").option("--priority <priority>", "[\u5355\u6761] \u4F18\u5148\u7EA7: critical | high | medium | low").option("--tags <tags...>", "[\u5355\u6761] \u6807\u7B7E\uFF08\u591A\u4E2A\u7A7A\u683C\u5206\u9694\uFF0C\u7528\u4E8E\u5206\u7C7B\u7B5B\u9009\uFF09").option("--conversation-id <id>", "[\u5355\u6761] \u5173\u8054\u7684\u4F1A\u8BDD ID").option("--record-id <id>", "[\u5355\u6761] \u5173\u8054\u7684\u6D88\u606F\u8BB0\u5F55 ID").option("--workspace-id <id>", "[\u5355\u6761] \u5DE5\u4F5C\u7A7A\u95F4 ID\uFF08\u9ED8\u8BA4\u4ECE\u73AF\u5883\u53D8\u91CF\u8BFB\u53D6\uFF09").option("--source-type <type>", "[\u5355\u6761] \u6765\u6E90\u7C7B\u578B").option("--source-data <json>", "[\u5355\u6761] \u6765\u6E90\u6570\u636E\uFF08JSON \u5B57\u7B26\u4E32\uFF09").option("--file <path>", "[\u6279\u91CF] \u4ECE JSON \u6587\u4EF6\u8BFB\u8F93\u5165\uFF08\u4E0E --stdin \u4E92\u65A5\uFF09").option("--stdin", "[\u6279\u91CF] \u4ECE stdin \u8BFB JSON\uFF08\u4E0E --file \u4E92\u65A5\uFF09", false).option("--dry-run", "[\u6279\u91CF] \u9884\u89C8\uFF1A\u5B8C\u6210 user_name \u53CD\u67E5 + content \u62FC\u88C5\uFF0C\u4F46\u4E0D\u8C03\u521B\u5EFA\u63A5\u53E3", false).option("--concurrency <n>", "[\u6279\u91CF] \u5E76\u53D1\u6570\uFF08\u9ED8\u8BA4 5\uFF09", "5").option("--yes", "[\u6279\u91CF] \u8DF3\u8FC7 N>=20 \u4E8C\u6B21\u786E\u8BA4\uFF08\u975E TTY \u5FC5\u987B\u663E\u5F0F\u4F20\uFF09", false).action(async (opts) => {
|
|
2505
|
+
const hasBatch = Boolean(opts.file) || opts.stdin === true;
|
|
2506
|
+
const hasSingleLike = Boolean(opts.title || opts.content);
|
|
2507
|
+
if (hasBatch && hasSingleLike) {
|
|
2508
|
+
reportCaughtError(
|
|
2509
|
+
new Error("\u4E0D\u80FD\u540C\u65F6\u63D0\u4F9B\u5355\u6761\u5B57\u6BB5\uFF08--title/--content\uFF09\u548C\u6279\u91CF\u5B57\u6BB5\uFF08--file/--stdin\uFF09")
|
|
2510
|
+
);
|
|
2511
|
+
process.exit(1);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
if (!hasBatch) {
|
|
2515
|
+
try {
|
|
2516
|
+
if (!opts.title || !opts.content) {
|
|
2517
|
+
throw new Error(
|
|
2518
|
+
"\u5355\u6761\u521B\u5EFA\u9700\u63D0\u4F9B --title \u548C --content\uFF08\u6216\u6539\u7528 --file/--stdin \u6279\u91CF\u6A21\u5F0F\uFF09"
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
const data = await createIssue({
|
|
2522
|
+
title: opts.title,
|
|
2523
|
+
content: opts.content,
|
|
2524
|
+
priority: opts.priority,
|
|
2525
|
+
tags: opts.tags,
|
|
2526
|
+
conversation_id: opts.conversationId,
|
|
2527
|
+
record_id: opts.recordId,
|
|
2528
|
+
workspace_id: opts.workspaceId,
|
|
2529
|
+
source_type: opts.sourceType,
|
|
2530
|
+
source_data: opts.sourceData ? JSON.parse(opts.sourceData) : void 0
|
|
2531
|
+
});
|
|
2532
|
+
formatOutput({ success: true, data }, program2.opts().table);
|
|
2533
|
+
} catch (err) {
|
|
2534
|
+
reportCaughtError(err);
|
|
2535
|
+
process.exit(toExitCode(err));
|
|
2536
|
+
}
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
let batchReport = null;
|
|
1829
2540
|
try {
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2541
|
+
const stdinContent = opts.stdin ? await readAllStdin() : void 0;
|
|
2542
|
+
const parsed = parseIssueBatchInput({ file: opts.file, stdin: stdinContent });
|
|
2543
|
+
const workspaceId = parsed.workspace_id;
|
|
2544
|
+
const resolved = await resolveSessionsForIssues(parsed.issues, workspaceId);
|
|
2545
|
+
if (opts.dryRun) {
|
|
2546
|
+
formatOutput(
|
|
2547
|
+
{
|
|
2548
|
+
success: true,
|
|
2549
|
+
data: {
|
|
2550
|
+
dry_run: true,
|
|
2551
|
+
version: parsed.version,
|
|
2552
|
+
workspace_id: workspaceId,
|
|
2553
|
+
total: parsed.issues.length,
|
|
2554
|
+
will_create: resolved.map((r) => ({
|
|
2555
|
+
title: r.issue.title,
|
|
2556
|
+
priority: r.issue.priority,
|
|
2557
|
+
conversation_id: r.conversationId,
|
|
2558
|
+
tags: r.issue.tags,
|
|
2559
|
+
resolved_from_user_name: r.issue.conversation_id ? void 0 : r.issue.user_name,
|
|
2560
|
+
preview_content: buildContent(r.issue)
|
|
2561
|
+
}))
|
|
2562
|
+
}
|
|
2563
|
+
},
|
|
2564
|
+
program2.opts().table
|
|
2565
|
+
);
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
await confirmBatch({ count: parsed.issues.length, action: "\u521B\u5EFA", yes: opts.yes });
|
|
2569
|
+
const concurrency = Number(opts.concurrency);
|
|
2570
|
+
if (!Number.isFinite(concurrency) || concurrency < 1) {
|
|
2571
|
+
throw new Error(`--concurrency \u5FC5\u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u6536\u5230: ${opts.concurrency}`);
|
|
2572
|
+
}
|
|
2573
|
+
const ids = parsed.issues.map((_, idx) => `idx-${idx}`);
|
|
2574
|
+
const indexed = new Map(resolved.map((r, idx) => [`idx-${idx}`, r]));
|
|
2575
|
+
batchReport = await runBatch({
|
|
2576
|
+
ids,
|
|
2577
|
+
concurrency,
|
|
2578
|
+
perItem: async (id) => {
|
|
2579
|
+
const r = indexed.get(id);
|
|
2580
|
+
if (!r) throw new Error(`internal: missing resolved entry for ${id}`);
|
|
2581
|
+
const data = await createIssue({
|
|
2582
|
+
title: r.issue.title,
|
|
2583
|
+
content: buildContent(r.issue),
|
|
2584
|
+
priority: r.issue.priority,
|
|
2585
|
+
tags: r.issue.tags,
|
|
2586
|
+
conversation_id: r.conversationId,
|
|
2587
|
+
workspace_id: workspaceId,
|
|
2588
|
+
source_type: r.issue.source_type,
|
|
2589
|
+
source_data: r.issue.source_data
|
|
2590
|
+
});
|
|
2591
|
+
return { status: "success", id, data };
|
|
2592
|
+
}
|
|
1840
2593
|
});
|
|
1841
|
-
formatOutput({ success: true, data }, program2.opts().table);
|
|
2594
|
+
formatOutput({ success: true, data: batchReport }, program2.opts().table);
|
|
1842
2595
|
} catch (err) {
|
|
1843
2596
|
reportCaughtError(err);
|
|
1844
2597
|
process.exit(toExitCode(err));
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
if (batchReport !== null) {
|
|
2601
|
+
const code = reportToExitCode(batchReport);
|
|
2602
|
+
if (code !== 0) process.exit(code);
|
|
1845
2603
|
}
|
|
1846
2604
|
});
|
|
1847
|
-
issue.command("update").description(
|
|
2605
|
+
issue.command("update").description(
|
|
2606
|
+
"\u66F4\u65B0\u5DE5\u5355\u5B57\u6BB5\uFF08\u5982 status\u3001priority\u3001tags\u3001title\u3001content\uFF09\u3002\u5355\u6761\uFF1A\u4F20 <issue_id>\u3002\u6279\u91CF\uFF1A\u4F20 --ids \u6216 --ids-file\uFF0C\u6240\u6709 ID \u5E94\u7528\u540C\u4E00 --data\u3002\u6279\u91CF\u6A21\u5F0F\u4E0B N>20 \u4E14 TTY \u9700\u8981\u4E8C\u6B21\u786E\u8BA4\uFF1B\u975E TTY \u5FC5\u987B\u663E\u5F0F --yes"
|
|
2607
|
+
).argument("[issue_id]", "\u5DE5\u5355 ID\uFF08\u5355\u6761\u66F4\u65B0\u65F6\u5FC5\u586B\uFF1B\u6279\u91CF\u66F4\u65B0\u8D70 --ids / --ids-file\uFF09").requiredOption(
|
|
1848
2608
|
"--data <json>",
|
|
1849
2609
|
"JSON \u6570\u636E\u6216 @\u6587\u4EF6\u8DEF\u5F84\u3002\u53EF\u66F4\u65B0: status\uFF08open|closed\uFF09\u3001priority\u3001tags\u3001title\u3001content"
|
|
1850
|
-
).action(async (issueId, opts) => {
|
|
2610
|
+
).option("--ids <ids>", "\u6279\u91CF\u66F4\u65B0\uFF1A\u9017\u53F7\u5206\u9694\u7684\u5DE5\u5355 ID\uFF08\u4E0E <issue_id> \u4E92\u65A5\uFF09").option("--ids-file <path>", "\u6279\u91CF\u66F4\u65B0\uFF1A\u4ECE\u6587\u4EF6\u8BFB\u53D6 ID\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF1B# \u5F00\u5934\u6CE8\u91CA\uFF09").option("--dry-run", "\u6279\u91CF\u6A21\u5F0F\u9884\u89C8\uFF1A\u53EA\u6253\u5370 will-update \u5217\u8868\uFF0C\u4E0D\u8C03\u5199\u63A5\u53E3", false).option("--concurrency <n>", "\u6279\u91CF\u5E76\u53D1\u6570\uFF08\u9ED8\u8BA4 5\uFF09", "5").option("--yes", "\u6279\u91CF\u6A21\u5F0F\u8DF3\u8FC7\u4E8C\u6B21\u786E\u8BA4\uFF08\u975E TTY \u5FC5\u987B\u663E\u5F0F\u4F20\uFF09", false).action(async (issueId, opts) => {
|
|
2611
|
+
const hasSingle = issueId !== void 0;
|
|
2612
|
+
const hasBatch = opts.ids !== void 0 || opts.idsFile !== void 0;
|
|
2613
|
+
if (hasSingle && hasBatch) {
|
|
2614
|
+
reportCaughtError(new Error("\u4E0D\u80FD\u540C\u65F6\u63D0\u4F9B <issue_id> \u548C --ids/--ids-file"));
|
|
2615
|
+
process.exit(1);
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
if (!hasSingle && !hasBatch) {
|
|
2619
|
+
reportCaughtError(new Error("\u9700\u8981\u63D0\u4F9B <issue_id> \u6216 --ids/--ids-file \u4E4B\u4E00"));
|
|
2620
|
+
process.exit(1);
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
let batchReport = null;
|
|
1851
2624
|
try {
|
|
1852
2625
|
const body = parseDataOption(opts.data);
|
|
1853
|
-
|
|
1854
|
-
|
|
2626
|
+
if (hasSingle) {
|
|
2627
|
+
const data = await updateIssue(issueId, body);
|
|
2628
|
+
formatOutput({ success: true, data }, program2.opts().table);
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
const ids = parseIdsInput({ ids: opts.ids, idsFile: opts.idsFile });
|
|
2632
|
+
if (opts.dryRun) {
|
|
2633
|
+
formatOutput(
|
|
2634
|
+
{
|
|
2635
|
+
success: true,
|
|
2636
|
+
data: {
|
|
2637
|
+
dry_run: true,
|
|
2638
|
+
total: ids.length,
|
|
2639
|
+
ids,
|
|
2640
|
+
will_apply: body
|
|
2641
|
+
}
|
|
2642
|
+
},
|
|
2643
|
+
program2.opts().table
|
|
2644
|
+
);
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
await confirmBatch({ count: ids.length, action: "\u66F4\u65B0", yes: opts.yes });
|
|
2648
|
+
const concurrency = Number(opts.concurrency);
|
|
2649
|
+
if (!Number.isFinite(concurrency) || concurrency < 1) {
|
|
2650
|
+
throw new Error(`--concurrency \u5FC5\u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u6536\u5230: ${opts.concurrency}`);
|
|
2651
|
+
}
|
|
2652
|
+
batchReport = await runBatch({
|
|
2653
|
+
ids,
|
|
2654
|
+
concurrency,
|
|
2655
|
+
perItem: async (id) => {
|
|
2656
|
+
const data = await updateIssue(id, body);
|
|
2657
|
+
return { status: "success", id, data };
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
formatOutput({ success: true, data: batchReport }, program2.opts().table);
|
|
1855
2661
|
} catch (err) {
|
|
1856
2662
|
reportCaughtError(err);
|
|
1857
2663
|
process.exit(toExitCode(err));
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
if (batchReport !== null) {
|
|
2667
|
+
const code = reportToExitCode(batchReport);
|
|
2668
|
+
if (code !== 0) process.exit(code);
|
|
1858
2669
|
}
|
|
1859
2670
|
});
|
|
1860
2671
|
issue.command("update-owner").description(
|
|
1861
|
-
"\u66F4\u65B0\u5DE5\u5355\u8D1F\u8D23\u4EBA\
|
|
1862
|
-
).argument("
|
|
2672
|
+
"\u66F4\u65B0\u5DE5\u5355\u8D1F\u8D23\u4EBA\u3002--owner \u63A5\u53D7 user_id\uFF08\u22647 \u4F4D\u7EAF\u6570\u5B57\uFF09\u6216\u82B1\u540D\uFF08\u81EA\u52A8\u901A\u8FC7 cs-admin /api/account-roles \u89E3\u6790\uFF09\u3002\u5355\u6761\uFF1A\u4F20 <issue_id>\u3002\u6279\u91CF\uFF1A\u4F20 --ids \u6216 --ids-file\uFF0C\u8D70 cs-admin BFF \u7684 batch-owner\uFF08\u5355\u6B21\u8C03\u7528\u3001\u539F\u5B50\u6539\u6D3E\u3001\u81EA\u52A8 Feishu/DingTalk \u901A\u77E5\uFF09"
|
|
2673
|
+
).argument("[issue_id]", "\u5DE5\u5355 ID\uFF08\u5355\u6761\u66F4\u65B0\u65F6\u5FC5\u586B\uFF1B\u6279\u91CF\u8D70 --ids / --ids-file\uFF09").requiredOption("--owner <user_id_or_name>", "\u8D1F\u8D23\u4EBA user_id \u6216\u82B1\u540D").option("--ids <ids>", "\u6279\u91CF\u6539\u6D3E\uFF1A\u9017\u53F7\u5206\u9694\u7684\u5DE5\u5355 ID\uFF08\u4E0E <issue_id> \u4E92\u65A5\uFF09").option("--ids-file <path>", "\u6279\u91CF\u6539\u6D3E\uFF1A\u4ECE\u6587\u4EF6\u8BFB\u53D6 ID\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF1B# \u5F00\u5934\u6CE8\u91CA\uFF09").option("--dry-run", "\u6279\u91CF\u6A21\u5F0F\u9884\u89C8\uFF1A\u4EC5\u89E3\u6790 owner + \u5217\u51FA ID\uFF0C\u4E0D\u8C03\u5199\u63A5\u53E3", false).option("--yes", "\u6279\u91CF\u6A21\u5F0F\u8DF3\u8FC7 N>=20 \u4E8C\u6B21\u786E\u8BA4\uFF08\u975E TTY \u5FC5\u987B\u663E\u5F0F\u4F20\uFF09", false).action(async (issueId, opts) => {
|
|
2674
|
+
const hasSingle = issueId !== void 0;
|
|
2675
|
+
const hasBatch = opts.ids !== void 0 || opts.idsFile !== void 0;
|
|
2676
|
+
if (hasSingle && hasBatch) {
|
|
2677
|
+
reportCaughtError(new Error("\u4E0D\u80FD\u540C\u65F6\u63D0\u4F9B <issue_id> \u548C --ids/--ids-file"));
|
|
2678
|
+
process.exit(1);
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
if (!hasSingle && !hasBatch) {
|
|
2682
|
+
reportCaughtError(new Error("\u9700\u8981\u63D0\u4F9B <issue_id> \u6216 --ids/--ids-file \u4E4B\u4E00"));
|
|
2683
|
+
process.exit(1);
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
1863
2686
|
try {
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2687
|
+
const ownerId = await resolveOwner(String(opts.owner));
|
|
2688
|
+
if (hasSingle) {
|
|
2689
|
+
const data = await updateIssueOwner(issueId, { owner: ownerId });
|
|
2690
|
+
formatOutput({ success: true, data }, program2.opts().table);
|
|
2691
|
+
return;
|
|
1868
2692
|
}
|
|
1869
|
-
const
|
|
1870
|
-
if (
|
|
1871
|
-
|
|
1872
|
-
if (!Number.isInteger(ownerId) || ownerId <= 0) {
|
|
1873
|
-
throw new Error(`--owner \u5FC5\u987B\u4E3A\u6B63\u6574\u6570\uFF0C\u6536\u5230: ${opts.owner}`);
|
|
1874
|
-
}
|
|
1875
|
-
body.owner = ownerId;
|
|
1876
|
-
} else {
|
|
1877
|
-
const phone = String(opts.ownerPhone).trim();
|
|
1878
|
-
if (!phone) {
|
|
1879
|
-
throw new Error("--owner-phone \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1880
|
-
}
|
|
1881
|
-
body.owner_phone = phone;
|
|
2693
|
+
const ids = parseIdsInput({ ids: opts.ids, idsFile: opts.idsFile });
|
|
2694
|
+
if (ids.length > 200) {
|
|
2695
|
+
throw new Error(`\u6279\u91CF\u6539\u6D3E\u5355\u6279\u6700\u591A 200 \u6761\uFF0C\u5F53\u524D ${ids.length}\u3002\u8BF7\u62C6\u5206\u8F93\u5165`);
|
|
1882
2696
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
2697
|
+
if (opts.dryRun) {
|
|
2698
|
+
formatOutput(
|
|
2699
|
+
{
|
|
2700
|
+
success: true,
|
|
2701
|
+
data: {
|
|
2702
|
+
dry_run: true,
|
|
2703
|
+
total: ids.length,
|
|
2704
|
+
ids,
|
|
2705
|
+
resolved_owner_id: ownerId
|
|
2706
|
+
}
|
|
2707
|
+
},
|
|
2708
|
+
program2.opts().table
|
|
2709
|
+
);
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
await confirmBatch({ count: ids.length, action: "\u6539\u6D3E", yes: opts.yes });
|
|
2713
|
+
const result = await batchReassignOwner({ issueIds: ids, ownerUserId: ownerId });
|
|
2714
|
+
formatOutput({ success: true, data: result }, program2.opts().table);
|
|
1885
2715
|
} catch (err) {
|
|
1886
2716
|
reportCaughtError(err);
|
|
1887
2717
|
process.exit(toExitCode(err));
|
|
@@ -1919,9 +2749,30 @@ function registerIssueCommand(program2) {
|
|
|
1919
2749
|
}
|
|
1920
2750
|
});
|
|
1921
2751
|
}
|
|
2752
|
+
async function resolveSessionsForIssues(issues, workspaceId) {
|
|
2753
|
+
const out = [];
|
|
2754
|
+
for (let i = 0; i < issues.length; i++) {
|
|
2755
|
+
const it = issues[i];
|
|
2756
|
+
let conversationId;
|
|
2757
|
+
if (it.conversation_id) {
|
|
2758
|
+
conversationId = it.conversation_id;
|
|
2759
|
+
} else if (it.user_name) {
|
|
2760
|
+
try {
|
|
2761
|
+
conversationId = await resolveSession({ userName: it.user_name, workspaceId });
|
|
2762
|
+
} catch (err) {
|
|
2763
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2764
|
+
throw new Error(`issues[${i}] \u53CD\u67E5\u4F1A\u8BDD\u5931\u8D25\uFF1A${msg}`);
|
|
2765
|
+
}
|
|
2766
|
+
} else {
|
|
2767
|
+
throw new Error(`issues[${i}] \u65E2\u65E0 conversation_id \u4E5F\u65E0 user_name`);
|
|
2768
|
+
}
|
|
2769
|
+
out.push({ issue: it, conversationId });
|
|
2770
|
+
}
|
|
2771
|
+
return out;
|
|
2772
|
+
}
|
|
1922
2773
|
|
|
1923
2774
|
// src/commands/knowledge.ts
|
|
1924
|
-
import
|
|
2775
|
+
import fs8 from "fs";
|
|
1925
2776
|
|
|
1926
2777
|
// src/client/knowledge-api.ts
|
|
1927
2778
|
async function listExternalKnowledge(opts) {
|
|
@@ -2027,7 +2878,7 @@ async function deleteKnowledgeContent(opts) {
|
|
|
2027
2878
|
// src/commands/knowledge.ts
|
|
2028
2879
|
function readContentArg(value) {
|
|
2029
2880
|
if (value.startsWith("@")) {
|
|
2030
|
-
return
|
|
2881
|
+
return fs8.readFileSync(value.slice(1), "utf-8");
|
|
2031
2882
|
}
|
|
2032
2883
|
return value;
|
|
2033
2884
|
}
|
|
@@ -3156,29 +4007,23 @@ async function listRepairRecords(opts) {
|
|
|
3156
4007
|
if (opts.workspaceId) query.workspace_id = opts.workspaceId;
|
|
3157
4008
|
if (opts.page) query.page = opts.page;
|
|
3158
4009
|
if (opts.pageSize) query.page_size = opts.pageSize;
|
|
3159
|
-
return request(
|
|
4010
|
+
return request(getCsAdminUrl(), PATH_PREFIX2, {
|
|
3160
4011
|
method: "GET",
|
|
3161
|
-
query
|
|
3162
|
-
headers: buildCookieHeaders(),
|
|
3163
|
-
skipAuth: true
|
|
4012
|
+
query
|
|
3164
4013
|
});
|
|
3165
4014
|
}
|
|
3166
4015
|
async function createRepairRecord(data) {
|
|
3167
4016
|
const request = createRequest();
|
|
3168
|
-
return request(
|
|
4017
|
+
return request(getCsAdminUrl(), PATH_PREFIX2, {
|
|
3169
4018
|
method: "POST",
|
|
3170
|
-
body: data
|
|
3171
|
-
headers: buildCookieHeaders(),
|
|
3172
|
-
skipAuth: true
|
|
4019
|
+
body: data
|
|
3173
4020
|
});
|
|
3174
4021
|
}
|
|
3175
4022
|
async function updateRepairRecord(recordId, data) {
|
|
3176
4023
|
const request = createRequest();
|
|
3177
|
-
return request(
|
|
4024
|
+
return request(getCsAdminUrl(), `${PATH_PREFIX2}/${recordId}`, {
|
|
3178
4025
|
method: "PUT",
|
|
3179
|
-
body: data
|
|
3180
|
-
headers: buildCookieHeaders(),
|
|
3181
|
-
skipAuth: true
|
|
4026
|
+
body: data
|
|
3182
4027
|
});
|
|
3183
4028
|
}
|
|
3184
4029
|
|
|
@@ -3448,22 +4293,7 @@ function registerSACommand(program2) {
|
|
|
3448
4293
|
}
|
|
3449
4294
|
|
|
3450
4295
|
// src/commands/testset.ts
|
|
3451
|
-
import
|
|
3452
|
-
|
|
3453
|
-
// src/utils/file-output.ts
|
|
3454
|
-
import fs6 from "fs";
|
|
3455
|
-
import path4 from "path";
|
|
3456
|
-
async function writeBinaryToFile(filePath, buffer) {
|
|
3457
|
-
try {
|
|
3458
|
-
const dir = path4.dirname(filePath);
|
|
3459
|
-
fs6.mkdirSync(dir, { recursive: true });
|
|
3460
|
-
fs6.writeFileSync(filePath, buffer);
|
|
3461
|
-
} catch (err) {
|
|
3462
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3463
|
-
throw new APIError(1, `\u6587\u4EF6\u5199\u5165\u5931\u8D25: ${msg}`);
|
|
3464
|
-
}
|
|
3465
|
-
return { path: filePath, bytes: buffer.length };
|
|
3466
|
-
}
|
|
4296
|
+
import fs9 from "fs";
|
|
3467
4297
|
|
|
3468
4298
|
// src/client/testset-api.ts
|
|
3469
4299
|
function unwrapPaginated(raw, fallbackPageSize) {
|
|
@@ -3975,7 +4805,7 @@ function readPromptInput(value) {
|
|
|
3975
4805
|
if (!filePath) {
|
|
3976
4806
|
throw new Error("File path cannot be empty after @");
|
|
3977
4807
|
}
|
|
3978
|
-
return
|
|
4808
|
+
return fs9.readFileSync(filePath, "utf-8");
|
|
3979
4809
|
}
|
|
3980
4810
|
return value;
|
|
3981
4811
|
}
|
|
@@ -4134,7 +4964,7 @@ var require2 = createRequire(import.meta.url);
|
|
|
4134
4964
|
var { version } = require2("../package.json");
|
|
4135
4965
|
var program = new Command();
|
|
4136
4966
|
program.name("cs-cli").description(
|
|
4137
|
-
"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"
|
|
4138
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(
|
|
4139
4969
|
"--request-timeout <ms>",
|
|
4140
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",
|
|
@@ -4169,6 +4999,7 @@ registerFaqCommand(program);
|
|
|
4169
4999
|
registerConversationCommand(program);
|
|
4170
5000
|
registerDebugCommand(program);
|
|
4171
5001
|
registerMonitorCommand(program);
|
|
5002
|
+
registerDashboardCommand(program);
|
|
4172
5003
|
registerRepairRecordCommand(program);
|
|
4173
5004
|
registerOperationsRecordCommand(program);
|
|
4174
5005
|
registerChangeConsumerCommand(program);
|