更新代码仓库相关功能,修改 API 参数,添加删除仓库功能,更新文档和测试用例
This commit is contained in:
@@ -6,9 +6,8 @@ import { nanoid } from 'nanoid';
|
|||||||
|
|
||||||
export const config = useConfig()
|
export const config = useConfig()
|
||||||
export const cnb = useContextKey<CNB>('cnb', () => {
|
export const cnb = useContextKey<CNB>('cnb', () => {
|
||||||
const token = useKey('CNB_TOKEN') as string
|
const token = useKey('CNB_API_KEY') as string
|
||||||
const cookie = useKey('CNB_COOKIE') as string
|
const cookie = useKey('CNB_COOKIE') as string
|
||||||
|
|
||||||
return new CNB({ token: token, cookie: cookie });
|
return new CNB({ token: token, cookie: cookie });
|
||||||
})
|
})
|
||||||
export const appId = nanoid();
|
export const appId = nanoid();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { type Plugin } from "@opencode-ai/plugin"
|
|||||||
import { app, cnb, appId } from './index.ts';
|
import { app, cnb, appId } from './index.ts';
|
||||||
import { } from 'es-toolkit'
|
import { } from 'es-toolkit'
|
||||||
import { Skill } from "@kevisual/router";
|
import { Skill } from "@kevisual/router";
|
||||||
|
|
||||||
const routes = app.routes.filter(r => {
|
const routes = app.routes.filter(r => {
|
||||||
const metadata = r.metadata as Skill
|
const metadata = r.metadata as Skill
|
||||||
if (metadata && metadata.tags && metadata.tags.includes('opencode')) {
|
if (metadata && metadata.tags && metadata.tags.includes('opencode')) {
|
||||||
@@ -10,30 +11,46 @@ const routes = app.routes.filter(r => {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
const toolSkills = routes.reduce((acc, route) => {
|
|
||||||
|
// opencode run "请使用 cnb-login-verify 工具验证登录信信息,检查cookie"
|
||||||
|
export const CnbPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
||||||
|
return {
|
||||||
|
'tool': {
|
||||||
|
...routes.reduce((acc, route) => {
|
||||||
const metadata = route.metadata as Skill
|
const metadata = route.metadata as Skill
|
||||||
acc[metadata.skill!] = {
|
acc[metadata.skill!] = {
|
||||||
name: metadata.title || metadata.skill,
|
name: metadata.title || metadata.skill,
|
||||||
description: metadata.summary || '',
|
description: metadata.summary || '',
|
||||||
args: metadata.args || {},
|
args: metadata.args || {},
|
||||||
async execute(args: Record<string, any>) {
|
async execute(args: Record<string, any>) {
|
||||||
|
console.log(`Executing skill ${metadata.skill} with args:`, args);
|
||||||
|
await client.app.log({
|
||||||
|
body: {
|
||||||
|
service: 'cnb',
|
||||||
|
level: 'info',
|
||||||
|
message: `Executing skill ${metadata.skill} with args: ${JSON.stringify(args)}`
|
||||||
|
}
|
||||||
|
});
|
||||||
const res = await app.run({
|
const res = await app.run({
|
||||||
path: route.path,
|
path: route.path,
|
||||||
key: route.key,
|
key: route.key,
|
||||||
payload: args
|
payload: args
|
||||||
}, { appId });
|
}, { appId });
|
||||||
return res.data?.content || res.data || res;
|
if (res.code === 200) {
|
||||||
|
if (res.data?.content) {
|
||||||
|
return res.data.content;
|
||||||
|
}
|
||||||
|
const str = JSON.stringify(res.data || res, null, 2);
|
||||||
|
if (str.length > 5000) {
|
||||||
|
return str.slice(0, 5000) + '... (truncated)';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return `Error: ${res?.message || '无法获取结果'}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, any>);
|
}, {} as Record<string, any>)
|
||||||
console.log('CnbPlugin loaded skills:', Object.keys(toolSkills));
|
|
||||||
|
|
||||||
// opencode run "请使用 cnb-login-verify 工具验证登录信信息,检查cookie"
|
|
||||||
export const CnbPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
|
||||||
return {
|
|
||||||
'tool': {
|
|
||||||
...toolSkills
|
|
||||||
},
|
},
|
||||||
'tool.execute.before': async (opts) => {
|
'tool.execute.before': async (opts) => {
|
||||||
// console.log('CnbPlugin: tool.execute.before', opts.tool);
|
// console.log('CnbPlugin: tool.execute.before', opts.tool);
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ app.route({
|
|||||||
skill: 'create-repo',
|
skill: 'create-repo',
|
||||||
title: '创建代码仓库',
|
title: '创建代码仓库',
|
||||||
args: {
|
args: {
|
||||||
name: tool.schema.string().describe('代码仓库名称'),
|
name: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
|
||||||
|
visibility: tool.schema.string().describe('代码仓库可见性, public 或 private').default('public'),
|
||||||
description: tool.schema.string().describe('代码仓库描述'),
|
description: tool.schema.string().describe('代码仓库描述'),
|
||||||
},
|
},
|
||||||
summary: '创建一个新的代码仓库',
|
summary: '创建一个新的代码仓库',
|
||||||
@@ -20,14 +21,14 @@ app.route({
|
|||||||
}
|
}
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const name = ctx.query?.name;
|
const name = ctx.query?.name;
|
||||||
const visibility = ctx.query?.visibility ?? 'private';
|
const visibility = ctx.query?.visibility ?? 'public';
|
||||||
const description = ctx.query?.description ?? '';
|
const description = ctx.query?.description ?? '';
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
ctx.throw(400, '缺少参数 name');
|
ctx.throw(400, '缺少参数 name');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await cnb.repo.createRepo(cnb.group, {
|
const res = await cnb.repo.createRepo({
|
||||||
name,
|
name,
|
||||||
visibility,
|
visibility,
|
||||||
description,
|
description,
|
||||||
@@ -38,7 +39,7 @@ app.route({
|
|||||||
app.route({
|
app.route({
|
||||||
path: 'cnb',
|
path: 'cnb',
|
||||||
key: 'create-repo-file',
|
key: 'create-repo-file',
|
||||||
description: '在代码仓库中创建文件, 参数repoName, path, content, encoding',
|
description: '在代码仓库中创建文件, name, path, content, encoding',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: ['opencode'],
|
tags: ['opencode'],
|
||||||
@@ -46,7 +47,7 @@ app.route({
|
|||||||
skill: 'create-repo-file',
|
skill: 'create-repo-file',
|
||||||
title: '在代码仓库中创建文件',
|
title: '在代码仓库中创建文件',
|
||||||
args: {
|
args: {
|
||||||
repoName: tool.schema.string().describe('代码仓库名称'),
|
name: tool.schema.string().describe('代码仓库名称'),
|
||||||
path: tool.schema.string().describe('文件路径, 如 src/index.ts'),
|
path: tool.schema.string().describe('文件路径, 如 src/index.ts'),
|
||||||
content: tool.schema.string().describe('文件内容'),
|
content: tool.schema.string().describe('文件内容'),
|
||||||
encoding: tool.schema.string().describe('编码方式, 默认为 raw').optional(),
|
encoding: tool.schema.string().describe('编码方式, 默认为 raw').optional(),
|
||||||
@@ -55,16 +56,16 @@ app.route({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const repoName = ctx.query?.repoName;
|
const name = ctx.query?.name;
|
||||||
const path = ctx.query?.path;
|
const path = ctx.query?.path;
|
||||||
const content = ctx.query?.content;
|
const content = ctx.query?.content;
|
||||||
const encoding = ctx.query?.encoding ?? 'raw';
|
const encoding = ctx.query?.encoding ?? 'raw';
|
||||||
|
|
||||||
if (!repoName || !path || !content) {
|
if (!name || !path || !content) {
|
||||||
ctx.throw(400, '缺少参数 repoName, path 或 content');
|
ctx.throw(400, '缺少参数 name, path 或 content');
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await cnb.repo.createCommit(repoName, {
|
const res = await cnb.repo.createCommit(name, {
|
||||||
message: `添加文件 ${path} 通过 API `,
|
message: `添加文件 ${path} 通过 API `,
|
||||||
files: [
|
files: [
|
||||||
{ path, content, encoding },
|
{ path, content, encoding },
|
||||||
@@ -72,3 +73,31 @@ app.route({
|
|||||||
});
|
});
|
||||||
ctx.forward(res);
|
ctx.forward(res);
|
||||||
}).addTo(app);
|
}).addTo(app);
|
||||||
|
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'cnb',
|
||||||
|
key: 'delete-repo',
|
||||||
|
description: '删除代码仓库, 参数name',
|
||||||
|
middleware: ['auth'],
|
||||||
|
metadata: {
|
||||||
|
tags: ['opencode'],
|
||||||
|
...createSkill({
|
||||||
|
skill: 'delete-repo',
|
||||||
|
title: '删除代码仓库',
|
||||||
|
args: {
|
||||||
|
name: tool.schema.string().describe('代码仓库名称'),
|
||||||
|
},
|
||||||
|
summary: '删除一个代码仓库',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const name = ctx.query?.name;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
ctx.throw(400, '缺少参数 name');
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await cnb.repo.deleteRepo(name);
|
||||||
|
ctx.forward(res);
|
||||||
|
}).addTo(app);
|
||||||
14
create-repo-test.ts
Normal file
14
create-repo-test.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { CNB } from './src/index.ts';
|
||||||
|
|
||||||
|
const cnb = new CNB({
|
||||||
|
token: 'cIDfLOOIr1Trt15cdnwfndupEZG',
|
||||||
|
cookie: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await cnb.repo.createRepo('kevisual', {
|
||||||
|
name: 'exam-kevisual',
|
||||||
|
description: 'exam repository for kevisual',
|
||||||
|
visibility: 'public'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Result:', JSON.stringify(res, null, 2));
|
||||||
@@ -1,14 +1,66 @@
|
|||||||
---
|
---
|
||||||
name: create-new-repo
|
name: create-new-repo
|
||||||
description: 创建一个新的代码仓库,并自动添加必要的配置文件。
|
description: 创建一个基本的新的代码仓库,并自动添加必要的配置文件。
|
||||||
---
|
---
|
||||||
|
|
||||||
# 创建新的代码仓库
|
# 创建新的代码仓库
|
||||||
该技能用于创建一个新的代码仓库,并自动添加必要的配置文件,如 `.cnb.yml` 和 `opencode.json`。
|
该技能用于创建一个新的代码仓库,并自动添加必要的配置文件,如 `.cnb.yml`
|
||||||
|
|
||||||
|
|
||||||
## 调用工具链
|
## 调用工具链
|
||||||
|
|
||||||
1. 执行`create-repo`工具
|
1. 执行`create-repo`工具
|
||||||
2. 判断是否需要立刻需要云开发打开
|
2. 添加.cnb.yml配置文件
|
||||||
3. 如果需要,执行`open-cloud-editor`工具
|
|
||||||
4. 返回创建的仓库信息和云开发环境信息(如果适用)
|
### .cnb.yml配置文件内容示例
|
||||||
|
假设新仓库名称为 REPO_NAME,则
|
||||||
|
|
||||||
|
TO_REPO 为 kevisual/REPO_NAME
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .cnb.yml
|
||||||
|
include:
|
||||||
|
- https://cnb.cool/kevisual/cnb/-/blob/main/.cnb/template.yml
|
||||||
|
|
||||||
|
.common_env: &common_env
|
||||||
|
env:
|
||||||
|
TO_REPO: kevisual/cnb
|
||||||
|
TO_URL: git.xiongxiao.me
|
||||||
|
imports:
|
||||||
|
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
||||||
|
|
||||||
|
$:
|
||||||
|
vscode:
|
||||||
|
- docker:
|
||||||
|
image: docker.cnb.cool/kevisual/dev-env:latest
|
||||||
|
services:
|
||||||
|
- vscode
|
||||||
|
- docker
|
||||||
|
imports: !reference [.common_env, imports]
|
||||||
|
# 开发环境启动后会执行的任务
|
||||||
|
# stages:
|
||||||
|
# - name: pnpm install
|
||||||
|
# script: pnpm install
|
||||||
|
stages: !reference [.dev_tempalte, stages]
|
||||||
|
|
||||||
|
.common_sync_to_gitea: &common_sync_to_gitea
|
||||||
|
- <<: *common_env
|
||||||
|
services: !reference [.common_sync_to_gitea_template, services]
|
||||||
|
stages: !reference [.common_sync_to_gitea_template, stages]
|
||||||
|
|
||||||
|
.common_sync_from_gitea: &common_sync_from_gitea
|
||||||
|
- <<: *common_env
|
||||||
|
services: !reference [.common_sync_from_gitea_template, services]
|
||||||
|
stages: !reference [.common_sync_from_gitea_template, stages]
|
||||||
|
|
||||||
|
main:
|
||||||
|
web_trigger_sync_to_gitea:
|
||||||
|
- <<: *common_sync_to_gitea
|
||||||
|
web_trigger_sync_from_gitea:
|
||||||
|
- <<: *common_sync_from_gitea
|
||||||
|
api_trigger_sync_to_gitea:
|
||||||
|
- <<: *common_sync_to_gitea
|
||||||
|
api_trigger_sync_from_gitea:
|
||||||
|
- <<: *common_sync_from_gitea
|
||||||
|
|
||||||
|
```
|
||||||
@@ -9,7 +9,6 @@ import { Mission } from "./mission/index.ts";
|
|||||||
import { AiBase } from "./ai/index.ts";
|
import { AiBase } from "./ai/index.ts";
|
||||||
|
|
||||||
type CNBOptions = CNBCoreOptions<{
|
type CNBOptions = CNBCoreOptions<{
|
||||||
group?: string;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export class CNB extends CNBCore {
|
export class CNB extends CNBCore {
|
||||||
@@ -21,7 +20,6 @@ export class CNB extends CNBCore {
|
|||||||
issue!: Issue;
|
issue!: Issue;
|
||||||
mission!: Mission;
|
mission!: Mission;
|
||||||
ai!: AiBase;
|
ai!: AiBase;
|
||||||
group!: string;
|
|
||||||
constructor(options: CNBOptions) {
|
constructor(options: CNBOptions) {
|
||||||
super({ token: options.token, cookie: options.cookie, cnb: options.cnb });
|
super({ token: options.token, cookie: options.cookie, cnb: options.cnb });
|
||||||
this.init(options);
|
this.init(options);
|
||||||
|
|||||||
@@ -6,27 +6,32 @@ export class Repo extends CNBCore {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 创建代码仓库
|
* 创建代码仓库
|
||||||
* @param group e.g. my-group
|
|
||||||
* @param data
|
* @param data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
createRepo(group: string, data: CreateRepoData): Promise<any> {
|
createRepo(data: CreateRepoData): Promise<any> {
|
||||||
|
const name = data.name;
|
||||||
|
const [group, repo] = name.includes('/') ? name.split('/') : ['', name];
|
||||||
const url = `/${group}/-/repos`;
|
const url = `/${group}/-/repos`;
|
||||||
let postData: CreateRepoData = {
|
let postData: CreateRepoData = {
|
||||||
...data,
|
...data,
|
||||||
description: data.description || '',
|
description: data.description || '',
|
||||||
name: data.name,
|
name: repo,
|
||||||
license: data.license || 'Unlicense',
|
license: data.license || 'Unlicense',
|
||||||
visibility: data.visibility || 'private',
|
visibility: data.visibility || 'private',
|
||||||
};
|
};
|
||||||
return this.post({ url, data: postData });
|
return this.post({ url, data: postData });
|
||||||
}
|
}
|
||||||
|
deleteRepo(name: string): Promise<any> {
|
||||||
|
const url = `https://cnb.cool/${name}`;
|
||||||
|
return this.delete({ url, useCookie: true });
|
||||||
|
}
|
||||||
async createCommit(repo: string, data: CreateCommitData): Promise<any> {
|
async createCommit(repo: string, data: CreateCommitData): Promise<any> {
|
||||||
const commitList = await this.getCommitList(repo, {
|
const commitList = await this.getCommitList(repo, {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 1,
|
page_size: 1,
|
||||||
}, { useOrigin: true }).catch((err) => {
|
}, { useOrigin: true }).catch((err) => {
|
||||||
console.error("Error fetching commit list:", err);
|
// console.error("Error fetching commit list:", err);
|
||||||
return []
|
return []
|
||||||
});
|
});
|
||||||
const preCommitSha = commitList.length > 0 ? commitList[0].sha : undefined;
|
const preCommitSha = commitList.length > 0 ? commitList[0].sha : undefined;
|
||||||
@@ -46,7 +51,14 @@ export class Repo extends CNBCore {
|
|||||||
delete postData.parent_commit_sha;
|
delete postData.parent_commit_sha;
|
||||||
delete postData.base_branch;
|
delete postData.base_branch;
|
||||||
}
|
}
|
||||||
return this.post({ url, data: postData, useCookie: true, });
|
return this.post({
|
||||||
|
url,
|
||||||
|
data: postData,
|
||||||
|
useCookie: true,
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/vnd.cnb.web+json',
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
createBlobs(repo: string, data: { content: string, encoding?: 'utf-8' | 'base64' }): Promise<any> {
|
createBlobs(repo: string, data: { content: string, encoding?: 'utf-8' | 'base64' }): Promise<any> {
|
||||||
const url = `/${repo}/-/git/blobs`;
|
const url = `/${repo}/-/git/blobs`;
|
||||||
|
|||||||
37
test/agent.ts
Normal file
37
test/agent.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { app, showMore } from './common.ts';
|
||||||
|
|
||||||
|
// const res = await app.run({
|
||||||
|
// path: 'cnb',
|
||||||
|
// key: 'create-repo',
|
||||||
|
// payload: {
|
||||||
|
// name: 'kevisual/exam',
|
||||||
|
// description: 'kevisual 创建的代码仓库',
|
||||||
|
// visibility: 'public',
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// console.log(showMore(res));
|
||||||
|
|
||||||
|
|
||||||
|
// const res2 = await app.run({
|
||||||
|
// path: 'cnb',
|
||||||
|
// key: 'create-repo-file',
|
||||||
|
// payload: {
|
||||||
|
// name: 'kevisual/exam',
|
||||||
|
// path: 'README.md',
|
||||||
|
// content: '# Example Skill\nThis is an example skill created via API.',
|
||||||
|
// encoding: 'raw',
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// console.log(showMore(res2));
|
||||||
|
|
||||||
|
// const deleteRes = await app.run({
|
||||||
|
// path: 'cnb',
|
||||||
|
// key: 'delete-repo',
|
||||||
|
// payload: {
|
||||||
|
// name: 'kevisual/exam',
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// console.log(showMore(deleteRes));
|
||||||
@@ -6,13 +6,16 @@ const config = useConfig()
|
|||||||
export const token = useKey("CNB_TOKEN") as string || '';
|
export const token = useKey("CNB_TOKEN") as string || '';
|
||||||
export const cookie = useKey("CNB_COOKIE") as string || '';
|
export const cookie = useKey("CNB_COOKIE") as string || '';
|
||||||
console.log('token', token)
|
console.log('token', token)
|
||||||
|
import { app } from '../agent/index.ts'
|
||||||
|
|
||||||
|
export { app }
|
||||||
export const cnb = new CNB({ token, cookie });
|
export const cnb = new CNB({ token, cookie });
|
||||||
export const showMore = (obj: any) => {
|
export const showMore = (obj: any) => {
|
||||||
return util.inspect(obj, { showHidden: false, depth: null, colors: true });
|
return util.inspect(obj, { showHidden: false, depth: null, colors: true });
|
||||||
}
|
}
|
||||||
const worksaceList = await cnb.workspace.list({ status: 'running' });
|
// const worksaceList = await cnb.workspace.list({ status: 'running' });
|
||||||
|
|
||||||
console.log("worksaceList", showMore(worksaceList));
|
// console.log("worksaceList", showMore(worksaceList));
|
||||||
|
|
||||||
// const sn = 'cnb-o18-1jbklfuoh'
|
// const sn = 'cnb-o18-1jbklfuoh'
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Repo } from "../src/repo";
|
import { Repo } from "../src/repo";
|
||||||
|
import { app } from '../agent/index.ts'
|
||||||
import { token, showMore, cookie } from "./common.ts";
|
import { token, showMore, cookie } from "./common.ts";
|
||||||
|
import util from 'node:util';
|
||||||
|
|
||||||
const repo = new Repo({ token: token, cookie: cookie });
|
const repo = new Repo({ token: token, cookie: cookie });
|
||||||
|
|
||||||
|
export { app }
|
||||||
// const res = await repo.createRepo({
|
// const res = await repo.createRepo({
|
||||||
// name: "test-cnb-2",
|
// name: "test-cnb-2",
|
||||||
// description: "This is my new repository",
|
// description: "This is my new repository",
|
||||||
|
|||||||
Reference in New Issue
Block a user