@cnbcool/cnb-api-generate 1.1.0 → 1.2.0
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/client/index.ts +26 -121
- package/client/shortcuts.ts +76 -0
- package/package.json +1 -1
package/client/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'fs';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { showModuleHelp } from './modules.help';
|
|
6
6
|
import { showToolHelp } from './tools.help';
|
|
7
|
-
import { showShort, resolveShortcut } from './shortcuts';
|
|
7
|
+
import { showShort, resolveShortcut, summarizeResponse } from './shortcuts';
|
|
8
8
|
|
|
9
9
|
const helpFileContent = fs.readFileSync(
|
|
10
10
|
path.join(__dirname, 'help.json'),
|
|
@@ -182,6 +182,7 @@ function formatParams(
|
|
|
182
182
|
if (params.help) formatted.help = true;
|
|
183
183
|
if (params.short) formatted.short = true;
|
|
184
184
|
if (params.verbose) formatted.verbose = true;
|
|
185
|
+
if (params.summary) formatted.summary = true;
|
|
185
186
|
|
|
186
187
|
// 旧格式兼容:--path / --query / --data 是 JSON 字符串
|
|
187
188
|
if (typeof params.path === 'string') {
|
|
@@ -204,7 +205,7 @@ function formatParams(
|
|
|
204
205
|
const pathDef = paramDefs.path || {};
|
|
205
206
|
const queryDef = paramDefs.query || {};
|
|
206
207
|
const reservedKeys = new Set([
|
|
207
|
-
'module', 'tool', 'help', 'short', 'verbose',
|
|
208
|
+
'module', 'tool', 'help', 'short', 'verbose', 'summary',
|
|
208
209
|
'path', 'query', 'data', 'h', 'v',
|
|
209
210
|
]);
|
|
210
211
|
|
|
@@ -248,114 +249,6 @@ function formatParams(
|
|
|
248
249
|
return formatted;
|
|
249
250
|
}
|
|
250
251
|
|
|
251
|
-
/**
|
|
252
|
-
* 将嵌套对象打平取主字段值
|
|
253
|
-
* 用于表格输出时将嵌套字段(如 author: {username: "alice"})展示为 "alice"
|
|
254
|
-
* @param val 任意值
|
|
255
|
-
* @returns 打平后的字符串
|
|
256
|
-
*/
|
|
257
|
-
function flattenValue(val: any): string {
|
|
258
|
-
if (val === null || val === undefined) return '';
|
|
259
|
-
if (typeof val !== 'object') return String(val);
|
|
260
|
-
if (Array.isArray(val)) {
|
|
261
|
-
return val.map(v => typeof v === 'object' ? flattenValue(v) : String(v)).join(',');
|
|
262
|
-
}
|
|
263
|
-
// 对象:取第一个 string 类型的字段值(如 author.login)
|
|
264
|
-
for (const v of Object.values(val)) {
|
|
265
|
-
if (typeof v === 'string') return v;
|
|
266
|
-
if (typeof v === 'number') return String(v);
|
|
267
|
-
}
|
|
268
|
-
return JSON.stringify(val);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* 缩短时间戳:2026-04-05T15:50:35Z → 2026-04-05
|
|
273
|
-
*/
|
|
274
|
-
function shortenTimestamp(str: string): string {
|
|
275
|
-
if (/^\d{4}-\d{2}-\d{2}T/.test(str)) {
|
|
276
|
-
return str.substring(0, 10);
|
|
277
|
-
}
|
|
278
|
-
return str;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* 格式化数组响应为紧凑表格
|
|
283
|
-
* - 过滤低信号字段(url、avatar 等)和全空列
|
|
284
|
-
* - 缩短时间戳为日期
|
|
285
|
-
* - 嵌套对象打平取主字段值
|
|
286
|
-
* - 格式:表头行 + 数据行,用 | 分隔
|
|
287
|
-
*/
|
|
288
|
-
function formatArrayAsTable(response: any): string {
|
|
289
|
-
const { status, page, total, totalPages, data } = response;
|
|
290
|
-
|
|
291
|
-
if (!Array.isArray(data) || data.length === 0) {
|
|
292
|
-
return JSON.stringify(response);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// 收集所有出现的 key
|
|
296
|
-
const allKeys = new Set<string>();
|
|
297
|
-
for (const item of data) {
|
|
298
|
-
if (typeof item === 'object' && item !== null) {
|
|
299
|
-
for (const key of Object.keys(item)) {
|
|
300
|
-
allKeys.add(key);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (allKeys.size === 0) {
|
|
306
|
-
return JSON.stringify(response);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// 低信号字段
|
|
310
|
-
const lowSignalPatterns = [
|
|
311
|
-
/url$/i, /avatar/i, /html_url/i, /href/i, /link/i,
|
|
312
|
-
/^_/, /updated_at/i, /^invisible$/i, /^state_reason$/i,
|
|
313
|
-
/^started_at$/i, /^ended_at$/i, /^last_acted_at$/i,
|
|
314
|
-
/^is_npc$/i, /^email$/i, /^nickname$/i,
|
|
315
|
-
];
|
|
316
|
-
|
|
317
|
-
const filteredKeys = [...allKeys].filter(key =>
|
|
318
|
-
!lowSignalPatterns.some(p => p.test(key))
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
const keysList = filteredKeys.length >= 3 ? filteredKeys : [...allKeys];
|
|
322
|
-
|
|
323
|
-
// 构建列数据,同时过滤全空列
|
|
324
|
-
const columns: { key: string; values: string[] }[] = [];
|
|
325
|
-
for (const key of keysList) {
|
|
326
|
-
const values: string[] = [];
|
|
327
|
-
let hasNonEmpty = false;
|
|
328
|
-
for (const item of data) {
|
|
329
|
-
let str = flattenValue(item?.[key]);
|
|
330
|
-
str = shortenTimestamp(str);
|
|
331
|
-
str = str.replace(/\|/g, '/');
|
|
332
|
-
if (str.length > 80) str = str.substring(0, 77) + '...';
|
|
333
|
-
if (str !== '' && str !== 'false') hasNonEmpty = true;
|
|
334
|
-
values.push(str);
|
|
335
|
-
}
|
|
336
|
-
if (hasNonEmpty) {
|
|
337
|
-
columns.push({ key, values });
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (columns.length === 0) {
|
|
342
|
-
return JSON.stringify(response);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// 构建输出(紧凑格式,不对齐)
|
|
346
|
-
let output = `status:${status}`;
|
|
347
|
-
if (total != null) {
|
|
348
|
-
output += ` page:${page}/${totalPages} total:${total}`;
|
|
349
|
-
}
|
|
350
|
-
output += '\n';
|
|
351
|
-
output += columns.map(c => c.key).join('|') + '\n';
|
|
352
|
-
for (let r = 0; r < data.length; r++) {
|
|
353
|
-
output += columns.map(c => c.values[r]).join('|') + '\n';
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return output.trimEnd();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
252
|
/**
|
|
360
253
|
* 精简响应对象(非 verbose 模式使用)
|
|
361
254
|
* - 去掉 trace(仅错误时保留)
|
|
@@ -395,11 +288,14 @@ function isStandardResponse(response: any): boolean {
|
|
|
395
288
|
/**
|
|
396
289
|
* 格式化 CLI 输出
|
|
397
290
|
* - verbose 模式:完整 JSON(含 trace、header 等全部字段)
|
|
398
|
-
* -
|
|
399
|
-
* -
|
|
400
|
-
*
|
|
291
|
+
* - summary 模式:只输出核心摘要字段(需要 --summary 显式指定)
|
|
292
|
+
* - 默认:精简 JSON(去掉 trace、header,保留完整 data)
|
|
293
|
+
* @param response 原始响应
|
|
294
|
+
* @param verbose 是否 verbose 模式
|
|
295
|
+
* @param summary 是否 summary 模式
|
|
296
|
+
* @param toolKey 当前 tool 标识,格式为 "module/tool",用于匹配摘要配置
|
|
401
297
|
*/
|
|
402
|
-
function formatOutput(response: any, verbose: boolean): string {
|
|
298
|
+
function formatOutput(response: any, verbose: boolean, summary: boolean, toolKey: string): string {
|
|
403
299
|
// --verbose 模式:输出完整原始信息
|
|
404
300
|
if (verbose) {
|
|
405
301
|
return JSON.stringify(response, null, 2);
|
|
@@ -407,19 +303,27 @@ function formatOutput(response: any, verbose: boolean): string {
|
|
|
407
303
|
|
|
408
304
|
// converter 可能返回裸 data(没有标准 {status, data} 结构),直接输出
|
|
409
305
|
if (!isStandardResponse(response)) {
|
|
410
|
-
|
|
306
|
+
if (summary) {
|
|
307
|
+
const summarized = summarizeResponse(response, toolKey);
|
|
308
|
+
if (summarized !== null) {
|
|
309
|
+
return JSON.stringify(summarized, null, 2);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return JSON.stringify(response, null, 2);
|
|
411
313
|
}
|
|
412
314
|
|
|
413
315
|
// 精简响应
|
|
414
316
|
const compact = compactResponse(response);
|
|
415
317
|
|
|
416
|
-
//
|
|
417
|
-
if (compact &&
|
|
418
|
-
|
|
318
|
+
// --summary 模式:对特定 tool 应用摘要提取(仅成功响应)
|
|
319
|
+
if (summary && compact.status >= 200 && compact.status < 300) {
|
|
320
|
+
const summarized = summarizeResponse(compact.data, toolKey);
|
|
321
|
+
if (summarized !== null) {
|
|
322
|
+
compact.data = summarized;
|
|
323
|
+
}
|
|
419
324
|
}
|
|
420
325
|
|
|
421
|
-
|
|
422
|
-
return JSON.stringify(compact);
|
|
326
|
+
return JSON.stringify(compact, null, 2);
|
|
423
327
|
}
|
|
424
328
|
|
|
425
329
|
/**
|
|
@@ -614,7 +518,8 @@ async function main() {
|
|
|
614
518
|
}
|
|
615
519
|
|
|
616
520
|
const data = await toolFunction(...toolsParam);
|
|
617
|
-
|
|
521
|
+
const toolKey = `${formattedParams.module}/${formattedParams.tool}`;
|
|
522
|
+
console.log(formatOutput(data, !!formattedParams.verbose, !!formattedParams.summary, toolKey));
|
|
618
523
|
}
|
|
619
524
|
|
|
620
525
|
main();
|
package/client/shortcuts.ts
CHANGED
|
@@ -121,6 +121,7 @@ export function showShort(): void {
|
|
|
121
121
|
${list}
|
|
122
122
|
|
|
123
123
|
提示: 以上命令自动使用环境变量 CNB_REPO_SLUG 和 CNB_ISSUE_IID,无需手动传 --path
|
|
124
|
+
添加 --summary 可只输出核心摘要字段
|
|
124
125
|
`);
|
|
125
126
|
} else if (ctx === 'pull_request') {
|
|
126
127
|
const number = getPRNumber();
|
|
@@ -133,6 +134,7 @@ ${list}
|
|
|
133
134
|
${list}
|
|
134
135
|
|
|
135
136
|
提示: 以上命令自动使用环境变量 CNB_REPO_SLUG 和 CNB_PULL_REQUEST_IID,无需手动传 --path
|
|
137
|
+
添加 --summary 可只输出核心摘要字段
|
|
136
138
|
`);
|
|
137
139
|
} else {
|
|
138
140
|
// 未知上下文,两组都显示
|
|
@@ -152,6 +154,8 @@ ${prList}
|
|
|
152
154
|
CNB_REPO_SLUG - 仓库路径
|
|
153
155
|
CNB_ISSUE_IID - Issue 编号 (Issue 事件)
|
|
154
156
|
CNB_PULL_REQUEST_IID - PR 编号 (PR 事件)
|
|
157
|
+
|
|
158
|
+
添加 --summary 可只输出核心摘要字段
|
|
155
159
|
`);
|
|
156
160
|
}
|
|
157
161
|
}
|
|
@@ -206,3 +210,75 @@ export function resolveShortcut(
|
|
|
206
210
|
autoData: matched.autoData || null,
|
|
207
211
|
};
|
|
208
212
|
}
|
|
213
|
+
|
|
214
|
+
// ============================================================
|
|
215
|
+
// 摘要输出
|
|
216
|
+
// 对返回数据量大的快捷命令,默认只输出核心字段,减少 token 消耗
|
|
217
|
+
// ============================================================
|
|
218
|
+
|
|
219
|
+
type SummaryExtractor = (item: any) => any;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 摘要提取器配置
|
|
223
|
+
* key: "module/tool",value: 摘要提取函数
|
|
224
|
+
* - 对象响应:直接对 data 应用提取函数
|
|
225
|
+
* - 数组响应:对 data 中每个元素应用提取函数
|
|
226
|
+
*/
|
|
227
|
+
const SUMMARY_EXTRACTORS: Record<string, SummaryExtractor> = {
|
|
228
|
+
// Issue 详情摘要:只保留标题和正文
|
|
229
|
+
'issues/get-issue': (item) => ({
|
|
230
|
+
title: item.title,
|
|
231
|
+
body: item.body,
|
|
232
|
+
}),
|
|
233
|
+
|
|
234
|
+
// Issue 更新摘要:只保留状态变更结果
|
|
235
|
+
'issues/update-issue': (item) => ({
|
|
236
|
+
number: item.number,
|
|
237
|
+
state: item.state,
|
|
238
|
+
state_reason: item.state_reason,
|
|
239
|
+
}),
|
|
240
|
+
|
|
241
|
+
// Issue 添加处理人摘要:只保留处理人列表
|
|
242
|
+
'issues/post-issue-assignees': (item) => ({
|
|
243
|
+
number: item.number,
|
|
244
|
+
assignees: item.assignees?.map?.((a: any) => a.username).filter(Boolean) || [],
|
|
245
|
+
}),
|
|
246
|
+
|
|
247
|
+
// PR 详情摘要:只保留标题、状态、正文和分支信息
|
|
248
|
+
'pulls/get-pull': (item) => ({
|
|
249
|
+
title: item.title,
|
|
250
|
+
state: item.state,
|
|
251
|
+
base: item.base?.ref,
|
|
252
|
+
head: item.head?.ref,
|
|
253
|
+
body: item.body,
|
|
254
|
+
}),
|
|
255
|
+
|
|
256
|
+
// PR 文件列表摘要:只保留文件名、变更状态和增删行数
|
|
257
|
+
'pulls/list-pull-files': (item) => ({
|
|
258
|
+
filename: item.filename,
|
|
259
|
+
status: item.status,
|
|
260
|
+
additions: item.additions,
|
|
261
|
+
deletions: item.deletions,
|
|
262
|
+
}),
|
|
263
|
+
|
|
264
|
+
// PR 提交列表摘要:只保留 sha 前 8 位和 commit message
|
|
265
|
+
'pulls/list-pull-commits': (item) => ({
|
|
266
|
+
sha: typeof item.sha === 'string' ? item.sha.substring(0, 8) : item.sha,
|
|
267
|
+
message: item.commit?.message,
|
|
268
|
+
}),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 对响应数据应用摘要提取(如果当前 tool 配置了摘要提取器)
|
|
273
|
+
* @param data 原始 data(对象或数组)
|
|
274
|
+
* @param toolKey 当前 tool 标识,格式为 "module/tool"
|
|
275
|
+
* @returns 摘要后的 data,如果无匹配提取器则返回 null
|
|
276
|
+
*/
|
|
277
|
+
export function summarizeResponse(data: any, toolKey: string): any | null {
|
|
278
|
+
const extractor = SUMMARY_EXTRACTORS[toolKey];
|
|
279
|
+
if (!extractor) return null;
|
|
280
|
+
if (Array.isArray(data)) {
|
|
281
|
+
return data.map(extractor);
|
|
282
|
+
}
|
|
283
|
+
return extractor(data);
|
|
284
|
+
}
|