feat: 重构CNB管理模块,添加清理记录功能,更新中间件为统一认证方式,优化工作空间相关路由

This commit is contained in:
xiongxiao
2026-03-09 18:54:33 +08:00
committed by cnb
parent 21ba07e55b
commit 382c4809ea
25 changed files with 296 additions and 73 deletions

View File

@@ -1,23 +1,13 @@
import { app } from '../../app.ts';
import { app, notCNBCheck } from '../../app.ts';
import { useKey } from '@kevisual/context'
import { getLiveMdContent } from './live/live-content.ts';
import z from 'zod';
const notCNBCheck = (ctx: any) => {
const isCNB = useKey('CNB');
if (!isCNB) {
ctx.body = {
title: '非 cnb-board 环境',
list: []
}
return true;
}
return false;
}
app.route({
path: 'cnb_board',
key: 'live',
description: '获取cnb-board live的mdContent内容',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
args: {
more: z.boolean().optional().describe('是否获取更多系统信息默认false'),
@@ -37,7 +27,7 @@ app.route({
path: 'cnb_board',
key: 'live_repo_info',
description: '获取cnb-board live的repo信息',
middleware: ['auth-admin']
middleware: ['auth']
}).define(async (ctx) => {
const repoSlug = useKey('CNB_REPO_SLUG') || '';
const repoName = useKey('CNB_REPO_NAME') || '';
@@ -90,7 +80,7 @@ app.route({
path: 'cnb_board',
key: 'live_build_info',
description: '获取cnb-board live的构建信息',
middleware: ['auth-admin']
middleware: ['auth']
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const labels = [

View File

@@ -1,4 +1,4 @@
import { app } from '../../app.ts';
import { app, notCNBCheck } from '../../app.ts';
import './cnb-dev-env.ts';
import { useKey } from '@kevisual/context';
import { spawnSync } from 'node:child_process';
@@ -31,8 +31,9 @@ app.route({
path: 'cnb_board',
key: 'exit',
description: 'cnb的工作环境退出程序',
middleware: ['auth-admin'],
middleware: ['auth'],
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const cmd = 'kill 1';
execCommand(cmd);
}).addTo(app);

View File

@@ -1,5 +1,5 @@
import { createSkill } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
import { tool } from '@opencode-ai/plugin/tool';
@@ -7,7 +7,7 @@ app.route({
path: 'cnb',
key: 'user-check',
description: '检查用户登录状态参数checkToken,default true; checkCookie, default false',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -24,6 +24,7 @@ app.route({
const checkToken = ctx.query?.checkToken ?? true;
const checkCookie = ctx.query?.checkCookie ?? false;
let content = '';
const cnb = await cnbManager.getContext(ctx);
if (checkToken) {
const res = await cnb.user.getUser();
if (res?.code !== 200) {

View File

@@ -1,12 +1,12 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
// 设置 CNB_COOKIE环境变量和获取环境变量,用于界面操作定制模块功能
app.route({
path: 'cnb',
key: 'set-cnb-cookie',
description: '设置当前cnb工作空间的cookie环境变量',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -19,6 +19,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const cookie = ctx.query?.cookie;
if (!cookie) {
ctx.body = { content: '请提供有效的cookie值' };
@@ -33,7 +34,7 @@ app.route({
path: 'cnb',
key: 'get-cnb-cookie',
description: '获取当前cnb工作空间的cookie环境变量',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -43,6 +44,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const cookie = cnb.cookie || '未设置cookie环境变量';
ctx.body = { content: `当前cnb工作空间的cookie环境变量为${cookie}` };
}).addTo(app);

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, notCNBCheck } from '../../app.ts';
import { CNB_ENV } from "@/common/cnb-env.ts";
@@ -11,7 +11,7 @@ app.route({
path: 'cnb',
key: 'get-cnb-port-uri',
description: '获取当前cnb工作空间的port代理uri',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -24,6 +24,7 @@ app.route({
})
}
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const port = ctx.query?.port || 51515;
const uri = CNB_ENV?.CNB_VSCODE_PROXY_URI as string || '';
const finalUri = uri.replace('{{port}}', port.toString());
@@ -40,7 +41,7 @@ app.route({
path: 'cnb',
key: 'get-cnb-vscode-uri',
description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -58,6 +59,7 @@ app.route({
})
}
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const web = ctx.query?.web ?? false;
const vscode = ctx.query?.vscode ?? true; // 默认true
const codebuddy = ctx.query?.codebuddy ?? false;

View File

@@ -0,0 +1,24 @@
import { app, cnbManager } from '../../app.ts';
// "列出我的代码仓库search blog"
// 列出我的知识库的代码仓库
app.route({
path: 'cnb',
key: 'clear-me-manager',
description: '清理我的cnb-manager记录',
middleware: ['auth'],
}).define(async (ctx) => {
const tokenUser = ctx.tokenUser;
if (!tokenUser) {
ctx.throw(401, '未授权');
}
const username = tokenUser.username;
if (!username) {
ctx.throw(400, '无效的用户信息');
}
if (username !== 'default') {
cnbManager.clearUsername(username);
}
ctx.body = { content: '已清理cnb-manager记录' };
}).addTo(app);

View File

@@ -8,6 +8,8 @@ import './knowledge/index.ts'
import './issues/index.ts'
import './cnb-board/index.ts';
import './share/index.ts';
import './cnb-manager/index.ts';
/**
* 验证上下文中的 App ID 是否与指定的 App ID 匹配
* @param {any} ctx - 上下文对象,可能包含 appId 属性
@@ -32,6 +34,9 @@ app.route({
}).define(async (ctx) => {
// ctx.body = 'Auth Route';
if (checkAppId(ctx, app.appId)) {
ctx.state.tokenUser = {
username: 'default',
}
return;
}
}).addTo(app, { overwrite: false });
@@ -43,6 +48,9 @@ app.route({
}).define(async (ctx) => {
// ctx.body = 'Admin Auth Route';
if (checkAppId(ctx, app.appId)) {
ctx.state.tokenUser = {
username: 'default',
}
return;
}
}).addTo(app, { overwrite: false });

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
import { IssueItem } from '@/index.ts';
// 创建cnb issue, 仓库为 kevisual/kevisual 标题为 "自动化测试创建issue", 内容为 "这是通过API创建的issue用于测试目的", body: "这是通过API创建的issue用于测试目的"
@@ -7,7 +7,7 @@ app.route({
path: 'cnb',
key: 'create-issue',
description: '创建 Issue, 参数 repo, title, body, assignees, labels, priority',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -25,6 +25,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo;
const title = ctx.query?.title;
const body = ctx.query?.body;
@@ -51,7 +52,7 @@ app.route({
path: 'cnb',
key: 'complete-issue',
description: '完成 Issue, 参数 repo, issueNumber',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -66,6 +67,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo;
const issueNumber = ctx.query?.issueNumber;
const state = ctx.query?.state ?? 'closed';

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
import { useKey } from '@kevisual/context';
// 查询 Issue 列表 repo是 kevisual/kevisual
@@ -7,7 +7,7 @@ app.route({
path: 'cnb',
key: 'list-issues',
description: '查询 Issue 列表, 参数 repo, state, keyword, labels, page, page_size 等',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -26,6 +26,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
const state = ctx.query?.state;
const keyword = ctx.query?.keyword;

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
import { CNBChat } from '@kevisual/ai/browser'
import { useKey } from '@kevisual/context';
@@ -13,7 +13,7 @@ app.route({
path: 'cnb',
key: 'cnb-ai-chat',
description: '调用cnb的知识库ai对话功能进行聊天',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -27,6 +27,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const question = ctx.query?.question;
if (!question) {
ctx.body = { content: '请提供有效的消息内容' };
@@ -89,7 +90,7 @@ app.route({
path: 'cnb',
key: 'cnb-rag-query',
description: '调用cnb的知识库RAG查询功能进行问答',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -103,6 +104,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const question = ctx.query?.question;
if (!question) {
ctx.body = { content: '请提供有效的消息内容' };

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
// "列出我的代码仓库search blog"
// 列出我的知识库的代码仓库
@@ -7,7 +7,7 @@ app.route({
path: 'cnb',
key: 'list-repos',
description: '列出我的代码仓库',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -22,6 +22,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const search = ctx.query?.search;
const pageSize = ctx.query?.pageSize || 9999;
const flags = ctx.query?.flags;

View File

@@ -1,4 +1,4 @@
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
import { createSkill, Skill, tool } from '@kevisual/router'
// 创建一个仓库 kevisual/test-repo
@@ -6,7 +6,7 @@ app.route({
path: 'cnb',
key: 'create-repo',
description: '创建代码仓库, 参数name, visibility, description',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -21,6 +21,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const name = ctx.query?.name;
const visibility = ctx.query?.visibility ?? 'public';
const description = ctx.query?.description ?? '';
@@ -46,7 +47,7 @@ app.route({
path: 'cnb',
key: 'create-repo-file',
description: '在代码仓库中创建文件, repoName, filePath, content, encoding。使用CNB_COOKIE进行鉴权',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -62,6 +63,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repoName = ctx.query?.repoName;
const filePath = ctx.query?.filePath;
const content = ctx.query?.content;
@@ -85,7 +87,7 @@ app.route({
path: 'cnb',
key: 'delete-repo',
description: '删除代码仓库, 参数name',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -98,12 +100,13 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const name = ctx.query?.name;
if (!name) {
ctx.throw(400, '缺少参数 name');
}
const res = await cnb.repo.deleteRepo(name);
const res = await cnb.repo.deleteRepoCookie(name);
ctx.forward(res);
}).addTo(app);

View File

@@ -1,5 +1,5 @@
import { useKey } from '@kevisual/context';
import { app, cnb } from '../../app.ts';
import { app } from '../../app.ts';
import z from 'zod';
app.route({

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager, notCNBCheck } from '../../app.ts';
import z from 'zod';
import './skills.ts';
import './keep.ts';
@@ -9,7 +9,7 @@ app.route({
path: 'cnb',
key: 'start-workspace',
description: '启动开发工作空间, 参数 repo',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -24,6 +24,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo;
const branch = ctx.query?.branch;
const ref = ctx.query?.ref;
@@ -42,7 +43,7 @@ app.route({
path: 'cnb',
key: 'list-workspace',
description: '获取cnb开发工作空间列表可选参数 status=running 获取运行中的环境',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -59,6 +60,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const { status = 'running', page, pageSize, slug, branch } = ctx.query || {};
const res = await cnb.workspace.list({
status: status as 'running' | 'closed' | undefined,
@@ -73,7 +75,7 @@ app.route({
path: 'cnb',
key: 'get-workspace',
description: '获取工作空间详情,通过 repo 和 sn 获取',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -87,6 +89,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo;
const sn = ctx.query?.sn;
if (!repo) {
@@ -104,7 +107,7 @@ app.route({
path: 'cnb',
key: 'delete-workspace',
description: '删除工作空间,通过 pipelineId 或 sn',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -119,6 +122,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const pipelineId = ctx.query?.pipelineId;
const sn = ctx.query?.sn;
const sns = ctx.query?.sns;
@@ -143,7 +147,7 @@ app.route({
path: 'cnb',
key: 'stop-workspace',
description: '停止工作空间,通过 pipelineId 或 sn',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -157,6 +161,8 @@ app.route({
})
}
}).define(async (ctx) => {
if (notCNBCheck(ctx)) { return; }
const cnb = await cnbManager.getContext(ctx);
const pipelineId = ctx.query?.pipelineId;
const sn = ctx.query?.sn;
if (!pipelineId && !sn) {

View File

@@ -1,5 +1,5 @@
import { tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager, notCNBCheck } from '../../app.ts';
import { addKeepAliveData, KeepAliveData, removeKeepAliveData, createLiveData } from '../../../src/workspace/keep-file-live.ts';
import { useKey } from '@kevisual/context';
@@ -8,7 +8,7 @@ app.route({
path: 'cnb',
key: 'keep-workspace-alive',
description: '保持工作空间存活技能参数repo:代码仓库路径,例如 user/repopipelineId:流水线ID例如 cnb-708-1ji9sog7o-001',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: [],
...({
@@ -19,9 +19,11 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo as string;
const pipelineId = ctx.query?.pipelineId as string;
if (notCNBCheck(ctx)) return;
if (!repo || !pipelineId) {
ctx.throw(400, '缺少参数 repo 或 pipelineId');
}
@@ -51,7 +53,7 @@ app.route({
path: 'cnb',
key: 'stop-keep-workspace-alive',
description: '停止保持工作空间存活技能, 参数repo:代码仓库路径,例如 user/repopipelineId:流水线ID例如 cnb-708-1ji9sog7o-001',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: [],
...({
@@ -62,6 +64,7 @@ app.route({
})
}
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const repo = ctx.query?.repo as string;
const pipelineId = ctx.query?.pipelineId as string;
@@ -79,7 +82,7 @@ app.route({
path: 'cnb',
key: 'keep-alive-current-workspace',
description: '保持当前工作空间存活技能',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
skill: 'keep-alive-current-workspace',
@@ -87,6 +90,7 @@ app.route({
summary: '保持当前工作空间存活,防止被关闭或释放资源',
}
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const pipelineId = useKey('CNB_PIPELINE_ID');
const repo = useKey('CNB_REPO_SLUG_LOWERCASE');
if (!pipelineId || !repo) {

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts';
import { app, cnbManager } from '../../app.ts';
// 批量删除已停止的cnb工作空间
// app.route({
@@ -35,7 +35,7 @@ app.route({
path: 'cnb',
key: 'clean-closed-workspace',
description: '批量删除已停止的cnb工作空间',
middleware: ['auth-admin'],
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
@@ -45,6 +45,7 @@ app.route({
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const closedWorkspaces = await cnb.workspace.list({ status: 'closed', pageSize: 100 });
if (closedWorkspaces.code !== 200) {
ctx.throw(500, '获取已关闭工作空间列表失败');