@cnbcool/cnb-api-generate 2.0.0 → 2.1.1
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.
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { printJSON } from './print-json';
|
|
1
2
|
import { summarizeResponse } from './summary-extractors';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -49,7 +50,7 @@ export function isStandardResponse(response: any): boolean {
|
|
|
49
50
|
export function formatOutput(response: any, verbose: boolean, summary: boolean, toolKey: string): string {
|
|
50
51
|
// --verbose 模式:输出完整原始信息
|
|
51
52
|
if (verbose) {
|
|
52
|
-
return
|
|
53
|
+
return printJSON(response, verbose);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// converter 可能返回裸 data(没有标准 {status, data} 结构),直接输出
|
|
@@ -57,10 +58,10 @@ export function formatOutput(response: any, verbose: boolean, summary: boolean,
|
|
|
57
58
|
if (summary) {
|
|
58
59
|
const summarized = summarizeResponse(response, toolKey);
|
|
59
60
|
if (summarized !== null) {
|
|
60
|
-
return
|
|
61
|
+
return printJSON(summarized);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
return
|
|
64
|
+
return printJSON(response);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// 精简响应
|
|
@@ -74,5 +75,5 @@ export function formatOutput(response: any, verbose: boolean, summary: boolean,
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
return
|
|
78
|
+
return printJSON(compact);
|
|
78
79
|
}
|
|
@@ -111,7 +111,15 @@ export function formatParams(
|
|
|
111
111
|
} else if (bodyProps[key]) {
|
|
112
112
|
// body 字段(无冲突,直接用原 key)
|
|
113
113
|
if (!formatted.data) formatted.data = {};
|
|
114
|
-
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
formatted.data[key] = value;
|
|
116
|
+
} else if (bodyProps[key].type === 'string') {
|
|
117
|
+
// schema 定义为 string 类型时,保持原始字符串,不做 JSON 解析
|
|
118
|
+
// 避免纯数字字符串(如 "123")被转为 number
|
|
119
|
+
formatted.data[key] = value;
|
|
120
|
+
} else {
|
|
121
|
+
formatted.data[key] = tryParseJSON(value as string);
|
|
122
|
+
}
|
|
115
123
|
} else {
|
|
116
124
|
// 处理 d- 前缀(冲突时 CLI 以 d- 前缀传入)
|
|
117
125
|
const stripped = (key.startsWith('d-') || key.startsWith('d_'))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将数据转换为格式化的 JSON 字符串
|
|
3
|
+
* @param data 要序列化的数据(可以是任意类型)缩进)
|
|
4
|
+
* @param verbose 是否 verbose 模式
|
|
5
|
+
* @returns 格式化的 JSON 字符串,如果序列化失败则返回错误信息的 JSON
|
|
6
|
+
*/
|
|
7
|
+
export function printJSON(
|
|
8
|
+
data: Record<string, any> | Array<any>,
|
|
9
|
+
verbose: boolean = false
|
|
10
|
+
): string {
|
|
11
|
+
return verbose ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
12
|
+
}
|
|
@@ -3,7 +3,7 @@ import { trimSummary } from './trim-summary';
|
|
|
3
3
|
import { helpData } from './help-data';
|
|
4
4
|
import { flatOptionsData } from './flat-options-data';
|
|
5
5
|
import { executeAction } from './execute-action';
|
|
6
|
-
import {
|
|
6
|
+
import { ISSUE_SHORTCUTS, PR_SHORTCUTS, type ShortcutDefinition } from '../shortcuts';
|
|
7
7
|
|
|
8
8
|
interface FlatOption {
|
|
9
9
|
optKey: string;
|
|
@@ -22,19 +22,22 @@ interface FlatOption {
|
|
|
22
22
|
* 为单个 tool 子命令注册 Commander options
|
|
23
23
|
* 直接读取预生成的 flat-options.json 中已扁平化的参数定义,无需运行时计算
|
|
24
24
|
*/
|
|
25
|
-
function registerToolOptions(toolCmd: Command, moduleName: string, toolName: string): void {
|
|
25
|
+
function registerToolOptions(toolCmd: Command, moduleName: string, toolName: string, skipPathMandatory?: boolean): void {
|
|
26
26
|
const moduleOptions = flatOptionsData[moduleName];
|
|
27
27
|
if (!moduleOptions) return;
|
|
28
28
|
const toolOptions: Record<string, FlatOption> = moduleOptions[toolName];
|
|
29
29
|
if (!toolOptions) return;
|
|
30
30
|
|
|
31
31
|
for (const [, opt] of Object.entries(toolOptions) as [string, FlatOption][]) {
|
|
32
|
+
// 快捷命令跳过 path 参数(由环境变量自动注入)
|
|
33
|
+
if (skipPathMandatory && opt.source === 'path') continue;
|
|
34
|
+
|
|
32
35
|
const option = new Option(
|
|
33
36
|
`--${opt.optKey} ${opt.valuePlaceholder}`.trim(),
|
|
34
37
|
opt.description,
|
|
35
38
|
);
|
|
36
39
|
|
|
37
|
-
if (opt.required && opt.source === 'path') {
|
|
40
|
+
if (opt.required && opt.source === 'path' && !skipPathMandatory) {
|
|
38
41
|
option.makeOptionMandatory();
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -61,6 +64,15 @@ function registerToolOptions(toolCmd: Command, moduleName: string, toolName: str
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
/**
|
|
68
|
+
* 获取指定模块的快捷命令列表
|
|
69
|
+
*/
|
|
70
|
+
function getShortcutsForModule(moduleName: string): ShortcutDefinition[] {
|
|
71
|
+
if (moduleName === 'issues') return ISSUE_SHORTCUTS;
|
|
72
|
+
if (moduleName === 'pulls') return PR_SHORTCUTS;
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
/**
|
|
65
77
|
* 为每个模块注册子命令,使 cnb <module> -h 显示模块帮助
|
|
66
78
|
* 每个 tool 的参数通过 Commander 的 option 系统注册,--help 自动展示参数说明
|
|
@@ -75,6 +87,9 @@ export function registerModuleCommands(program: Command): void {
|
|
|
75
87
|
.description(`${moduleName} 模块 (${toolEntries.length} tools)`)
|
|
76
88
|
.helpOption('-h, --help', '显示帮助文档');
|
|
77
89
|
|
|
90
|
+
// 收集已注册的子命令名,避免快捷命令与真实 tool 名冲突
|
|
91
|
+
const registeredNames = new Set<string>();
|
|
92
|
+
|
|
78
93
|
// 为每个 tool 注册子命令
|
|
79
94
|
for (const [toolName, toolInfo] of Object.entries(tools) as [string, any][]) {
|
|
80
95
|
const summary = trimSummary(toolInfo.summary || '');
|
|
@@ -95,6 +110,30 @@ export function registerModuleCommands(program: Command): void {
|
|
|
95
110
|
|
|
96
111
|
// 根据 flat-options.json 注册参数选项
|
|
97
112
|
registerToolOptions(toolCmd, moduleName, toolName);
|
|
113
|
+
registeredNames.add(toolName);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 为快捷命令注册 Commander 子命令(别名)
|
|
117
|
+
// 复用对应真实 tool 的选项定义,但跳过 path 参数(由环境变量自动注入)
|
|
118
|
+
const shortcuts = getShortcutsForModule(moduleName);
|
|
119
|
+
for (const shortcut of shortcuts) {
|
|
120
|
+
// 如果快捷命令名与真实 tool 名重复,跳过(真实 tool 优先)
|
|
121
|
+
if (registeredNames.has(shortcut.shortName)) continue;
|
|
122
|
+
|
|
123
|
+
const shortcutCmd = sub
|
|
124
|
+
.command(shortcut.shortName)
|
|
125
|
+
.description(`[快捷] ${shortcut.description}`)
|
|
126
|
+
.option('-v, --verbose', '输出完整原始响应')
|
|
127
|
+
.helpOption('-h, --help', '显示帮助文档')
|
|
128
|
+
.showHelpAfterError(true)
|
|
129
|
+
.action(async (opts: Record<string, any>) => {
|
|
130
|
+
// 传入快捷命令名,executeAction 内部会通过 resolveShortcut 转换
|
|
131
|
+
await executeAction(moduleName, shortcut.shortName, opts, sub);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// 复用真实 tool 的选项,跳过 path 参数
|
|
135
|
+
registerToolOptions(shortcutCmd, moduleName, shortcut.realTool, true);
|
|
136
|
+
registeredNames.add(shortcut.shortName);
|
|
98
137
|
}
|
|
99
138
|
|
|
100
139
|
// 允许未匹配到子命令时也能正常执行(不报错)
|
|
@@ -102,18 +141,13 @@ export function registerModuleCommands(program: Command): void {
|
|
|
102
141
|
sub.allowExcessArguments();
|
|
103
142
|
|
|
104
143
|
// 模块级 action:处理未匹配到子命令的情况
|
|
105
|
-
// 如果输入了快捷命令名(如 cnb issues get),Commander 匹配不到真实 tool 子命令,
|
|
106
|
-
// 会走到这里,此时尝试 resolveShortcut 将其转换为真实 tool
|
|
107
144
|
sub
|
|
108
145
|
.argument('[tool]', '工具名称(支持快捷命令)')
|
|
109
|
-
.action(async (toolArg: string | undefined,
|
|
146
|
+
.action(async (toolArg: string | undefined, _opts: Record<string, any>) => {
|
|
110
147
|
if (toolArg) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await executeAction(moduleName, toolArg, opts, sub);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
148
|
+
// 此处已不会再命中快捷命令(因为已注册为子命令),仅作兜底
|
|
149
|
+
sub.help();
|
|
150
|
+
return;
|
|
117
151
|
}
|
|
118
152
|
sub.help();
|
|
119
153
|
});
|
package/client/shortcuts.ts
CHANGED
|
@@ -67,7 +67,7 @@ function getPRNumber(): string {
|
|
|
67
67
|
// 快捷命令定义
|
|
68
68
|
// ============================================================
|
|
69
69
|
|
|
70
|
-
const ISSUE_SHORTCUTS: ShortcutDefinition[] = [
|
|
70
|
+
export const ISSUE_SHORTCUTS: ShortcutDefinition[] = [
|
|
71
71
|
{ shortName: 'get', realTool: 'get-issue', description: '获取详情' },
|
|
72
72
|
{ shortName: 'list-comments', realTool: 'list-issue-comments', description: '获取评论列表' },
|
|
73
73
|
{ shortName: 'comment', realTool: 'post-issue-comment', description: '评论', dataTip: "--body 内容" },
|
|
@@ -81,7 +81,7 @@ const ISSUE_SHORTCUTS: ShortcutDefinition[] = [
|
|
|
81
81
|
{ shortName: 'upload-image', realTool: 'post-issue-image-asset-upload-url', description: '上传图片', upload: true, dataTip: "--file 图片路径" },
|
|
82
82
|
];
|
|
83
83
|
|
|
84
|
-
const PR_SHORTCUTS: ShortcutDefinition[] = [
|
|
84
|
+
export const PR_SHORTCUTS: ShortcutDefinition[] = [
|
|
85
85
|
{ shortName: 'get', realTool: 'get-pull', description: '获取详情' },
|
|
86
86
|
{ shortName: 'list-files', realTool: 'list-pull-files', description: '获取文件变更' },
|
|
87
87
|
{ shortName: 'list-commits', realTool: 'list-pull-commits', description: '获取提交记录' },
|
package/package.json
CHANGED
package/skills-template/SKILL.md
CHANGED
|
@@ -12,30 +12,29 @@ description: CNB OpenAPI 交互能力,支持仓库、Issue、PR、流水线、
|
|
|
12
12
|
issues 快捷命令:
|
|
13
13
|
- `<$CNB_CLI_CMD$> issues get` — 获取 Issue 详情
|
|
14
14
|
- `<$CNB_CLI_CMD$> issues list-comments` — 列出 Issue 评论
|
|
15
|
-
- `<$CNB_CLI_CMD$> issues comment --
|
|
15
|
+
- `<$CNB_CLI_CMD$> issues comment --body 内容` — 发表 Issue 评论
|
|
16
16
|
- `<$CNB_CLI_CMD$> issues close` — 关闭 Issue
|
|
17
17
|
- `<$CNB_CLI_CMD$> issues open` — 打开 Issue
|
|
18
18
|
- `<$CNB_CLI_CMD$> issues list-labels` — 列出 Issue 标签
|
|
19
|
-
- `<$CNB_CLI_CMD$> issues add-labels --
|
|
19
|
+
- `<$CNB_CLI_CMD$> issues add-labels --labels bug --labels feature` — 添加 Issue 标签
|
|
20
20
|
- `<$CNB_CLI_CMD$> issues list-assignees` — 查看 Issue 处理人
|
|
21
|
-
- `<$CNB_CLI_CMD$> issues add-assignees --
|
|
22
|
-
- `<$CNB_CLI_CMD$> issues upload-file --
|
|
23
|
-
- `<$CNB_CLI_CMD$> issues upload-image --
|
|
21
|
+
- `<$CNB_CLI_CMD$> issues add-assignees --assignees username` — 添加 Issue 处理人
|
|
22
|
+
- `<$CNB_CLI_CMD$> issues upload-file --file 文件路径` — 上传文件到 Issue
|
|
23
|
+
- `<$CNB_CLI_CMD$> issues upload-image --file 图片路径` — 上传图片到 Issue
|
|
24
24
|
|
|
25
25
|
pulls 快捷命令:
|
|
26
26
|
- `<$CNB_CLI_CMD$> pulls get` — 获取 PR 详情
|
|
27
27
|
- `<$CNB_CLI_CMD$> pulls list-files` — 列出 PR 变更文件
|
|
28
28
|
- `<$CNB_CLI_CMD$> pulls list-commits` — 列出 PR 提交记录
|
|
29
29
|
- `<$CNB_CLI_CMD$> pulls list-comments` — 列出 PR 评论
|
|
30
|
-
- `<$CNB_CLI_CMD$> pulls comment --
|
|
30
|
+
- `<$CNB_CLI_CMD$> pulls comment --body 内容` — 发表 PR 评论
|
|
31
31
|
- `<$CNB_CLI_CMD$> pulls list-labels` — 列出 PR 标签
|
|
32
|
-
- `<$CNB_CLI_CMD$> pulls add-labels --
|
|
32
|
+
- `<$CNB_CLI_CMD$> pulls add-labels --labels ready --labels approved` — 添加 PR 标签
|
|
33
33
|
- `<$CNB_CLI_CMD$> pulls check-status` — 查看 CI 状态
|
|
34
34
|
- `<$CNB_CLI_CMD$> pulls list-reviews` — 查看评审列表
|
|
35
35
|
- `<$CNB_CLI_CMD$> pulls list-assignees` — 查看 PR 处理人
|
|
36
|
-
- `<$CNB_CLI_CMD$> pulls
|
|
37
|
-
- `<$CNB_CLI_CMD$> pulls upload-
|
|
38
|
-
- `<$CNB_CLI_CMD$> pulls upload-image --data '{"file":"本地图片路径"}'` — 上传图片到 PR
|
|
36
|
+
- `<$CNB_CLI_CMD$> pulls upload-file --file 文件路径` — 上传文件到 PR
|
|
37
|
+
- `<$CNB_CLI_CMD$> pulls upload-image --file 图片路径` — 上传图片到 PR
|
|
39
38
|
|
|
40
39
|
注意:
|
|
41
40
|
- **路径参数自动注入**:仓库 slug、Issue/PR 编号优先从环境变量自动获取,无需额外传入任何其他参数
|