@cnbcool/cnb-api-generate 1.0.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.
Files changed (89) hide show
  1. package/README.md +399 -0
  2. package/bin/cag.js +3 -0
  3. package/bin/csg.js +3 -0
  4. package/built/codegen/action/generate-redux-action-ast-node.js +85 -0
  5. package/built/codegen/action/generate-redux-action-call-api-function-param-ast-node.js +40 -0
  6. package/built/codegen/action/generate-redux-action-normal-params-ast-node.js +52 -0
  7. package/built/codegen/action/generate-redux-action-params-binding-element-ast-node.js +40 -0
  8. package/built/codegen/action/generate-redux-action-params-type-literal-ast-node.js +58 -0
  9. package/built/codegen/api/generate-api-error-response-ast-node.js +83 -0
  10. package/built/codegen/api/generate-api-function-ast-node.js +133 -0
  11. package/built/codegen/api/generate-api-function-request-params-type-ast-node.js +59 -0
  12. package/built/codegen/api/generate-api-response-return-type-content.js +80 -0
  13. package/built/codegen/api/generate-api-success-response-ast-node.js +84 -0
  14. package/built/codegen/api/generate-function-content-ast-node.js +104 -0
  15. package/built/codegen/api/generate-function-normal-params-ast-node.js +49 -0
  16. package/built/codegen/api/generate-function-params-ast-node.js +249 -0
  17. package/built/codegen/api/generate-function-refs-type-params-ast-node.js +102 -0
  18. package/built/codegen/api/generate-import-ast-node.js +58 -0
  19. package/built/codegen/api/generate-request-function.js +81 -0
  20. package/built/codegen/api/generate-request-url-ast-node.js +98 -0
  21. package/built/codegen/enum/generate-enum-ast-node.js +95 -0
  22. package/built/codegen/enum/generate-enum-content-ast-node.js +74 -0
  23. package/built/codegen/interface/generate-import-interface-ast-node.js +47 -0
  24. package/built/codegen/interface/generate-interface-anchor-ast-node.js +25 -0
  25. package/built/codegen/interface/generate-interface-ast-node.js +322 -0
  26. package/built/codegen/interface/generate-interface-desc-ast-node.js +51 -0
  27. package/built/codegen/interface/generate-interface-entrance.js +110 -0
  28. package/built/codegen/printer/printer-skill-client-modules.js +58 -0
  29. package/built/codegen/printer/printer-skills-client-core.js +69 -0
  30. package/built/codegen/printer/printer-swagger-api-to-file.js +128 -0
  31. package/built/codegen/printer/printer-swagger-definitions-to-file.js +81 -0
  32. package/built/codegen/printer/printer-ts-ast-to-js-file.js +72 -0
  33. package/built/codegen/record/generate-record-anchor-ast-node.js +25 -0
  34. package/built/codegen/record/generate-record-ast-node.js +134 -0
  35. package/built/codegen/swagger-definitions-to-ast-node.js +36 -0
  36. package/built/codegen/types/md-array-type-content-ast-node.js +46 -0
  37. package/built/constants/actions.js +8 -0
  38. package/built/constants/definitions.js +20 -0
  39. package/built/constants/file.js +12 -0
  40. package/built/constants/index.js +20 -0
  41. package/built/constants/prettier.js +4 -0
  42. package/built/constants/request.js +35 -0
  43. package/built/constants/skills.js +5 -0
  44. package/built/generate-api.js +36 -0
  45. package/built/generate-defintions.js +32 -0
  46. package/built/generate-entrypoint.js +130 -0
  47. package/built/index.js +19 -0
  48. package/built/interface/index.js +16 -0
  49. package/built/skills-tools/generate-config.js +28 -0
  50. package/built/skills-tools/generate-skill-cli-help.js +156 -0
  51. package/built/skills-tools/generate-skill-cli-scripts.js +20 -0
  52. package/built/skills-tools/generate-skill-md.js +25 -0
  53. package/built/skills.js +36 -0
  54. package/built/test.js +1 -0
  55. package/built/utils/camel-to-kebab.js +13 -0
  56. package/built/utils/comment-line-break.js +6 -0
  57. package/built/utils/filter-api-param.js +64 -0
  58. package/built/utils/format-interface-name.js +12 -0
  59. package/built/utils/format-type-name.js +12 -0
  60. package/built/utils/gen-operation-id.js +24 -0
  61. package/built/utils/generate-map-item.js +16 -0
  62. package/built/utils/get-codegen-target.js +8 -0
  63. package/built/utils/get-config.js +68 -0
  64. package/built/utils/get-defintion-name.js +8 -0
  65. package/built/utils/get-defintion-type.js +13 -0
  66. package/built/utils/get-generate-license.js +39 -0
  67. package/built/utils/get-keyword-type-node.js +60 -0
  68. package/built/utils/get-printer-file-prefix.js +21 -0
  69. package/built/utils/get-skills-codegen-target.js +15 -0
  70. package/built/utils/get-swagger-file.js +60 -0
  71. package/built/utils/inject-param-to-path.js +12 -0
  72. package/built/utils/is-english-only.js +8 -0
  73. package/built/utils/is-skills.js +6 -0
  74. package/built/utils/lower-first-char.js +8 -0
  75. package/built/utils/path-to-camel-case.js +31 -0
  76. package/built/utils/progress.js +76 -0
  77. package/built/utils/to-camel-case.js +8 -0
  78. package/built/utils/translations-keyworad-to-string.js +55 -0
  79. package/client/core.ts +114 -0
  80. package/client/fetch-response-handler.ts +36 -0
  81. package/client/index.ts +377 -0
  82. package/client/modules.help.ts +36 -0
  83. package/client/schemaToJson.ts +26 -0
  84. package/client/shortcuts.ts +208 -0
  85. package/client/tools.help.ts +104 -0
  86. package/client/utils/convert-link.ts +139 -0
  87. package/client/utils/generate-unique-id.ts +3 -0
  88. package/package.json +75 -0
  89. package/skills-template/SKILL.md +100 -0
@@ -0,0 +1,377 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { showModuleHelp } from './modules.help';
6
+ import { showToolHelp } from './tools.help';
7
+ import { showShort, resolveShortcut } from './shortcuts';
8
+ import util from 'util';
9
+
10
+ const helpFileContent = fs.readFileSync(
11
+ path.join(__dirname, 'help.json'),
12
+ 'utf8',
13
+ );
14
+ if (!helpFileContent) {
15
+ console.error('help.json not found');
16
+ process.exit(1);
17
+ }
18
+
19
+ const helpData = JSON.parse(helpFileContent);
20
+ /**
21
+ * 解析命令行参数
22
+ * @returns 解析后的参数对象
23
+ */
24
+ // function parseArguments(): Record<string, string | boolean | undefined> {
25
+ // const args = process.argv.slice(2);
26
+ // const result: Record<string, string | boolean | undefined> = {};
27
+
28
+ // for (let i = 0; i < args.length; i++) {
29
+ // const arg = args[i];
30
+
31
+ // // 检查是否是参数名(以--开头)
32
+ // if (arg.startsWith('--')) {
33
+ // const paramName = arg.slice(2);
34
+
35
+ // // 检查下一个参数是否是值(不是以--开头)
36
+ // if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
37
+ // result[paramName] = args[i + 1];
38
+ // i++; // 跳过下一个参数(值)
39
+ // } else {
40
+ // result[paramName] = paramName === 'help' ? true : undefined;
41
+ // }
42
+ // }
43
+ // }
44
+ // return result;
45
+ // }
46
+
47
+ export function parseArguments() {
48
+ const args = process.argv.slice(2);
49
+ const result: Record<string, string | boolean | undefined> = {};
50
+
51
+ let positionalCount = 0;
52
+ let i = 0;
53
+
54
+ while (i < args.length) {
55
+ const arg = args[i];
56
+
57
+ if (arg.startsWith('--')) {
58
+ // --- 处理命名参数 (Options) ---
59
+ const fullKey = arg.slice(2);
60
+
61
+ // 支持 key=value 格式 (e.g., --config=file.json)
62
+ if (fullKey.includes('=')) {
63
+ const [key, ...valueParts] = fullKey.split('=');
64
+ result[key] = valueParts.join('=');
65
+ i++;
66
+ continue;
67
+ }
68
+
69
+ const key = fullKey;
70
+
71
+ // 检查下一个参数是否是值
72
+ const nextArg = args[i + 1];
73
+ const isNextArgValue =
74
+ i + 1 < args.length &&
75
+ !nextArg.startsWith('--') &&
76
+ !nextArg.startsWith('-'); // 也防止捕获短参数作为值
77
+
78
+ if (isNextArgValue) {
79
+ result[key] = nextArg;
80
+ i += 2; // 跳过 key 和 value
81
+ } else {
82
+ // 没有值,视为布尔标志 (flag)
83
+ // 特殊处理:如果用户显式想要 undefined 行为,可以在这里调整,但通常 CLI 中 flag 存在即为 true
84
+ result[key] = true;
85
+ i++;
86
+ }
87
+ } else if (arg.startsWith('-') && arg.length > 1 && !/^-?\d+$/.test(arg)) {
88
+ // --- 处理短参数 (Short flags, e.g., -h, -v) ---
89
+ // 注意:排除负数数字的情况
90
+ const key = arg.slice(1);
91
+
92
+ // 简单处理:短参数通常不带长值,或者支持 -f value
93
+ const nextArg = args[i + 1];
94
+ const isNextArgValue =
95
+ i + 1 < args.length &&
96
+ !nextArg.startsWith('--') &&
97
+ !nextArg.startsWith('-');
98
+
99
+ if (isNextArgValue && key.length === 1) {
100
+ // 只有单字符短参才自动吞并下一个值 (如 -o output.txt),多字符连写 (如 -abc) 通常视为多个布尔旗标
101
+ result[key] = nextArg;
102
+ i += 2;
103
+ } else {
104
+ result[key] = true;
105
+ i++;
106
+ }
107
+ } else {
108
+ // --- 处理位置参数 (Positional Args) ---
109
+ if (positionalCount === 0) {
110
+ result.module = arg;
111
+ } else if (positionalCount === 1) {
112
+ result.tool = arg;
113
+ }
114
+ positionalCount++;
115
+ i++;
116
+ }
117
+ }
118
+
119
+
120
+ return result;
121
+ }
122
+
123
+ /**
124
+ * 验证必须参数是否存在
125
+ * @param params 解析后的参数对象
126
+ * @returns 验证结果
127
+ */
128
+ function validateRequiredParams(
129
+ params: Record<string, string | boolean | undefined>,
130
+ ): boolean {
131
+ if (!params.tool || params.help) {
132
+ return false;
133
+ }
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * 尝试解析JSON字符串
139
+ * @param str 要解析的字符串
140
+ * @returns 解析后的对象或原始字符串
141
+ */
142
+ function tryParseJSON(str: string | boolean | undefined): any {
143
+ if (typeof str !== 'string') return str;
144
+
145
+ // 先将真实控制字符转义为 JSON 合法形式,处理 shell/AI 传入的原始换行等情况
146
+ const escaped = str.replace(/[\x00-\x1F\x7F]/g, (ch) => {
147
+ const map = { '\n': '\\n', '\r': '\\r', '\t': '\\t', '\b': '\\b', '\f': '\\f' };
148
+ return map[ch] || '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0');
149
+ });
150
+ try {
151
+ return JSON.parse(escaped);
152
+ } catch (error) {
153
+ return str;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * 尝试从文件引用读取内容(支持 @filepath 语法,类似 curl -d @file)
159
+ * @param str 要检查的字符串
160
+ * @returns 文件内容或原始字符串
161
+ */
162
+ /**
163
+ * 从文件引用或 stdin 读取内容(类似 curl 的 @file / @- 语法)
164
+ * @param str 要检查的字符串
165
+ * @returns 文件/stdin 内容或原始字符串
166
+ */
167
+ function tryReadFileRef(str) {
168
+ if (typeof str !== 'string' || !str.startsWith('@')) return str;
169
+
170
+ const ref = str.slice(1);
171
+
172
+ // @- 表示从 stdin 读取
173
+ if (ref === '-') {
174
+ try {
175
+ return fs.readFileSync(0, 'utf8').trim();
176
+ } catch (e) {
177
+ console.error('从 stdin 读取失败:', e.message);
178
+ return str;
179
+ }
180
+ }
181
+
182
+ // @/path/to/file 表示从文件读取
183
+ if (fs.existsSync(ref)) {
184
+ return fs.readFileSync(ref, 'utf8').trim();
185
+ }
186
+
187
+ return str;
188
+ }
189
+
190
+ /**
191
+ * 格式化参数对象
192
+ * @param params 原始参数对象
193
+ * @returns 格式化后的参数对象
194
+ */
195
+ function formatParams(
196
+ params: Record<string, string | boolean | undefined>,
197
+ ): Record<string, any> {
198
+ const formatted: Record<string, any> = {};
199
+
200
+ // 处理每个参数
201
+ for (const [key, value] of Object.entries(params)) {
202
+ if (typeof value === 'string') {
203
+ formatted[key] = tryParseJSON(tryReadFileRef(value));
204
+ } else if (typeof value === 'boolean') {
205
+ formatted[key] = value;
206
+ }
207
+ }
208
+
209
+ // 当没有传递query时,要判断当前tool是否支持query
210
+ if (formatted.query === undefined) {
211
+ const { module, tool } = formatted;
212
+ const toolsHelp = helpData.modulesHelp?.[module]?.[tool];
213
+ const toolsParam = toolsHelp?.help?.parameter || {};
214
+
215
+ if (toolsParam.query) {
216
+ formatted.query = {};
217
+ }
218
+ }
219
+
220
+ return formatted;
221
+ }
222
+
223
+ /**
224
+ * 显示帮助文档
225
+ * @param moduleName 模块名称,如果指定则显示模块帮助
226
+ */
227
+ function showHelp(moduleName?: string, tool?: string): void {
228
+ if (moduleName && tool) {
229
+ showToolHelp(helpData, moduleName, tool);
230
+ } else if (moduleName) {
231
+ showModuleHelp(helpData, moduleName);
232
+ } else {
233
+ let moduleListMsg = ``;
234
+ for (const [module, count] of Object.entries(helpData.mainHelp)) {
235
+ moduleListMsg += `- ${module}, tool数量(${count})\n `;
236
+ }
237
+
238
+ const helpMeg = `
239
+ CNB OpenAPI CLI 工具\n
240
+ 可用模块:
241
+ ${moduleListMsg}
242
+ 参数说明:
243
+ <module> (必须) 模块名称 (例如: issues),可直接配合 --help 查看该模块帮助
244
+ <tool> (必须) 工具/动作名称 (例如: list-issues)
245
+ --path (可选) 路径参数,JSON字符串
246
+ --query (可选) 查询参数,JSON字符串
247
+ --data (可选) 数据参数,JSON字符串
248
+ --help (可选) 显示此帮助文档
249
+ --short (可选) 显示操作当前仓库(Issue/PR)的快捷命令
250
+
251
+ 使用示例:
252
+ ${process.env.CNB_CLI_CMD} --help
253
+ ${process.env.CNB_CLI_CMD} --short
254
+ ${process.env.CNB_CLI_CMD} issues --help
255
+ ${process.env.CNB_CLI_CMD} issues list-issues --help
256
+ ${process.env.CNB_CLI_CMD} issues list-issues --path '{"repo": "my-project"}' --query '{"page": 1, "pageSize": 10}'
257
+ `;
258
+ console.log(helpMeg);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * 主函数
264
+ */
265
+ async function main() {
266
+ // 解析命令行参数
267
+ const params = parseArguments();
268
+
269
+ // 处理 --short:显示当前场景的快捷命令
270
+ if (params.short) {
271
+ showShort();
272
+ process.exit(0);
273
+ }
274
+
275
+ // 尝试解析快捷命令(如 cnb issues get → get-issue)
276
+ const shortcut = resolveShortcut(
277
+ params.module as string | undefined,
278
+ params.tool as string | undefined,
279
+ );
280
+ if (shortcut) {
281
+ // 快捷命令:替换为实际 tool 名
282
+ params.tool = shortcut.tool;
283
+
284
+ // 自动注入 path 参数(如果用户没有手动传 --path)
285
+ if (!params.path) {
286
+ if (Object.keys(shortcut.autoPath).length > 0) {
287
+ params.path = JSON.stringify(shortcut.autoPath);
288
+ } else {
289
+ // 环境变量未设置,提示用户
290
+ const envHint =
291
+ params.module === 'issues'
292
+ ? 'CNB_REPO_SLUG 和 CNB_ISSUE_IID'
293
+ : 'CNB_REPO_SLUG 和 CNB_PULL_REQUEST_IID';
294
+ console.error(
295
+ `快捷命令需要环境变量 ${envHint},或手动传 --path 参数。\n` +
296
+ `提示:运行 ${process.env.CNB_CLI_CMD || 'cnb'} --short 查看快捷命令详情。`,
297
+ );
298
+ process.exit(1);
299
+ }
300
+ }
301
+
302
+ // 自动注入 data 参数(如 close → {"state":"closed"})
303
+ if (shortcut.autoData && !params.data) {
304
+ params.data = JSON.stringify(shortcut.autoData);
305
+ }
306
+ }
307
+
308
+ // 验证必须参数(当没有请求帮助时)
309
+ if (!validateRequiredParams(params)) {
310
+ showHelp(
311
+ params.module as string | undefined,
312
+ params.tool as string | undefined,
313
+ );
314
+ process.exit(0);
315
+ }
316
+
317
+ // 格式化参数
318
+ const formattedParams = formatParams(params);
319
+
320
+ // 动态引入模块
321
+ const toolPath = path.join(
322
+ __dirname,
323
+ '../modules',
324
+ `${formattedParams.module}/${formattedParams.tool}.js`,
325
+ );
326
+
327
+ if (!fs.existsSync(toolPath)) {
328
+ console.error(`工具文件不存在: ${toolPath}`);
329
+ process.exit(1);
330
+ }
331
+
332
+ const toolModule = require(toolPath);
333
+ const toolFunction = toolModule.default;
334
+
335
+ if (!toolFunction) {
336
+ console.error(`工具函数不存在`);
337
+ process.exit(1);
338
+ }
339
+
340
+ const toolsParam: any[] = [];
341
+ let pathAndQueryParams: Record<string, any> | string | null = null;
342
+
343
+ if (
344
+ formattedParams.path &&
345
+ Object.keys(formattedParams.path).length === 1 &&
346
+ !formattedParams.query
347
+ ) {
348
+ pathAndQueryParams =
349
+ formattedParams.path[Object.keys(formattedParams.path)[0]];
350
+ } else {
351
+ if (formattedParams.path) {
352
+ pathAndQueryParams = { ...formattedParams.path };
353
+ }
354
+
355
+ if (formattedParams.query) {
356
+ pathAndQueryParams = {
357
+ ...(pathAndQueryParams && typeof pathAndQueryParams === 'object'
358
+ ? pathAndQueryParams
359
+ : {}),
360
+ ...formattedParams.query,
361
+ };
362
+ }
363
+ }
364
+
365
+ if (pathAndQueryParams) {
366
+ toolsParam.push(pathAndQueryParams);
367
+ }
368
+
369
+ if (formattedParams.data) {
370
+ toolsParam.push(formattedParams.data);
371
+ }
372
+
373
+ const data = await toolFunction(...toolsParam);
374
+ console.log(util.inspect(data, { showHidden: false, depth: null }));
375
+ }
376
+
377
+ main();
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 显示模块帮助文档
3
+ * @param helpData 帮助数据
4
+ * @param moduleName 模块名称
5
+ */
6
+ export function showModuleHelp(helpData: any, moduleName: string): void {
7
+ const moduleHelpData = helpData.modulesHelp[moduleName];
8
+ if (!moduleHelpData) {
9
+ console.error(`模块 ${moduleName} 不存在`);
10
+ process.exit(1);
11
+ }
12
+
13
+ let toolListMsg = ``;
14
+ for (const [tool, info] of Object.entries(moduleHelpData)) {
15
+ toolListMsg += `- ${(info as any).filename}, ${(info as any).summary}\n `;
16
+ }
17
+
18
+ const helpMeg = `
19
+ 模块${moduleName}帮助文档\n
20
+ 可用工具:
21
+ ${toolListMsg}
22
+ 参数说明:
23
+ <module> (必须) 模块名称 (例如: issues),可直接配合 --help 查看该模块帮助
24
+ <tool> (必须) 工具/动作名称 (例如: list-issues)
25
+ --path (可选) 路径参数,JSON字符串
26
+ --query (可选) 查询参数,JSON字符串
27
+ --data (可选) 数据参数,JSON字符串
28
+ --help (可选) 显示此帮助文档
29
+
30
+ 使用示例:
31
+ ${process.env.CNB_CLI_CMD} issues --help
32
+ ${process.env.CNB_CLI_CMD} issues list-issues --help
33
+ ${process.env.CNB_CLI_CMD} issues list-issues --path '{"repo": "my-project"}' --query '{"page": 1, "pageSize": 10}'
34
+ `;
35
+ console.log(helpMeg);
36
+ }
@@ -0,0 +1,26 @@
1
+ export function schemaToJson(schema: any, json: any): any {
2
+ if (schema.type === 'object' || schema.type === 'array') {
3
+ const props =
4
+ schema.type === 'object' ? schema.properties : schema.items.properties;
5
+ // eslint-disable-next-line no-restricted-syntax
6
+ for (const key in props) {
7
+ const { type, items } = props[key];
8
+
9
+ if (type === 'object') {
10
+ json[key] = schemaToJson(props[key], {});
11
+ } else if (type === 'array') {
12
+ if (items.properties) {
13
+ json[key] = [schemaToJson(props[key], {})];
14
+ } else {
15
+ json[key] = [items.type];
16
+ }
17
+ } else {
18
+ json[key] = type;
19
+ }
20
+ }
21
+ } else {
22
+ return schema.type;
23
+ }
24
+
25
+ return json;
26
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * 快捷命令模块
3
+ *
4
+ * 根据 CNB 环境变量自动检测当前事件类型(Issue / PR),
5
+ * 提供场景化的快捷命令,自动从环境变量注入 path 参数。
6
+ */
7
+
8
+ // ============================================================
9
+ // 类型定义
10
+ // ============================================================
11
+
12
+ export type EventContext = 'issue' | 'pull_request' | 'unknown';
13
+
14
+ export interface ShortcutDefinition {
15
+ /** 用户看到的短命令名 */
16
+ shortName: string;
17
+ /** 实际对应的 tool 文件名 */
18
+ realTool: string;
19
+ /** 显示用的中文说明 */
20
+ description: string;
21
+ /** 是否需要用户传 --data,如果不为 null 则自动注入 */
22
+ autoData?: Record<string, any>;
23
+ /** --data 的使用提示(展示用) */
24
+ dataTip?: string;
25
+ }
26
+
27
+ export interface ResolvedShortcut {
28
+ /** 解析后的实际 tool 名 */
29
+ tool: string;
30
+ /** 自动注入的 path 参数 */
31
+ autoPath: Record<string, string>;
32
+ /** 自动注入的 data 参数(如 close/open) */
33
+ autoData: Record<string, any> | null;
34
+ }
35
+
36
+ // ============================================================
37
+ // 环境变量检测
38
+ // ============================================================
39
+
40
+ export function detectEventContext(): EventContext {
41
+ if (process.env.CNB_ISSUE_IID) return 'issue';
42
+ if (process.env.CNB_PULL_REQUEST_IID) return 'pull_request';
43
+ return 'unknown';
44
+ }
45
+
46
+ function getRepo(): string {
47
+ return process.env.CNB_REPO_SLUG || '<CNB_REPO_SLUG>';
48
+ }
49
+
50
+ function getIssueNumber(): string {
51
+ return process.env.CNB_ISSUE_IID || '<CNB_ISSUE_IID>';
52
+ }
53
+
54
+ function getPRNumber(): string {
55
+ return process.env.CNB_PULL_REQUEST_IID || '<CNB_PULL_REQUEST_IID>';
56
+ }
57
+
58
+ // ============================================================
59
+ // 快捷命令定义
60
+ // ============================================================
61
+
62
+ const ISSUE_SHORTCUTS: ShortcutDefinition[] = [
63
+ { shortName: 'get', realTool: 'get-issue', description: '获取详情' },
64
+ { shortName: 'list-comments', realTool: 'list-issue-comments', description: '获取评论列表' },
65
+ { shortName: 'comment', realTool: 'post-issue-comment', description: '评论', dataTip: "--data '{\"body\":\"内容\"}'" },
66
+ { shortName: 'close', realTool: 'update-issue', description: '关闭', autoData: { state: 'closed', state_reason: 'completed' } },
67
+ { shortName: 'open', realTool: 'update-issue', description: '打开', autoData: { state: 'open', state_reason: 'reopened' } },
68
+ { shortName: 'list-labels', realTool: 'list-issue-labels', description: '查看标签' },
69
+ { shortName: 'add-labels', realTool: 'post-issue-labels', description: '添加标签', dataTip: "--data '{\"labels\":[\"bug\",\"feature\"]}'" },
70
+ { shortName: 'list-assignees', realTool: 'list-issue-assignees', description: '查看处理人' },
71
+ { shortName: 'add-assignees', realTool: 'post-issue-assignees', description: '添加处理人', dataTip: "--data '{\"assignees\":[\"username\"]}'" },
72
+ ];
73
+
74
+ const PR_SHORTCUTS: ShortcutDefinition[] = [
75
+ { shortName: 'get', realTool: 'get-pull', description: '获取详情' },
76
+ { shortName: 'list-files', realTool: 'list-pull-files', description: '获取文件变更' },
77
+ { shortName: 'list-commits', realTool: 'list-pull-commits', description: '获取提交记录' },
78
+ { shortName: 'list-comments', realTool: 'list-pull-comments', description: '获取评论列表' },
79
+ { shortName: 'comment', realTool: 'post-pull-comment', description: '评论', dataTip: "--data '{\"body\":\"内容\"}'" },
80
+ { shortName: 'list-labels', realTool: 'list-pull-labels', description: '查看标签' },
81
+ { shortName: 'add-labels', realTool: 'post-pull-labels', description: '添加标签', dataTip: "--data '{\"labels\":[\"ready\",\"approved\"]}'" },
82
+ { shortName: 'check-status', realTool: 'list-pull-commit-statuses', description: '查看 CI 状态' },
83
+ { shortName: 'list-reviews', realTool: 'list-pull-reviews', description: '查看评审列表' },
84
+ { shortName: 'list-assignees', realTool: 'list-pull-assignees', description: '查看处理人' },
85
+ ];
86
+
87
+ // ============================================================
88
+ // --short 帮助输出
89
+ // ============================================================
90
+
91
+ function formatShortcutList(
92
+ shortcuts: ShortcutDefinition[],
93
+ cliCmd: string,
94
+ moduleName: string,
95
+ ): string {
96
+ const maxDescLen = Math.max(...shortcuts.map((s) => s.description.length));
97
+
98
+ return shortcuts
99
+ .map((s) => {
100
+ const desc = s.description.padEnd(maxDescLen + 2, ' '); // 全角空格对齐
101
+ const cmd = `${cliCmd} ${moduleName} ${s.shortName}`;
102
+ const dataPart = s.dataTip ? ` ${s.dataTip}` : '';
103
+ return ` ${desc} ${cmd}${dataPart}`;
104
+ })
105
+ .join('\n');
106
+ }
107
+
108
+ export function showShort(): void {
109
+ const ctx = detectEventContext();
110
+ const cliCmd = process.env.CNB_CLI_CMD || 'cnb';
111
+ const repo = getRepo();
112
+
113
+ if (ctx === 'issue') {
114
+ const number = getIssueNumber();
115
+ const list = formatShortcutList(ISSUE_SHORTCUTS, cliCmd, 'issues');
116
+ console.log(`
117
+ 📋 当前场景: Issue 事件 (Issue #${number})
118
+ 仓库: ${repo}
119
+
120
+ 常用快捷命令(path 参数已从环境变量自动获取):
121
+ ${list}
122
+
123
+ 提示: 以上命令自动使用环境变量 CNB_REPO_SLUG 和 CNB_ISSUE_IID,无需手动传 --path
124
+ `);
125
+ } else if (ctx === 'pull_request') {
126
+ const number = getPRNumber();
127
+ const list = formatShortcutList(PR_SHORTCUTS, cliCmd, 'pulls');
128
+ console.log(`
129
+ 🔀 当前场景: Pull Request 事件 (PR #${number})
130
+ 仓库: ${repo}
131
+
132
+ 常用快捷命令(path 参数已从环境变量自动获取):
133
+ ${list}
134
+
135
+ 提示: 以上命令自动使用环境变量 CNB_REPO_SLUG 和 CNB_PULL_REQUEST_IID,无需手动传 --path
136
+ `);
137
+ } else {
138
+ // 未知上下文,两组都显示
139
+ const issueList = formatShortcutList(ISSUE_SHORTCUTS, cliCmd, 'issues');
140
+ const prList = formatShortcutList(PR_SHORTCUTS, cliCmd, 'pulls');
141
+ console.log(`
142
+ ⚠️ 未检测到 Issue/PR 事件上下文
143
+ (CNB_ISSUE_IID 和 CNB_PULL_REQUEST_IID 均未设置)
144
+
145
+ 📋 Issue 场景常用命令(需要设置 CNB_ISSUE_IID 环境变量):
146
+ ${issueList}
147
+
148
+ 🔀 PR 场景常用命令(需要设置 CNB_PULL_REQUEST_IID 环境变量):
149
+ ${prList}
150
+
151
+ 提示: 快捷命令需要以下环境变量:
152
+ CNB_REPO_SLUG - 仓库路径
153
+ CNB_ISSUE_IID - Issue 编号 (Issue 事件)
154
+ CNB_PULL_REQUEST_IID - PR 编号 (PR 事件)
155
+ `);
156
+ }
157
+ }
158
+
159
+ // ============================================================
160
+ // 快捷命令解析
161
+ // ============================================================
162
+
163
+ /**
164
+ * 尝试将用户输入的 module + tool 解析为快捷命令
165
+ *
166
+ * 即使环境变量未设置,也会完成 shortName → realTool 的映射,
167
+ * 只是 autoPath 为 null,调用方需要用户手动传 --path。
168
+ *
169
+ * @returns 解析结果,如果不是快捷命令则返回 null
170
+ */
171
+ export function resolveShortcut(
172
+ moduleName: string | undefined,
173
+ toolName: string | undefined,
174
+ ): ResolvedShortcut | null {
175
+ if (!moduleName || !toolName) return null;
176
+
177
+ // 根据 module 名确定对应的快捷命令表
178
+ let shortcuts: ShortcutDefinition[] | null = null;
179
+ let autoPath: Record<string, string> | null = null;
180
+
181
+ if (moduleName === 'issues') {
182
+ shortcuts = ISSUE_SHORTCUTS;
183
+ const repo = process.env.CNB_REPO_SLUG;
184
+ const number = process.env.CNB_ISSUE_IID;
185
+ if (repo && number) {
186
+ autoPath = { repo, number };
187
+ }
188
+ } else if (moduleName === 'pulls') {
189
+ shortcuts = PR_SHORTCUTS;
190
+ const repo = process.env.CNB_REPO_SLUG;
191
+ const number = process.env.CNB_PULL_REQUEST_IID;
192
+ if (repo && number) {
193
+ autoPath = { repo, number };
194
+ }
195
+ }
196
+
197
+ if (!shortcuts) return null;
198
+
199
+ // 在快捷命令表中查找匹配的 shortName
200
+ const matched = shortcuts.find((s) => s.shortName === toolName);
201
+ if (!matched) return null;
202
+
203
+ return {
204
+ tool: matched.realTool,
205
+ autoPath: autoPath || {},
206
+ autoData: matched.autoData || null,
207
+ };
208
+ }