From 382c4809ea6270522514d3f9c29ce22d40633b04 Mon Sep 17 00:00:00 2001 From: xiongxiao Date: Mon, 9 Mar 2026 18:54:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84CNB=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=B8=85=E7=90=86?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E4=B8=BA=E7=BB=9F=E4=B8=80=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E6=96=B9=E5=BC=8F=EF=BC=8C=E4=BC=98=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E7=A9=BA=E9=97=B4=E7=9B=B8=E5=85=B3=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + agent/app.ts | 42 ++++++--- agent/{command.ts => commander.ts} | 0 agent/main.ts | 18 ++++ agent/modules/cnb-manager.ts | 118 ++++++++++++++++++++++++++ agent/opencode.ts | 2 +- agent/routes/cnb-board/cnb-dev-env.ts | 20 ++--- agent/routes/cnb-board/index.ts | 5 +- agent/routes/cnb-env/check.ts | 5 +- agent/routes/cnb-env/env.ts | 8 +- agent/routes/cnb-env/vscode.ts | 8 +- agent/routes/cnb-manager/index.ts | 24 ++++++ agent/routes/index.ts | 8 ++ agent/routes/issues/issue.ts | 8 +- agent/routes/issues/list.ts | 5 +- agent/routes/knowledge/ai.ts | 8 +- agent/routes/repo/list.ts | 5 +- agent/routes/repo/repo.ts | 13 +-- agent/routes/share/index.ts | 2 +- agent/routes/workspace/index.ts | 18 ++-- agent/routes/workspace/keep.ts | 14 +-- agent/routes/workspace/skills.ts | 5 +- bun.config.ts | 2 +- package.json | 18 ++-- test/a-config.ts | 12 +++ 25 files changed, 296 insertions(+), 73 deletions(-) rename agent/{command.ts => commander.ts} (100%) create mode 100644 agent/main.ts create mode 100644 agent/modules/cnb-manager.ts create mode 100644 agent/routes/cnb-manager/index.ts create mode 100644 test/a-config.ts diff --git a/.gitignore b/.gitignore index 30969be..ba54d10 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ node_modules .pnpm-store dist +pack-dist storage \ No newline at end of file diff --git a/agent/app.ts b/agent/app.ts index a73519e..5aead23 100644 --- a/agent/app.ts +++ b/agent/app.ts @@ -1,17 +1,35 @@ import { QueryRouterServer as App } from '@kevisual/router' import { useContextKey } from '@kevisual/context' -import { useConfig, useKey } from '@kevisual/use-config' +import { useKey } from '@kevisual/use-config' import { CNB } from '../src/index.ts'; +import { CNBManager } from './modules/cnb-manager.ts' +export const cnbManager = new CNBManager() -export const config = useConfig() -export const cnb = useContextKey('cnb', () => { - // CNB_TOKEN是降级兼容变量,推荐使用CNB_API_KEY - // CNB_TOKEN 是流水线自己就有的变量,但是权限比较小 - const token = useKey('CNB_API_KEY') as string || useKey('CNB_TOKEN') as string - // cookie 变量是可选的 - const cookie = useKey('CNB_COOKIE') as string - return new CNB({ token: token, cookie: cookie }); -}) -export const app = useContextKey('app', () => { +// CNB_TOKEN是降级兼容变量,推荐使用CNB_API_KEY +// CNB_TOKEN 是流水线自己就有的变量,但是权限比较小 +const token = useKey('CNB_API_KEY') as string || useKey('CNB_TOKEN') as string +// cookie 变量是可选的 +const cookie = useKey('CNB_COOKIE') as string +try { + cnbManager.addCNB({ + username: 'default', + token: token, + cookie: cookie, + cnb: new CNB({ token: token, cookie: cookie }) + }) +} catch (error) { + +} +export const cnb = (await cnbManager.getCNB({ username: 'default' })).cnb +export const app = await useContextKey('app', () => { return new App({}) -}) \ No newline at end of file +}) + +export const notCNBCheck = (ctx: any) => { + const isCNB = useKey('CNB'); + if (!isCNB) { + ctx.throw(400, '当前环境非 cnb-board 环境,无法获取 live 内容'); + return true; + } + return false; +} \ No newline at end of file diff --git a/agent/command.ts b/agent/commander.ts similarity index 100% rename from agent/command.ts rename to agent/commander.ts diff --git a/agent/main.ts b/agent/main.ts new file mode 100644 index 0000000..ba7bb22 --- /dev/null +++ b/agent/main.ts @@ -0,0 +1,18 @@ +// import { RemoteApp } from '@kevisual/remote-app'; +import { app } from './index.ts' +// import { QueryLoginNode } from '@kevisual/api/login-node'; +// const queryLoginNode = new QueryLoginNode({}); +// await queryLoginNode.init() +// const token = await queryLoginNode.getToken(); +// app.createRouteList() +// const remoteApp = new RemoteApp({ +// id: 'cnb-agent', +// token: token, +// url: 'https://kevisual.cn/ws/proxy', +// app: app as any, +// }) +// const isConnected = await remoteApp.isConnect(); +// if (isConnected) { +// console.log('Remote app connected successfully'); +// remoteApp.listenProxy(); +// } \ No newline at end of file diff --git a/agent/modules/cnb-manager.ts b/agent/modules/cnb-manager.ts new file mode 100644 index 0000000..d00d68f --- /dev/null +++ b/agent/modules/cnb-manager.ts @@ -0,0 +1,118 @@ +import { Result } from '@kevisual/query'; +import { CNB } from '../../src/index.ts'; +export const getConfig = async (opts: { token?: string }) => { + const res = await fetch('https://kevisual.cn/api/router', { + method: 'POST', + body: JSON.stringify({ + path: 'config', + key: 'get', + data: { + key: "cnb_center_config.json" + } + }), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${opts.token!}` + }, + }).then(res => res.json()); + return res as Result<{ + id: string, key: 'cnb_center_config.json', data: { + CNB_API_KEY: string, + CNB_COOKIE: string + } + }>; +} +type CNBItem = { + username: string, + token: string, + cookie?: string + runAt?: number + owner?: boolean + cnb: CNB +} +export class CNBManager { + cnbMap: Map = new Map() + constructor() { + setInterval(() => { + this.clearExpiredCNB() + }, 1000 * 60 * 30) // 每30分钟清理一次过期的 CNB 实例 + } + getDefaultCNB() { + const cnbItem = this.cnbMap.get('default') + if (!cnbItem) { + throw new Error('Default CNB not found') + } + return cnbItem + } + async getCNB(opts?: { username?: string, kevisualToken?: string }): Promise { + if (opts?.username) { + return this.getDefaultCNB() + } + const username = opts?.kevisualToken + const cnbItem = this.cnbMap.get(username) + if (cnbItem) { + cnbItem.runAt = Date.now() + return cnbItem + } + const res = await getConfig({ token: opts?.kevisualToken }) + if (res.code === 200) { + const cookie = res.data?.data?.CNB_COOKIE + const token = res.data?.data?.CNB_API_KEY + if (token) { + return this.addCNB({ username, token, cookie }) + } + } + return null + } + /** + * 通过上下文获取 CNB 实例(直接返回 cnb 对象) + * @param ctx + * @returns CNB 实例 + */ + async getContext(ctx: any) { + const tokenUser = ctx?.state?.tokenUser + const username = tokenUser?.username + if (!username) { + ctx.throw(403, 'Unauthorized') + } + if (username === 'default') { + return this.getDefaultCNB().cnb + } + const kevisualToken = ctx.query?.token; + const item = await this.getCNB({ username, kevisualToken }); + if (!item) { + ctx.throw(400, '不存在的 CNB 配置项,请检查 登录 Token 是否正确,或添加 CNB 配置') + } + return item.cnb + } + addCNB(opts: Partial) { + if (!opts.username || !opts.token) { + throw new Error('username and token are required') + } + const exist = this.cnbMap.get(opts.username) + if (exist) { + exist.runAt = Date.now() + return exist + } + const cnb = opts?.cnb || new CNB({ token: opts.token, cookie: opts.cookie }); + opts.cnb = cnb; + opts.runAt = Date.now() + this.cnbMap.set(opts.username, opts as CNBItem) + return opts as CNBItem + } + // 定期清理过期的 CNB 实例,默认过期时间为 1 小时 + clearExpiredCNB(expireTime = 1000 * 60 * 60) { + const now = Date.now() + for (const [username, item] of this.cnbMap.entries()) { + if (username === 'default') { + continue + } + if (item.runAt && now - item.runAt > expireTime) { + this.cnbMap.delete(username) + } + } + } + clearUsername(username: string) { + this.cnbMap.delete(username) + } +} \ No newline at end of file diff --git a/agent/opencode.ts b/agent/opencode.ts index d90526f..56dd6ff 100644 --- a/agent/opencode.ts +++ b/agent/opencode.ts @@ -2,5 +2,5 @@ import { app } from './index.ts'; import { createRouterAgentPluginFn } from '@kevisual/router/opencode' export const CnbPlugin = createRouterAgentPluginFn({ - router: app, + router: app as any, }) diff --git a/agent/routes/cnb-board/cnb-dev-env.ts b/agent/routes/cnb-board/cnb-dev-env.ts index 1617706..e6aa34d 100644 --- a/agent/routes/cnb-board/cnb-dev-env.ts +++ b/agent/routes/cnb-board/cnb-dev-env.ts @@ -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 = [ diff --git a/agent/routes/cnb-board/index.ts b/agent/routes/cnb-board/index.ts index 1cb10e7..307ec6a 100644 --- a/agent/routes/cnb-board/index.ts +++ b/agent/routes/cnb-board/index.ts @@ -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); \ No newline at end of file diff --git a/agent/routes/cnb-env/check.ts b/agent/routes/cnb-env/check.ts index 8657b13..faa2f0f 100644 --- a/agent/routes/cnb-env/check.ts +++ b/agent/routes/cnb-env/check.ts @@ -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) { diff --git a/agent/routes/cnb-env/env.ts b/agent/routes/cnb-env/env.ts index 5838064..9c59b76 100644 --- a/agent/routes/cnb-env/env.ts +++ b/agent/routes/cnb-env/env.ts @@ -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); \ No newline at end of file diff --git a/agent/routes/cnb-env/vscode.ts b/agent/routes/cnb-env/vscode.ts index 7de3f11..aa1934e 100644 --- a/agent/routes/cnb-env/vscode.ts +++ b/agent/routes/cnb-env/vscode.ts @@ -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; diff --git a/agent/routes/cnb-manager/index.ts b/agent/routes/cnb-manager/index.ts new file mode 100644 index 0000000..2e0bc4c --- /dev/null +++ b/agent/routes/cnb-manager/index.ts @@ -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); \ No newline at end of file diff --git a/agent/routes/index.ts b/agent/routes/index.ts index 3001ef6..0f383ca 100644 --- a/agent/routes/index.ts +++ b/agent/routes/index.ts @@ -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 }); \ No newline at end of file diff --git a/agent/routes/issues/issue.ts b/agent/routes/issues/issue.ts index 4229bc3..713cb4d 100644 --- a/agent/routes/issues/issue.ts +++ b/agent/routes/issues/issue.ts @@ -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'; diff --git a/agent/routes/issues/list.ts b/agent/routes/issues/list.ts index 415e46a..163f429 100644 --- a/agent/routes/issues/list.ts +++ b/agent/routes/issues/list.ts @@ -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; diff --git a/agent/routes/knowledge/ai.ts b/agent/routes/knowledge/ai.ts index dadf3ce..05d25da 100644 --- a/agent/routes/knowledge/ai.ts +++ b/agent/routes/knowledge/ai.ts @@ -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: '请提供有效的消息内容' }; diff --git a/agent/routes/repo/list.ts b/agent/routes/repo/list.ts index 1528d9a..b2dbae1 100644 --- a/agent/routes/repo/list.ts +++ b/agent/routes/repo/list.ts @@ -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; diff --git a/agent/routes/repo/repo.ts b/agent/routes/repo/repo.ts index 6e2d5d4..3ff132b 100644 --- a/agent/routes/repo/repo.ts +++ b/agent/routes/repo/repo.ts @@ -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); \ No newline at end of file diff --git a/agent/routes/share/index.ts b/agent/routes/share/index.ts index 54e5646..f4dc580 100644 --- a/agent/routes/share/index.ts +++ b/agent/routes/share/index.ts @@ -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({ diff --git a/agent/routes/workspace/index.ts b/agent/routes/workspace/index.ts index e44808a..e3c1c93 100644 --- a/agent/routes/workspace/index.ts +++ b/agent/routes/workspace/index.ts @@ -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) { diff --git a/agent/routes/workspace/keep.ts b/agent/routes/workspace/keep.ts index 4f8cf4f..36e0fa2 100644 --- a/agent/routes/workspace/keep.ts +++ b/agent/routes/workspace/keep.ts @@ -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/repo,pipelineId:流水线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/repo,pipelineId:流水线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) { diff --git a/agent/routes/workspace/skills.ts b/agent/routes/workspace/skills.ts index b08cdfd..6d37397 100644 --- a/agent/routes/workspace/skills.ts +++ b/agent/routes/workspace/skills.ts @@ -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, '获取已关闭工作空间列表失败'); diff --git a/bun.config.ts b/bun.config.ts index aeed142..1a60b60 100644 --- a/bun.config.ts +++ b/bun.config.ts @@ -3,4 +3,4 @@ await buildWithBun({ naming: 'opencode', entry: 'agent/opencode.ts', dts: true } await buildWithBun({ naming: 'keep', entry: 'src/keep.ts', dts: true, target: 'node' }); await buildWithBun({ naming: 'routes', entry: 'agent/index.ts', dts: true }); -await buildWithBun({ naming: 'cli', entry: 'agent/command.ts', dts: true, target: 'node' }); \ No newline at end of file +await buildWithBun({ naming: 'cli', entry: 'agent/commander.ts', dts: true, target: 'node' }); \ No newline at end of file diff --git a/package.json b/package.json index 53b3bda..e3f3e9d 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,16 @@ "version": "0.0.37", "description": "", "main": "index.js", + "basename": "/root/cnb", + "app": { + "type": "system-app", + "entry": "./dist/routes.js", + "engine": "bun" + }, "scripts": { "build": "bun bun.config.ts", - "flow": "ev npm patch && pnpm build && ev npm publish npm -p" + "flow": "ev npm patch && pnpm build && ev npm publish npm -p", + "pub": "ev pack -u -m false -c -p" }, "keywords": [], "bin": { @@ -14,20 +21,21 @@ "files": [ "dist", "src", - "mod.ts", "agent" ], "author": "abearxiong (https://www.xiongxiao.me)", "license": "MIT", - "packageManager": "pnpm@10.30.3", + "packageManager": "pnpm@10.31.0", "type": "module", "devDependencies": { "@kevisual/ai": "^0.0.26", + "@kevisual/api": "^0.0.62", "@kevisual/code-builder": "^0.0.6", "@kevisual/context": "^0.0.8", "@kevisual/dts": "^0.0.4", + "@kevisual/remote-app": "^0.0.6", "@kevisual/types": "^0.0.12", - "@opencode-ai/plugin": "^1.2.20", + "@opencode-ai/plugin": "^1.2.22", "@types/bun": "^1.3.10", "@types/node": "^25.3.5", "@types/ws": "^8.18.1", @@ -43,7 +51,7 @@ }, "dependencies": { "@kevisual/query": "^0.0.53", - "@kevisual/router": "^0.0.88", + "@kevisual/router": "^0.0.90", "@kevisual/use-config": "^1.0.30", "es-toolkit": "^1.45.1", "nanoid": "^5.1.6", diff --git a/test/a-config.ts b/test/a-config.ts new file mode 100644 index 0000000..fb53096 --- /dev/null +++ b/test/a-config.ts @@ -0,0 +1,12 @@ +import { getConfig } from '../agent/modules/cnb-manager'; +import { QueryLoginNode } from '@kevisual/api/login-node'; +const queryLoginNode = new QueryLoginNode({}); +await queryLoginNode.init() +const testConfig = async () => { + const token = await queryLoginNode.getToken(); + console.log('Token:', token); + const res = await getConfig({ token }); + console.log('Config:', res); +} + +testConfig(); \ No newline at end of file