@kevisual/cnb 0.0.2 → 0.0.4
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/agent/app.ts +10 -3
- package/agent/opencode.ts +6 -0
- package/agent/routes/call/index.ts +32 -0
- package/agent/routes/cnb-env/env.ts +48 -0
- package/agent/routes/cnb-env/index.ts +3 -1
- package/agent/routes/cnb-env/vscode.ts +94 -0
- package/agent/routes/index.ts +15 -2
- package/agent/routes/issues/index.ts +2 -0
- package/agent/routes/issues/issue.ts +80 -0
- package/agent/routes/issues/list.ts +3 -0
- package/agent/routes/knowledge/ai.ts +142 -0
- package/agent/routes/knowledge/index.ts +1 -0
- package/agent/routes/repo/index.ts +2 -53
- package/agent/routes/repo/list.ts +45 -0
- package/agent/routes/repo/repo.ts +110 -0
- package/agent/routes/repo/skills.ts +0 -0
- package/agent/routes/user/check.ts +19 -8
- package/agent/routes/workspace/index.ts +146 -2
- package/agent/routes/workspace/skills.ts +64 -0
- package/dist/opencode.d.ts +5 -0
- package/dist/opencode.js +81008 -0
- package/package.json +22 -27
- package/src/cnb-core.ts +3 -3
- package/src/common/cnb-env.ts +57 -0
- package/src/index.ts +2 -9
- package/src/issue/index.ts +1 -0
- package/src/knowledge/index.ts +14 -1
- package/src/repo/index.ts +22 -5
- package/src/{workspace.ts → workspace/index.ts} +2 -2
- package/agent/opencode-plugin.ts +0 -35
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { app, cnb } from '../../app.ts';
|
|
2
|
+
import { createSkill, Skill } from '@kevisual/router'
|
|
3
|
+
import { tool } from "@opencode-ai/plugin/tool"
|
|
4
|
+
|
|
5
|
+
// 创建一个仓库 kevisual/test-repo
|
|
6
|
+
app.route({
|
|
7
|
+
path: 'cnb',
|
|
8
|
+
key: 'create-repo',
|
|
9
|
+
description: '创建代码仓库, 参数name, visibility, description',
|
|
10
|
+
middleware: ['auth'],
|
|
11
|
+
metadata: {
|
|
12
|
+
tags: ['opencode'],
|
|
13
|
+
...createSkill({
|
|
14
|
+
skill: 'create-repo',
|
|
15
|
+
title: '创建代码仓库',
|
|
16
|
+
args: {
|
|
17
|
+
name: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
18
|
+
visibility: tool.schema.string().describe('代码仓库可见性, public 或 private').default('public'),
|
|
19
|
+
description: tool.schema.string().describe('代码仓库描述'),
|
|
20
|
+
},
|
|
21
|
+
summary: '创建一个新的代码仓库',
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}).define(async (ctx) => {
|
|
25
|
+
const name = ctx.query?.name;
|
|
26
|
+
const visibility = ctx.query?.visibility ?? 'public';
|
|
27
|
+
const description = ctx.query?.description ?? '';
|
|
28
|
+
|
|
29
|
+
if (!name) {
|
|
30
|
+
ctx.throw(400, '缺少参数 name');
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
|
|
34
|
+
const res = await cnb.repo.createRepo({
|
|
35
|
+
name,
|
|
36
|
+
visibility,
|
|
37
|
+
description,
|
|
38
|
+
});
|
|
39
|
+
ctx.forward(res);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
ctx.code = 200
|
|
42
|
+
ctx.body = { content: 'JS仓库可能已存在' }
|
|
43
|
+
}
|
|
44
|
+
}).addTo(app);
|
|
45
|
+
|
|
46
|
+
app.route({
|
|
47
|
+
path: 'cnb',
|
|
48
|
+
key: 'create-repo-file',
|
|
49
|
+
description: '在代码仓库中创建文件, repoName, filePath, content, encoding',
|
|
50
|
+
middleware: ['auth'],
|
|
51
|
+
metadata: {
|
|
52
|
+
tags: ['opencode'],
|
|
53
|
+
...createSkill({
|
|
54
|
+
skill: 'create-repo-file',
|
|
55
|
+
title: '在代码仓库中创建文件',
|
|
56
|
+
summary: `在代码仓库中创建文件, encoding 可选,默认 raw`,
|
|
57
|
+
args: {
|
|
58
|
+
repoName: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
|
|
59
|
+
filePath: tool.schema.string().describe('文件路径, 如 src/index.ts'),
|
|
60
|
+
content: tool.schema.string().describe('文本的字符串的内容'),
|
|
61
|
+
encoding: tool.schema.string().describe('编码方式,如 raw').optional(),
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
}).define(async (ctx) => {
|
|
66
|
+
const repoName = ctx.query?.repoName;
|
|
67
|
+
const filePath = ctx.query?.filePath;
|
|
68
|
+
const content = ctx.query?.content;
|
|
69
|
+
const encoding = ctx.query?.encoding ?? 'raw';
|
|
70
|
+
|
|
71
|
+
if (!repoName || !filePath || !content) {
|
|
72
|
+
ctx.throw(400, '缺少参数 repoName, filePath 或 content');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const res = await cnb.repo.createCommit(repoName, {
|
|
76
|
+
message: `添加文件 ${filePath} 通过 API `,
|
|
77
|
+
files: [
|
|
78
|
+
{ path: filePath, content, encoding },
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
ctx.forward(res);
|
|
82
|
+
}).addTo(app);
|
|
83
|
+
|
|
84
|
+
// 删除一个仓库 kevisual/test-repo
|
|
85
|
+
app.route({
|
|
86
|
+
path: 'cnb',
|
|
87
|
+
key: 'delete-repo',
|
|
88
|
+
description: '删除代码仓库, 参数name',
|
|
89
|
+
middleware: ['auth'],
|
|
90
|
+
metadata: {
|
|
91
|
+
tags: ['opencode'],
|
|
92
|
+
...createSkill({
|
|
93
|
+
skill: 'delete-repo',
|
|
94
|
+
title: '删除代码仓库',
|
|
95
|
+
args: {
|
|
96
|
+
name: tool.schema.string().describe('代码仓库名称'),
|
|
97
|
+
},
|
|
98
|
+
summary: '删除一个代码仓库',
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}).define(async (ctx) => {
|
|
102
|
+
const name = ctx.query?.name;
|
|
103
|
+
|
|
104
|
+
if (!name) {
|
|
105
|
+
ctx.throw(400, '缺少参数 name');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const res = await cnb.repo.deleteRepo(name);
|
|
109
|
+
ctx.forward(res);
|
|
110
|
+
}).addTo(app);
|
|
File without changes
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSkill } from '@kevisual/router';
|
|
2
|
+
import { app, cnb } from '../../app.ts';
|
|
3
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
app.route({
|
|
@@ -7,27 +9,36 @@ app.route({
|
|
|
7
9
|
description: '检查用户登录状态,参数checkToken,default true; checkCookie, default false',
|
|
8
10
|
middleware: ['auth'],
|
|
9
11
|
metadata: {
|
|
10
|
-
tags: ['opencode']
|
|
12
|
+
tags: ['opencode'],
|
|
13
|
+
...createSkill({
|
|
14
|
+
skill: 'cnb-login-verify',
|
|
15
|
+
title: 'CNB 登录验证信息',
|
|
16
|
+
summary: '验证 CNB 登录信息是否有效',
|
|
17
|
+
args: {
|
|
18
|
+
checkToken: tool.schema.boolean().describe('是否检查 Token 的有效性').default(true),
|
|
19
|
+
checkCookie: tool.schema.boolean().describe('是否检查 Cookie 的有效性').default(false),
|
|
20
|
+
},
|
|
21
|
+
})
|
|
11
22
|
}
|
|
12
23
|
}).define(async (ctx) => {
|
|
13
24
|
const checkToken = ctx.query?.checkToken ?? true;
|
|
14
25
|
const checkCookie = ctx.query?.checkCookie ?? false;
|
|
15
|
-
let
|
|
26
|
+
let content = '';
|
|
16
27
|
if (checkToken) {
|
|
17
28
|
const res = await cnb.user.getUser();
|
|
18
29
|
if (res?.code !== 200) {
|
|
19
|
-
|
|
30
|
+
content += `Token 无效,请检查 CNB_TOKEN 配置。\n`;
|
|
20
31
|
} else {
|
|
21
|
-
|
|
32
|
+
content += `Token 有效,Token用户昵称:${res.data?.nickname}\n`;
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
35
|
if (checkCookie) {
|
|
25
36
|
const res = await cnb.user.getCurrentUser();
|
|
26
37
|
if (res?.code !== 200) {
|
|
27
|
-
|
|
38
|
+
content += `Cookie 无效,请检查 CNB_COOKIE 配置。\n`;
|
|
28
39
|
} else {
|
|
29
|
-
|
|
40
|
+
content += `Cookie 有效,当前Cookie用户:${res.data?.nickname}\n`;
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
|
-
ctx.body = {
|
|
43
|
+
ctx.body = { content };
|
|
33
44
|
}).addTo(app);
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSkill, tool } from '@kevisual/router';
|
|
2
|
+
import { app, cnb } from '../../app.ts';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
import './skills.ts';
|
|
2
5
|
|
|
6
|
+
// 启动工作空间
|
|
3
7
|
app.route({
|
|
4
8
|
path: 'cnb',
|
|
5
9
|
key: 'start-workspace',
|
|
6
10
|
description: '启动开发工作空间, 参数 repo',
|
|
7
11
|
middleware: ['auth'],
|
|
8
12
|
metadata: {
|
|
9
|
-
tags: ['opencode']
|
|
13
|
+
tags: ['opencode'],
|
|
14
|
+
...createSkill({
|
|
15
|
+
skill: 'start-workspace',
|
|
16
|
+
title: '启动cnb工作空间',
|
|
17
|
+
summary: '启动cnb工作空间',
|
|
18
|
+
args: {
|
|
19
|
+
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
|
20
|
+
branch: tool.schema.string().optional().describe('分支名称,默认主分支'),
|
|
21
|
+
ref: tool.schema.string().optional().describe('提交引用,例如 commit sha'),
|
|
22
|
+
},
|
|
23
|
+
})
|
|
10
24
|
}
|
|
11
25
|
}).define(async (ctx) => {
|
|
12
26
|
const repo = ctx.query?.repo;
|
|
@@ -21,3 +35,133 @@ app.route({
|
|
|
21
35
|
});
|
|
22
36
|
ctx.forward(res);
|
|
23
37
|
}).addTo(app);
|
|
38
|
+
|
|
39
|
+
// 获取cnb工作空间列表
|
|
40
|
+
app.route({
|
|
41
|
+
path: 'cnb',
|
|
42
|
+
key: 'list-workspace',
|
|
43
|
+
description: '获取cnb开发工作空间列表,可选参数 status=running 获取运行中的环境',
|
|
44
|
+
middleware: ['auth'],
|
|
45
|
+
metadata: {
|
|
46
|
+
tags: ['opencode'],
|
|
47
|
+
...createSkill({
|
|
48
|
+
skill: 'list-workspace',
|
|
49
|
+
title: '列出cnb工作空间',
|
|
50
|
+
summary: '列出cnb工作空间列表,支持按状态过滤, status 可选值 running 或 closed',
|
|
51
|
+
args: {
|
|
52
|
+
status: tool.schema.string().optional().describe('开发环境状态,running: 运行中,closed: 已关闭和停止的'),
|
|
53
|
+
page: tool.schema.number().optional().describe('分页页码,默认 1'),
|
|
54
|
+
pageSize: tool.schema.number().optional().describe('分页大小,默认 20,最大 100'),
|
|
55
|
+
slug: tool.schema.string().optional().describe('仓库路径,例如 groupname/reponame'),
|
|
56
|
+
branch: tool.schema.string().optional().describe('分支名称'),
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}).define(async (ctx) => {
|
|
61
|
+
const { status = 'running', page, pageSize, slug, branch } = ctx.query || {};
|
|
62
|
+
const res = await cnb.workspace.list({
|
|
63
|
+
status: status as 'running' | 'closed' | undefined,
|
|
64
|
+
page: page ?? 1,
|
|
65
|
+
pageSize: pageSize ?? 100,
|
|
66
|
+
});
|
|
67
|
+
ctx.forward({ code: 200, message: 'success', data: res });
|
|
68
|
+
}).addTo(app);
|
|
69
|
+
|
|
70
|
+
// 获取工作空间详情
|
|
71
|
+
app.route({
|
|
72
|
+
path: 'cnb',
|
|
73
|
+
key: 'get-workspace',
|
|
74
|
+
description: '获取工作空间详情,通过 repo 和 sn 获取',
|
|
75
|
+
middleware: ['auth'],
|
|
76
|
+
metadata: {
|
|
77
|
+
tags: ['opencode'],
|
|
78
|
+
...createSkill({
|
|
79
|
+
skill: 'get-workspace',
|
|
80
|
+
title: '获取工作空间详情',
|
|
81
|
+
summary: '获取工作空间详细信息',
|
|
82
|
+
args: {
|
|
83
|
+
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
|
84
|
+
sn: tool.schema.string().describe('工作空间流水线的 sn'),
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}).define(async (ctx) => {
|
|
89
|
+
const repo = ctx.query?.repo;
|
|
90
|
+
const sn = ctx.query?.sn;
|
|
91
|
+
if (!repo) {
|
|
92
|
+
ctx.throw(400, '缺少参数 repo');
|
|
93
|
+
}
|
|
94
|
+
if (!sn) {
|
|
95
|
+
ctx.throw(400, '缺少参数 sn');
|
|
96
|
+
}
|
|
97
|
+
const res = await cnb.workspace.getDetail(repo, sn);
|
|
98
|
+
ctx.forward({ code: 200, message: 'success', data: res });
|
|
99
|
+
}).addTo(app);
|
|
100
|
+
|
|
101
|
+
// 删除工作空间
|
|
102
|
+
app.route({
|
|
103
|
+
path: 'cnb',
|
|
104
|
+
key: 'delete-workspace',
|
|
105
|
+
description: '删除工作空间,通过 pipelineId 或 sn',
|
|
106
|
+
middleware: ['auth'],
|
|
107
|
+
metadata: {
|
|
108
|
+
tags: ['opencode'],
|
|
109
|
+
...createSkill({
|
|
110
|
+
skill: 'delete-workspace',
|
|
111
|
+
title: '删除工作空间',
|
|
112
|
+
summary: '删除工作空间,pipelineId 和 sn 二选一',
|
|
113
|
+
args: {
|
|
114
|
+
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
|
115
|
+
sn: tool.schema.string().optional().describe('流水线构建号'),
|
|
116
|
+
sns: tool.schema.array(z.string()).optional().describe('流水线构建号'),
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}).define(async (ctx) => {
|
|
121
|
+
const pipelineId = ctx.query?.pipelineId;
|
|
122
|
+
const sn = ctx.query?.sn;
|
|
123
|
+
const sns = ctx.query?.sns;
|
|
124
|
+
if (!pipelineId && !sn && (!sns || sns.length === 0)) {
|
|
125
|
+
ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个');
|
|
126
|
+
}
|
|
127
|
+
if (sns && sns.length > 0) {
|
|
128
|
+
const results = [];
|
|
129
|
+
for (const snItem of sns) {
|
|
130
|
+
const res = await cnb.workspace.deleteWorkspace({ sn: snItem });
|
|
131
|
+
results.push(res);
|
|
132
|
+
}
|
|
133
|
+
ctx.forward({ code: 200, message: 'success', data: results });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const res = await cnb.workspace.deleteWorkspace({ pipelineId, sn });
|
|
137
|
+
ctx.forward(res);
|
|
138
|
+
}).addTo(app);
|
|
139
|
+
|
|
140
|
+
// 停止工作空间
|
|
141
|
+
app.route({
|
|
142
|
+
path: 'cnb',
|
|
143
|
+
key: 'stop-workspace',
|
|
144
|
+
description: '停止工作空间,通过 pipelineId 或 sn',
|
|
145
|
+
middleware: ['auth'],
|
|
146
|
+
metadata: {
|
|
147
|
+
tags: ['opencode'],
|
|
148
|
+
...createSkill({
|
|
149
|
+
skill: 'stop-workspace',
|
|
150
|
+
title: '停止工作空间',
|
|
151
|
+
summary: '停止运行中的工作空间',
|
|
152
|
+
args: {
|
|
153
|
+
pipelineId: tool.schema.string().optional().describe('流水线 ID,优先使用'),
|
|
154
|
+
sn: tool.schema.string().optional().describe('流水线构建号'),
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
}).define(async (ctx) => {
|
|
159
|
+
const pipelineId = ctx.query?.pipelineId;
|
|
160
|
+
const sn = ctx.query?.sn;
|
|
161
|
+
if (!pipelineId && !sn) {
|
|
162
|
+
ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个');
|
|
163
|
+
}
|
|
164
|
+
const res = await cnb.workspace.stopWorkspace({ pipelineId, sn });
|
|
165
|
+
ctx.forward({ code: 200, message: 'success', data: res });
|
|
166
|
+
}).addTo(app);
|
|
167
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createSkill, tool } from '@kevisual/router';
|
|
2
|
+
import { app, cnb } from '../../app.ts';
|
|
3
|
+
|
|
4
|
+
// 批量删除已停止的cnb工作空间
|
|
5
|
+
// app.route({
|
|
6
|
+
// path: 'cnb',
|
|
7
|
+
// key: 'clean-closed-workspace-skill',
|
|
8
|
+
// description: '批量删除已停止的cnb工作空间',
|
|
9
|
+
// middleware: ['auth'],
|
|
10
|
+
// metadata: {
|
|
11
|
+
// tags: ['opencode'],
|
|
12
|
+
// ...createSkill({
|
|
13
|
+
// skill: 'clean-closed-workspace-skill',
|
|
14
|
+
// title: '清理已关闭的cnb工作空间',
|
|
15
|
+
// summary: '批量删除已停止的cnb工作空间,释放资源',
|
|
16
|
+
// args: {
|
|
17
|
+
// question: tool.schema.string().optional().describe('具体的要求的信息'),
|
|
18
|
+
// }
|
|
19
|
+
// })
|
|
20
|
+
// }
|
|
21
|
+
// }).define(async (ctx) => {
|
|
22
|
+
// const question = ctx.query?.question || '';
|
|
23
|
+
// let content = `这是一个技能任务, 批量删除已停止的cnb工作空间,释放资源
|
|
24
|
+
// 执行步骤:
|
|
25
|
+
// 1. 执行list-workspace,获取状态为 closed 的工作空间列表,提取sn
|
|
26
|
+
// 2. 执行delete-workspace技能,传入sns列表的数组,批量删除工作空间`
|
|
27
|
+
// if (question) {
|
|
28
|
+
// content += `\n注意用户的具体要求是:${question}`;
|
|
29
|
+
// }
|
|
30
|
+
// ctx.body = { content }
|
|
31
|
+
// }).addTo(app);
|
|
32
|
+
|
|
33
|
+
// 批量删除已停止的cnb工作空间
|
|
34
|
+
app.route({
|
|
35
|
+
path: 'cnb',
|
|
36
|
+
key: 'clean-closed-workspace',
|
|
37
|
+
description: '批量删除已停止的cnb工作空间',
|
|
38
|
+
middleware: ['auth'],
|
|
39
|
+
metadata: {
|
|
40
|
+
tags: ['opencode'],
|
|
41
|
+
...createSkill({
|
|
42
|
+
skill: 'clean-closed-workspace',
|
|
43
|
+
title: '清理已关闭的cnb工作空间',
|
|
44
|
+
summary: '批量删除已停止的cnb工作空间,释放资源',
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}).define(async (ctx) => {
|
|
48
|
+
const closedWorkspaces = await cnb.workspace.list({ status: 'closed' });
|
|
49
|
+
if (closedWorkspaces.code !== 200) {
|
|
50
|
+
ctx.throw(500, '获取已关闭工作空间列表失败');
|
|
51
|
+
}
|
|
52
|
+
const list = closedWorkspaces.data?.list || [];
|
|
53
|
+
if (list.length === 0) {
|
|
54
|
+
ctx.forward({ code: 200, message: '没有已关闭的工作空间需要删除', data: [] });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const sns = list.map(ws => ws.sn);
|
|
58
|
+
const results = [];
|
|
59
|
+
for (const sn of sns) {
|
|
60
|
+
const res = await cnb.workspace.deleteWorkspace({ sn });
|
|
61
|
+
results.push(res);
|
|
62
|
+
}
|
|
63
|
+
ctx.forward({ code: 200, message: '已关闭的工作空间删除完成', data: results });
|
|
64
|
+
}).addTo(app);
|