@kevisual/cnb 0.0.33 → 0.0.35

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,33 +1,31 @@
1
1
  import { createSkill, tool } from '@kevisual/router'
2
2
  import { app } from '../../app.ts'
3
3
 
4
- if (!app.hasRoute('call')) {
5
- // "调用 path: cnb key: list-repos"
6
- app.route({
7
- path: 'call',
8
- key: '',
9
- description: '调用',
10
- middleware: ['admin-auth'],
11
- metadata: {
12
- tags: ['opencode'],
13
- ...createSkill({
14
- skill: 'call-app',
15
- title: '调用app应用',
16
- summary: '调用router的应用, 参数path, key, payload',
17
- args: {
18
- path: tool.schema.string().describe('应用路径,例如 cnb'),
19
- key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
20
- payload: tool.schema.object({}).optional().describe('调用参数'),
21
- }
22
- })
23
- },
24
- }).define(async (ctx) => {
25
- const { path, key } = ctx.query;
26
- console.log('call app', ctx.query);
27
- if (!path) {
28
- ctx.throw('路径path不能为空');
29
- }
30
- const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
31
- ctx.forward(res);
32
- }).addTo(app)
33
- }
4
+ // "调用 path: cnb key: list-repos"
5
+ app.route({
6
+ path: 'call',
7
+ key: '',
8
+ description: '调用',
9
+ middleware: ['auth-admin'],
10
+ metadata: {
11
+ tags: ['opencode'],
12
+ ...createSkill({
13
+ skill: 'call-app',
14
+ title: '调用app应用',
15
+ summary: '调用router的应用, 参数path, key, payload',
16
+ args: {
17
+ path: tool.schema.string().describe('应用路径,例如 cnb'),
18
+ key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
19
+ payload: tool.schema.object({}).optional().describe('调用参数'),
20
+ }
21
+ })
22
+ },
23
+ }).define(async (ctx) => {
24
+ const { path, key } = ctx.query;
25
+ console.log('call app', ctx.query);
26
+ if (!path) {
27
+ ctx.throw('路径path不能为空');
28
+ }
29
+ const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
30
+ ctx.forward(res);
31
+ }).addTo(app, { overwrite: false })
@@ -0,0 +1,424 @@
1
+ import { app } from '../../app.ts';
2
+ import { useKey } from '@kevisual/context'
3
+ import { getLiveMdContent } from './live/live-content.ts';
4
+ import z from 'zod';
5
+ const notCNBCheck = (ctx: any) => {
6
+ const isCNB = useKey('CNB');
7
+ if (!isCNB) {
8
+ ctx.body = {
9
+ title: '非 cnb-board 环境',
10
+ list: []
11
+ }
12
+ return true;
13
+ }
14
+ return false;
15
+ }
16
+ app.route({
17
+ path: 'cnb_board',
18
+ key: 'live',
19
+ description: '获取cnb-board live的mdContent内容',
20
+ middleware: ['auth-admin'],
21
+ metadata: {
22
+ args: {
23
+ more: z.boolean().optional().describe('是否获取更多系统信息,默认false'),
24
+ }
25
+ }
26
+ }).define(async (ctx) => {
27
+ const more = ctx.query?.more ?? false
28
+ if (notCNBCheck(ctx)) return;
29
+ const list = getLiveMdContent({ more: more });
30
+ ctx.body = {
31
+ title: '开发环境模式配置',
32
+ list,
33
+ };
34
+ }).addTo(app);
35
+
36
+ app.route({
37
+ path: 'cnb_board',
38
+ key: 'live_repo_info',
39
+ description: '获取cnb-board live的repo信息',
40
+ middleware: ['auth-admin']
41
+ }).define(async (ctx) => {
42
+ const repoSlug = useKey('CNB_REPO_SLUG') || '';
43
+ const repoName = useKey('CNB_REPO_NAME') || '';
44
+ const repoId = useKey('CNB_REPO_ID') || '';
45
+ const repoUrlHttps = useKey('CNB_REPO_UR if (notCNBCheck(ctx)) return;L_HTTPS') || '';
46
+ if (notCNBCheck(ctx)) return;
47
+ // 从 repoSlug 提取仓库名称
48
+ const repoNameFromSlug = repoSlug.split('/').pop() || '';
49
+
50
+ const labels = [
51
+ {
52
+ title: 'CNB_REPO_SLUG',
53
+ value: repoSlug,
54
+ description: '目标仓库路径,格式为 group_slug / repo_name,group_slug / sub_gourp_slug /.../repo_name'
55
+ },
56
+ {
57
+ title: 'CNB_REPO_SLUG_LOWERCASE',
58
+ value: repoSlug.toLowerCase(),
59
+ description: '目标仓库路径小写格式'
60
+ },
61
+ {
62
+ title: 'CNB_REPO_NAME',
63
+ value: repoName || repoNameFromSlug,
64
+ description: '目标仓库名称'
65
+ },
66
+ {
67
+ title: 'CNB_REPO_NAME_LOWERCASE',
68
+ value: (repoName || repoNameFromSlug).toLowerCase(),
69
+ description: '目标仓库名称小写格式'
70
+ },
71
+ {
72
+ title: 'CNB_REPO_ID',
73
+ value: repoId,
74
+ description: '目标仓库的 id'
75
+ },
76
+ {
77
+ title: 'CNB_REPO_URL_HTTPS',
78
+ value: repoUrlHttps,
79
+ description: '目标仓库 https 地址'
80
+ }
81
+ ]
82
+ ctx.body = {
83
+ title: 'CNB_BOARD_LIVE_REPO_INFO',
84
+ list: labels
85
+ };
86
+ }).addTo(app);
87
+
88
+ // 构建类变量
89
+ app.route({
90
+ path: 'cnb_board',
91
+ key: 'live_build_info',
92
+ description: '获取cnb-board live的构建信息',
93
+ middleware: ['auth-admin']
94
+ }).define(async (ctx) => {
95
+ if (notCNBCheck(ctx)) return;
96
+ const labels = [
97
+ {
98
+ title: 'CNB_BUILD_ID',
99
+ value: useKey('CNB_BUILD_ID') || '',
100
+ description: '当前构建的流水号,全局唯一'
101
+ },
102
+ {
103
+ title: 'CNB_BUILD_WEB_URL',
104
+ value: useKey('CNB_BUILD_WEB_URL') || '',
105
+ description: '当前构建的日志地址'
106
+ },
107
+ {
108
+ title: 'CNB_BUILD_START_TIME',
109
+ value: useKey('CNB_BUILD_START_TIME') || '',
110
+ description: '当前构建的开始时间,UTC 格式,示例 2025-08-21T09:13:45.803Z'
111
+ },
112
+ {
113
+ title: 'CNB_BUILD_USER',
114
+ value: useKey('CNB_BUILD_USER') || '',
115
+ description: '当前构建的触发者用户名'
116
+ },
117
+ {
118
+ title: 'CNB_BUILD_USER_NICKNAME',
119
+ value: useKey('CNB_BUILD_USER_NICKNAME') || '',
120
+ description: '当前构建的触发者昵称'
121
+ },
122
+ {
123
+ title: 'CNB_BUILD_USER_EMAIL',
124
+ value: useKey('CNB_BUILD_USER_EMAIL') || '',
125
+ description: '当前构建的触发者邮箱'
126
+ },
127
+ {
128
+ title: 'CNB_BUILD_USER_ID',
129
+ value: useKey('CNB_BUILD_USER_ID') || '',
130
+ description: '当前构建的触发者 id'
131
+ },
132
+ {
133
+ title: 'CNB_BUILD_USER_NPC_SLUG',
134
+ value: useKey('CNB_BUILD_USER_NPC_SLUG') || '',
135
+ description: '当前构建若为 NPC 触发,则为 NPC 所属仓库的路径'
136
+ },
137
+ {
138
+ title: 'CNB_BUILD_USER_NPC_NAME',
139
+ value: useKey('CNB_BUILD_USER_NPC_NAME') || '',
140
+ description: '当前构建若为 NPC 触发,则为 NPC 角色名'
141
+ },
142
+ {
143
+ title: 'CNB_BUILD_STAGE_NAME',
144
+ value: useKey('CNB_BUILD_STAGE_NAME') || '',
145
+ description: '当前构建的 stage 名称'
146
+ },
147
+ {
148
+ title: 'CNB_BUILD_JOB_NAME',
149
+ value: useKey('CNB_BUILD_JOB_NAME') || '',
150
+ description: '当前构建的 job 名称'
151
+ },
152
+ {
153
+ title: 'CNB_BUILD_JOB_KEY',
154
+ value: useKey('CNB_BUILD_JOB_KEY') || '',
155
+ description: '当前构建的 job key,同 stage 下唯一'
156
+ },
157
+ {
158
+ title: 'CNB_BUILD_WORKSPACE',
159
+ value: useKey('CNB_BUILD_WORKSPACE') || '',
160
+ description: '自定义 shell 脚本执行的工作空间根目录'
161
+ },
162
+ {
163
+ title: 'CNB_BUILD_FAILED_MSG',
164
+ value: useKey('CNB_BUILD_FAILED_MSG') || '',
165
+ description: '流水线构建失败的错误信息,可在 failStages 中使用'
166
+ },
167
+ {
168
+ title: 'CNB_BUILD_FAILED_STAGE_NAME',
169
+ value: useKey('CNB_BUILD_FAILED_STAGE_NAME') || '',
170
+ description: '流水线构建失败的 stage 的名称,可在 failStages 中使用'
171
+ },
172
+ {
173
+ title: 'CNB_PIPELINE_NAME',
174
+ value: useKey('CNB_PIPELINE_NAME') || '',
175
+ description: '当前 pipeline 的 name,没声明时为空'
176
+ },
177
+ {
178
+ title: 'CNB_PIPELINE_KEY',
179
+ value: useKey('CNB_PIPELINE_KEY') || '',
180
+ description: '当前 pipeline 的索引 key,例如 pipeline-0'
181
+ },
182
+ {
183
+ title: 'CNB_PIPELINE_ID',
184
+ value: useKey('CNB_PIPELINE_ID') || '',
185
+ description: '当前 pipeline 的 id,全局唯一字符串'
186
+ },
187
+ {
188
+ title: 'CNB_PIPELINE_DOCKER_IMAGE',
189
+ value: useKey('CNB_PIPELINE_DOCKER_IMAGE') || '',
190
+ description: '当前 pipeline 所使用的 docker image,如:alpine:latest'
191
+ },
192
+ {
193
+ title: 'CNB_PIPELINE_STATUS',
194
+ value: useKey('CNB_PIPELINE_STATUS') || '',
195
+ description: '当前流水线的构建状态,可在 endStages 中查看,其可能的值包括:success、error、cancel'
196
+ },
197
+ {
198
+ title: 'CNB_PIPELINE_MAX_RUN_TIME',
199
+ value: useKey('CNB_PIPELINE_MAX_RUN_TIME') || '',
200
+ description: '流水线最大运行时间,单位为毫秒'
201
+ },
202
+ {
203
+ title: 'CNB_RUNNER_IP',
204
+ value: useKey('CNB_RUNNER_IP') || '',
205
+ description: '当前 pipeline 所在 Runner 的 ip'
206
+ },
207
+ {
208
+ title: 'CNB_CPUS',
209
+ value: useKey('CNB_CPUS') || '',
210
+ description: '当前构建流水线可以使用的最大 CPU 核数'
211
+ },
212
+ {
213
+ title: 'CNB_MEMORY',
214
+ value: useKey('CNB_MEMORY') || '',
215
+ description: '当前构建流水线可以使用的最大内存大小,单位为 GiB'
216
+ },
217
+ {
218
+ title: 'CNB_IS_RETRY',
219
+ value: useKey('CNB_IS_RETRY') || '',
220
+ description: '当前构建是否由 rebuild 触发'
221
+ },
222
+ {
223
+ title: 'HUSKY_SKIP_INSTALL',
224
+ value: useKey('HUSKY_SKIP_INSTALL') || '',
225
+ description: '兼容 ci 环境下 husky'
226
+ }
227
+ ]
228
+ ctx.body = {
229
+ title: 'CNB_BOARD_LIVE_BUILD_INFO',
230
+ list: labels
231
+ };
232
+ }).addTo(app);
233
+
234
+ // PR/合并类变量
235
+ app.route({
236
+ path: 'cnb_board',
237
+ key: 'live_pull_info',
238
+ description: '获取cnb-board live的PR信息',
239
+ middleware: ['auth-admin']
240
+ }).define(async (ctx) => {
241
+ const labels = [
242
+ {
243
+ title: 'CNB_PULL_REQUEST',
244
+ value: useKey('CNB_PULL_REQUEST') || '',
245
+ description: '对于由 pull_request、pull_request.update、pull_request.target 触发的构建,值为 true,否则为 false'
246
+ },
247
+ {
248
+ title: 'CNB_PULL_REQUEST_LIKE',
249
+ value: useKey('CNB_PULL_REQUEST_LIKE') || '',
250
+ description: '对于由 合并类事件 触发的构建,值为 true,否则为 false'
251
+ },
252
+ {
253
+ title: 'CNB_PULL_REQUEST_PROPOSER',
254
+ value: useKey('CNB_PULL_REQUEST_PROPOSER') || '',
255
+ description: '对于由 合并类事件 触发的构建,值为提出 PR 者名称,否则为空字符串'
256
+ },
257
+ {
258
+ title: 'CNB_PULL_REQUEST_TITLE',
259
+ value: useKey('CNB_PULL_REQUEST_TITLE') || '',
260
+ description: '对于由 合并类事件 触发的构建,值为提 PR 时候填写的标题,否则为空字符串'
261
+ },
262
+ {
263
+ title: 'CNB_PULL_REQUEST_BRANCH',
264
+ value: useKey('CNB_PULL_REQUEST_BRANCH') || '',
265
+ description: '对于由 合并类事件 触发的构建,值为发起 PR 的源分支名称,否则为空字符串'
266
+ },
267
+ {
268
+ title: 'CNB_PULL_REQUEST_SHA',
269
+ value: useKey('CNB_PULL_REQUEST_SHA') || '',
270
+ description: '对于由 合并类事件 触发的构建,值为当前 PR 源分支最新的提交 sha,否则为空字符串'
271
+ },
272
+ {
273
+ title: 'CNB_PULL_REQUEST_TARGET_SHA',
274
+ value: useKey('CNB_PULL_REQUEST_TARGET_SHA') || '',
275
+ description: '对于由 合并类事件 触发的构建,值为当前 PR 目标分支最新的提交 sha,否则为空字符串'
276
+ },
277
+ {
278
+ title: 'CNB_PULL_REQUEST_MERGE_SHA',
279
+ value: useKey('CNB_PULL_REQUEST_MERGE_SHA') || '',
280
+ description: '对于由 pull_request.merged 触发的构建,值为合并后的 sha;对于 pull_request 等触发的构建,值为预合并后的 sha,否则为空字符串'
281
+ },
282
+ {
283
+ title: 'CNB_PULL_REQUEST_SLUG',
284
+ value: useKey('CNB_PULL_REQUEST_SLUG') || '',
285
+ description: '对于由 合并类事件 触发的构建,值为源仓库的仓库 slug,如 group_slug/repo_name,否则为空字符串'
286
+ },
287
+ {
288
+ title: 'CNB_PULL_REQUEST_ACTION',
289
+ value: useKey('CNB_PULL_REQUEST_ACTION') || '',
290
+ description: '对于由 合并类事件 触发的构建,可能的值有:created(新建PR)、code_update(源分支push)、status_update(评审通过或CI状态变更),否则为空字符串'
291
+ },
292
+ {
293
+ title: 'CNB_PULL_REQUEST_ID',
294
+ value: useKey('CNB_PULL_REQUEST_ID') || '',
295
+ description: '对于由 合并类事件 触发的构建,值为当前或者关联 PR 的全局唯一 id,否则为空字符串'
296
+ },
297
+ {
298
+ title: 'CNB_PULL_REQUEST_IID',
299
+ value: useKey('CNB_PULL_REQUEST_IID') || '',
300
+ description: '对于由 合并类事件 触发的构建,值为当前或者关联 PR 在仓库中的编号 iid,否则为空字符串'
301
+ },
302
+ {
303
+ title: 'CNB_PULL_REQUEST_REVIEWERS',
304
+ value: useKey('CNB_PULL_REQUEST_REVIEWERS') || '',
305
+ description: '对于由 合并类事件 触发的构建,值为评审人列表,多个以 , 分隔,否则为空字符串'
306
+ },
307
+ {
308
+ title: 'CNB_PULL_REQUEST_REVIEW_STATE',
309
+ value: useKey('CNB_PULL_REQUEST_REVIEW_STATE') || '',
310
+ description: '对于由 合并类事件 触发的构建,有评审者且有人通过评审为 approve,有评审者但无人通过评审为 unapprove,否则为空字符串'
311
+ },
312
+ {
313
+ title: 'CNB_REVIEW_REVIEWED_BY',
314
+ value: useKey('CNB_REVIEW_REVIEWED_BY') || '',
315
+ description: '对于由 合并类事件 触发的构建,值为同意评审的评审人列表,多个以 , 分隔,否则为空字符串'
316
+ },
317
+ {
318
+ title: 'CNB_REVIEW_LAST_REVIEWED_BY',
319
+ value: useKey('CNB_REVIEW_LAST_REVIEWED_BY') || '',
320
+ description: '对于由 合并类事件 触发的构建,值为最后一个同意评审的评审人,否则为空字符串'
321
+ },
322
+ {
323
+ title: 'CNB_PULL_REQUEST_IS_WIP',
324
+ value: useKey('CNB_PULL_REQUEST_IS_WIP') || '',
325
+ description: '对于由 合并类事件 触发的构建,值为 true、false,表示 PR 是否被设置为 [WIP],否则为空字符串'
326
+ }
327
+ ]
328
+ ctx.body = {
329
+ title: 'CNB_BOARD_LIVE_PULL_INFO',
330
+ list: labels
331
+ };
332
+ }).addTo(app);
333
+
334
+ // NPC 类变量
335
+ app.route({
336
+ path: 'cnb_board',
337
+ key: 'live_npc_info',
338
+ description: '获取cnb-board live的NPC信息',
339
+ middleware: ['auth-admin']
340
+ }).define(async (ctx) => {
341
+ if (notCNBCheck(ctx)) return;
342
+ const labels = [
343
+ {
344
+ title: 'CNB_NPC_SLUG',
345
+ value: useKey('CNB_NPC_SLUG') || '',
346
+ description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库路径,否则为空字符串'
347
+ },
348
+ {
349
+ title: 'CNB_NPC_NAME',
350
+ value: useKey('CNB_NPC_NAME') || '',
351
+ description: '对于 NPC 事件触发的构建,值为 NPC 角色名,否则为空字符串'
352
+ },
353
+ {
354
+ title: 'CNB_NPC_SHA',
355
+ value: useKey('CNB_NPC_SHA') || '',
356
+ description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库默认分支最新提交的 sha,否则为空字符串'
357
+ },
358
+ {
359
+ title: 'CNB_NPC_PROMPT',
360
+ value: useKey('CNB_NPC_PROMPT') || '',
361
+ description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色 Prompt,否则为空字符串'
362
+ },
363
+ {
364
+ title: 'CNB_NPC_AVATAR',
365
+ value: useKey('CNB_NPC_AVATAR') || '',
366
+ description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色头像,否则为空字符串'
367
+ },
368
+ {
369
+ title: 'CNB_NPC_ENABLE_THINKING',
370
+ value: useKey('CNB_NPC_ENABLE_THINKING') || '',
371
+ description: '对于 @npc 事件触发的构建,值为 NPC 角色是否开启思考,否则为空字符串'
372
+ }
373
+ ]
374
+ ctx.body = {
375
+ title: 'CNB_BOARD_LIVE_NPC_INFO',
376
+ list: labels
377
+ };
378
+ }).addTo(app);
379
+
380
+ // 评论类变量
381
+ app.route({
382
+ path: 'cnb_board',
383
+ key: 'live_comment_info',
384
+ description: '获取cnb-board live的评论信息',
385
+ middleware: ['auth-admin']
386
+ }).define(async (ctx) => {
387
+ if (notCNBCheck(ctx)) return;
388
+ const labels = [
389
+ {
390
+ title: 'CNB_COMMENT_ID',
391
+ value: useKey('CNB_COMMENT_ID') || '',
392
+ description: '对于评论事件触发的构建,值为评论全局唯一 ID,否则为空字符串'
393
+ },
394
+ {
395
+ title: 'CNB_COMMENT_BODY',
396
+ value: useKey('CNB_COMMENT_BODY') || '',
397
+ description: '对于评论事件触发的构建,值为评论内容,否则为空字符串'
398
+ },
399
+ {
400
+ title: 'CNB_COMMENT_TYPE',
401
+ value: useKey('CNB_COMMENT_TYPE') || '',
402
+ description: '对于 PR 代码评审评论,值为 diff_note;对于 PR 非代码评审评论以及 Issue 评论,值为 note;否则为空字符串'
403
+ },
404
+ {
405
+ title: 'CNB_COMMENT_FILE_PATH',
406
+ value: useKey('CNB_COMMENT_FILE_PATH') || '',
407
+ description: '对于 PR 代码评审评论,值为评论所在文件,否则为空字符串'
408
+ },
409
+ {
410
+ title: 'CNB_COMMENT_RANGE',
411
+ value: useKey('CNB_COMMENT_RANGE') || '',
412
+ description: '对于 PR 代码评审评论,值为评论所在代码行。如,单行为 L12,多行为 L13-L16,否则为空字符串'
413
+ },
414
+ {
415
+ title: 'CNB_REVIEW_ID',
416
+ value: useKey('CNB_REVIEW_ID') || '',
417
+ description: '对于 PR 代码评审,值为评审 ID,否则为空字符串'
418
+ }
419
+ ]
420
+ ctx.body = {
421
+ title: 'CNB_BOARD_LIVE_COMMENT_INFO',
422
+ list: labels
423
+ };
424
+ }).addTo(app);
File without changes
@@ -0,0 +1,38 @@
1
+ import { app } from '../../app.ts';
2
+ import './cnb-dev-env.ts';
3
+ import { useKey } from '@kevisual/context';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ export const execCommand = (command: string, options: { cwd?: string } = {}) => {
7
+ const { cwd } = options;
8
+ return spawnSync(command, {
9
+ stdio: 'inherit',
10
+ shell: true,
11
+ cwd: cwd,
12
+ env: process.env,
13
+ });
14
+ };
15
+ app.route({
16
+ path: 'cnb-board',
17
+ key: 'is-cnb-board',
18
+ description: '检查是否是 cnb-board 环境',
19
+ middleware: ['auth-admin']
20
+ }).define(async (ctx) => {
21
+ const isCNB = useKey('CNB');
22
+ ctx.body = {
23
+ isCNB: !!isCNB,
24
+ };
25
+ }).addTo(app);
26
+
27
+
28
+
29
+
30
+ app.route({
31
+ path: 'cnb-board',
32
+ key: 'exit',
33
+ description: 'cnb的工作环境退出程序',
34
+ middleware: ['auth-admin'],
35
+ }).define(async (ctx) => {
36
+ const cmd = 'kill 1';
37
+ execCommand(cmd);
38
+ }).addTo(app);