@kevisual/cnb 0.0.37 → 0.0.40

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 CHANGED
@@ -1,17 +1,35 @@
1
1
  import { QueryRouterServer as App } from '@kevisual/router'
2
2
  import { useContextKey } from '@kevisual/context'
3
- import { useConfig, useKey } from '@kevisual/use-config'
3
+ import { useKey } from '@kevisual/use-config'
4
4
  import { CNB } from '../src/index.ts';
5
+ import { CNBManager } from './modules/cnb-manager.ts'
6
+ export const cnbManager = new CNBManager()
5
7
 
6
- export const config = useConfig()
7
- export const cnb = useContextKey<CNB>('cnb', () => {
8
- // CNB_TOKEN是降级兼容变量,推荐使用CNB_API_KEY
9
- // CNB_TOKEN 是流水线自己就有的变量,但是权限比较小
10
- const token = useKey('CNB_API_KEY') as string || useKey('CNB_TOKEN') as string
11
- // cookie 变量是可选的
12
- const cookie = useKey('CNB_COOKIE') as string
13
- return new CNB({ token: token, cookie: cookie });
14
- })
15
- export const app = useContextKey<App>('app', () => {
8
+ // CNB_TOKEN是降级兼容变量,推荐使用CNB_API_KEY
9
+ // CNB_TOKEN 是流水线自己就有的变量,但是权限比较小
10
+ const token = useKey('CNB_API_KEY') as string || useKey('CNB_TOKEN') as string
11
+ // cookie 变量是可选的
12
+ const cookie = useKey('CNB_COOKIE') as string
13
+ try {
14
+ cnbManager.addCNB({
15
+ username: 'default',
16
+ token: token,
17
+ cookie: cookie,
18
+ cnb: new CNB({ token: token, cookie: cookie })
19
+ })
20
+ } catch (error) {
21
+
22
+ }
23
+ export const cnb = (await cnbManager.getCNB({ username: 'default' })).cnb
24
+ export const app = await useContextKey<App>('app', () => {
16
25
  return new App({})
17
- })
26
+ })
27
+
28
+ export const notCNBCheck = (ctx: any) => {
29
+ const isCNB = useKey('CNB');
30
+ if (!isCNB) {
31
+ ctx.throw(400, '当前环境非 cnb-board 环境,无法获取 live 内容');
32
+ return true;
33
+ }
34
+ return false;
35
+ }
package/agent/main.ts ADDED
@@ -0,0 +1,18 @@
1
+ // import { RemoteApp } from '@kevisual/remote-app';
2
+ import { app } from './index.ts'
3
+ // import { QueryLoginNode } from '@kevisual/api/login-node';
4
+ // const queryLoginNode = new QueryLoginNode({});
5
+ // await queryLoginNode.init()
6
+ // const token = await queryLoginNode.getToken();
7
+ // app.createRouteList()
8
+ // const remoteApp = new RemoteApp({
9
+ // id: 'cnb-agent',
10
+ // token: token,
11
+ // url: 'https://kevisual.cn/ws/proxy',
12
+ // app: app as any,
13
+ // })
14
+ // const isConnected = await remoteApp.isConnect();
15
+ // if (isConnected) {
16
+ // console.log('Remote app connected successfully');
17
+ // remoteApp.listenProxy();
18
+ // }
@@ -0,0 +1,119 @@
1
+ import { Result } from '@kevisual/query';
2
+ import { CNB } from '../../src/index.ts';
3
+ import { useKey } from '@kevisual/context';
4
+ export const getConfig = async (opts: { token?: string }) => {
5
+ const kevisualEnv = useKey('KEVISUAL_ENV')
6
+ const baseUrl = kevisualEnv === 'production' ? 'https://kevisual.cn/api/router' : 'https://kevisual.xiongxiao.me/api/router';
7
+ const res = await fetch(baseUrl, {
8
+ method: 'POST',
9
+ body: JSON.stringify({
10
+ path: 'config',
11
+ key: 'get',
12
+ data: {
13
+ key: "cnb_center_config.json"
14
+ }
15
+ }),
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ 'Authorization': `Bearer ${opts.token!}`
19
+ },
20
+ }).then(res => res.json());
21
+ return res as Result<{
22
+ id: string, key: 'cnb_center_config.json', data: {
23
+ CNB_API_KEY: string,
24
+ CNB_COOKIE: string
25
+ }
26
+ }>;
27
+ }
28
+ type CNBItem = {
29
+ username: string,
30
+ token: string,
31
+ cookie?: string
32
+ runAt?: number
33
+ owner?: boolean
34
+ cnb: CNB
35
+ }
36
+ export class CNBManager {
37
+ cnbMap: Map<string, CNBItem> = new Map()
38
+ constructor() {
39
+ setInterval(() => {
40
+ this.clearExpiredCNB()
41
+ }, 1000 * 60 * 30) // 每30分钟清理一次过期的 CNB 实例
42
+ }
43
+ getDefaultCNB() {
44
+ const cnbItem = this.cnbMap.get('default')
45
+ if (!cnbItem) {
46
+ throw new Error('Default CNB not found')
47
+ }
48
+ return cnbItem
49
+ }
50
+ async getCNB(opts?: { username?: string, kevisualToken?: string }): Promise<CNBItem | null> {
51
+ const username = opts?.username
52
+ const cnbItem = this.cnbMap.get(username)
53
+ if (cnbItem) {
54
+ cnbItem.runAt = Date.now()
55
+ return cnbItem
56
+ }
57
+
58
+ const res = await getConfig({ token: opts?.kevisualToken })
59
+ if (res.code === 200) {
60
+ const cookie = res.data?.data?.CNB_COOKIE
61
+ const token = res.data?.data?.CNB_API_KEY
62
+ if (token) {
63
+ return this.addCNB({ username, token, cookie })
64
+ }
65
+ }
66
+ return null
67
+ }
68
+ /**
69
+ * 通过上下文获取 CNB 实例(直接返回 cnb 对象)
70
+ * @param ctx
71
+ * @returns CNB 实例
72
+ */
73
+ async getContext(ctx: any) {
74
+ const tokenUser = ctx?.state?.tokenUser
75
+ const username = tokenUser?.username
76
+ if (!username) {
77
+ ctx.throw(403, 'Unauthorized')
78
+ }
79
+ if (username === 'default') {
80
+ return this.getDefaultCNB().cnb
81
+ }
82
+ const kevisualToken = ctx.query?.token;
83
+ const item = await this.getCNB({ username, kevisualToken });
84
+ if (!item) {
85
+ ctx.throw(400, '不存在的 CNB 配置项,请检查 登录 Token 是否正确,或添加 CNB 配置')
86
+ }
87
+ return item.cnb
88
+ }
89
+ addCNB(opts: Partial<CNBItem>) {
90
+ if (!opts.username || !opts.token) {
91
+ throw new Error('username and token are required')
92
+ }
93
+ const exist = this.cnbMap.get(opts.username)
94
+ if (exist) {
95
+ exist.runAt = Date.now()
96
+ return exist
97
+ }
98
+ const cnb = opts?.cnb || new CNB({ token: opts.token, cookie: opts.cookie });
99
+ opts.cnb = cnb;
100
+ opts.runAt = Date.now()
101
+ this.cnbMap.set(opts.username, opts as CNBItem)
102
+ return opts as CNBItem
103
+ }
104
+ // 定期清理过期的 CNB 实例,默认过期时间为 1 小时
105
+ clearExpiredCNB(expireTime = 1000 * 60 * 60) {
106
+ const now = Date.now()
107
+ for (const [username, item] of this.cnbMap.entries()) {
108
+ if (username === 'default') {
109
+ continue
110
+ }
111
+ if (item.runAt && now - item.runAt > expireTime) {
112
+ this.cnbMap.delete(username)
113
+ }
114
+ }
115
+ }
116
+ clearUsername(username: string) {
117
+ this.cnbMap.delete(username)
118
+ }
119
+ }
package/agent/opencode.ts CHANGED
@@ -2,5 +2,5 @@ import { app } from './index.ts';
2
2
  import { createRouterAgentPluginFn } from '@kevisual/router/opencode'
3
3
 
4
4
  export const CnbPlugin = createRouterAgentPluginFn({
5
- router: app,
5
+ router: app as any,
6
6
  })
@@ -1,23 +1,13 @@
1
- import { app } from '../../app.ts';
1
+ import { app, notCNBCheck } from '../../app.ts';
2
2
  import { useKey } from '@kevisual/context'
3
3
  import { getLiveMdContent } from './live/live-content.ts';
4
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
- }
5
+
16
6
  app.route({
17
7
  path: 'cnb_board',
18
8
  key: 'live',
19
9
  description: '获取cnb-board live的mdContent内容',
20
- middleware: ['auth-admin'],
10
+ middleware: ['auth'],
21
11
  metadata: {
22
12
  args: {
23
13
  more: z.boolean().optional().describe('是否获取更多系统信息,默认false'),
@@ -37,7 +27,7 @@ app.route({
37
27
  path: 'cnb_board',
38
28
  key: 'live_repo_info',
39
29
  description: '获取cnb-board live的repo信息',
40
- middleware: ['auth-admin']
30
+ middleware: ['auth']
41
31
  }).define(async (ctx) => {
42
32
  const repoSlug = useKey('CNB_REPO_SLUG') || '';
43
33
  const repoName = useKey('CNB_REPO_NAME') || '';
@@ -90,7 +80,7 @@ app.route({
90
80
  path: 'cnb_board',
91
81
  key: 'live_build_info',
92
82
  description: '获取cnb-board live的构建信息',
93
- middleware: ['auth-admin']
83
+ middleware: ['auth']
94
84
  }).define(async (ctx) => {
95
85
  if (notCNBCheck(ctx)) return;
96
86
  const labels = [
@@ -1,4 +1,4 @@
1
- import { app } from '../../app.ts';
1
+ import { app, notCNBCheck } from '../../app.ts';
2
2
  import './cnb-dev-env.ts';
3
3
  import { useKey } from '@kevisual/context';
4
4
  import { spawnSync } from 'node:child_process';
@@ -31,8 +31,9 @@ app.route({
31
31
  path: 'cnb_board',
32
32
  key: 'exit',
33
33
  description: 'cnb的工作环境退出程序',
34
- middleware: ['auth-admin'],
34
+ middleware: ['auth'],
35
35
  }).define(async (ctx) => {
36
+ if (notCNBCheck(ctx)) return;
36
37
  const cmd = 'kill 1';
37
38
  execCommand(cmd);
38
39
  }).addTo(app);
@@ -1,5 +1,5 @@
1
1
  import { createSkill } from '@kevisual/router';
2
- import { app, cnb } from '../../app.ts';
2
+ import { app, cnbManager } from '../../app.ts';
3
3
  import { tool } from '@opencode-ai/plugin/tool';
4
4
 
5
5
 
@@ -7,7 +7,7 @@ app.route({
7
7
  path: 'cnb',
8
8
  key: 'user-check',
9
9
  description: '检查用户登录状态,参数checkToken,default true; checkCookie, default false',
10
- middleware: ['auth-admin'],
10
+ middleware: ['auth'],
11
11
  metadata: {
12
12
  tags: ['opencode'],
13
13
  ...createSkill({
@@ -24,6 +24,7 @@ app.route({
24
24
  const checkToken = ctx.query?.checkToken ?? true;
25
25
  const checkCookie = ctx.query?.checkCookie ?? false;
26
26
  let content = '';
27
+ const cnb = await cnbManager.getContext(ctx);
27
28
  if (checkToken) {
28
29
  const res = await cnb.user.getUser();
29
30
  if (res?.code !== 200) {
@@ -1,12 +1,12 @@
1
1
  import { createSkill, tool } from '@kevisual/router';
2
- import { app, cnb } from '../../app.ts';
2
+ import { app, cnbManager } from '../../app.ts';
3
3
 
4
4
  // 设置 CNB_COOKIE环境变量和获取环境变量,用于界面操作定制模块功能
5
5
  app.route({
6
6
  path: 'cnb',
7
7
  key: 'set-cnb-cookie',
8
8
  description: '设置当前cnb工作空间的cookie环境变量',
9
- middleware: ['auth-admin'],
9
+ middleware: ['auth'],
10
10
  metadata: {
11
11
  tags: ['opencode'],
12
12
  ...createSkill({
@@ -19,6 +19,7 @@ app.route({
19
19
  })
20
20
  }
21
21
  }).define(async (ctx) => {
22
+ const cnb = await cnbManager.getContext(ctx);
22
23
  const cookie = ctx.query?.cookie;
23
24
  if (!cookie) {
24
25
  ctx.body = { content: '请提供有效的cookie值' };
@@ -33,7 +34,7 @@ app.route({
33
34
  path: 'cnb',
34
35
  key: 'get-cnb-cookie',
35
36
  description: '获取当前cnb工作空间的cookie环境变量',
36
- middleware: ['auth-admin'],
37
+ middleware: ['auth'],
37
38
  metadata: {
38
39
  tags: ['opencode'],
39
40
  ...createSkill({
@@ -43,6 +44,7 @@ app.route({
43
44
  })
44
45
  }
45
46
  }).define(async (ctx) => {
47
+ const cnb = await cnbManager.getContext(ctx);
46
48
  const cookie = cnb.cookie || '未设置cookie环境变量';
47
49
  ctx.body = { content: `当前cnb工作空间的cookie环境变量为:${cookie}` };
48
50
  }).addTo(app);
@@ -1,5 +1,5 @@
1
1
  import { createSkill, tool } from '@kevisual/router';
2
- import { app, cnb } from '../../app.ts';
2
+ import { app, notCNBCheck } from '../../app.ts';
3
3
 
4
4
  import { CNB_ENV } from "@/common/cnb-env.ts";
5
5
 
@@ -11,7 +11,7 @@ app.route({
11
11
  path: 'cnb',
12
12
  key: 'get-cnb-port-uri',
13
13
  description: '获取当前cnb工作空间的port代理uri',
14
- middleware: ['auth-admin'],
14
+ middleware: ['auth'],
15
15
  metadata: {
16
16
  tags: ['opencode'],
17
17
  ...createSkill({
@@ -24,6 +24,7 @@ app.route({
24
24
  })
25
25
  }
26
26
  }).define(async (ctx) => {
27
+ if (notCNBCheck(ctx)) return;
27
28
  const port = ctx.query?.port || 51515;
28
29
  const uri = CNB_ENV?.CNB_VSCODE_PROXY_URI as string || '';
29
30
  const finalUri = uri.replace('{{port}}', port.toString());
@@ -40,7 +41,7 @@ app.route({
40
41
  path: 'cnb',
41
42
  key: 'get-cnb-vscode-uri',
42
43
  description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
43
- middleware: ['auth-admin'],
44
+ middleware: ['auth'],
44
45
  metadata: {
45
46
  tags: ['opencode'],
46
47
  ...createSkill({
@@ -58,6 +59,7 @@ app.route({
58
59
  })
59
60
  }
60
61
  }).define(async (ctx) => {
62
+ if (notCNBCheck(ctx)) return;
61
63
  const web = ctx.query?.web ?? false;
62
64
  const vscode = ctx.query?.vscode ?? true; // 默认true
63
65
  const codebuddy = ctx.query?.codebuddy ?? false;
@@ -0,0 +1,48 @@
1
+ import { app, cnbManager } from '../../app.ts';
2
+
3
+ // "列出我的代码仓库,search blog"
4
+ // 列出我的知识库的代码仓库
5
+ app.route({
6
+ path: 'cnb',
7
+ key: 'clear-me-manager',
8
+ description: '清理我的cnb-manager记录',
9
+ middleware: ['auth'],
10
+
11
+ }).define(async (ctx) => {
12
+ const tokenUser = ctx.tokenUser;
13
+ if (!tokenUser) {
14
+ ctx.throw(401, '未授权');
15
+ }
16
+ const username = tokenUser.username;
17
+ if (!username) {
18
+ ctx.throw(400, '无效的用户信息');
19
+ }
20
+ if (username !== 'default') {
21
+ cnbManager.clearUsername(username);
22
+ }
23
+ ctx.body = { content: '已清理cnb-manager记录' };
24
+ }).addTo(app);
25
+
26
+ app.route({
27
+ path: 'cnb',
28
+ key: 'get-my-config',
29
+ description: '获取我的cnb配置',
30
+ middleware: ['auth'],
31
+ }).define(async (ctx) => {
32
+ const username = ctx.tokenUser?.username;
33
+ const token = ctx.query?.token;
34
+ if (!username) {
35
+ ctx.throw(400, '未授权');
36
+ }
37
+ if (!token) {
38
+ ctx.throw(400, '缺少token参数');
39
+ }
40
+ const cnbItem = await cnbManager.getCNB({ username, kevisualToken: token });
41
+ if (!cnbItem) {
42
+ ctx.throw(404, '未找到cnb-manager记录');
43
+ }
44
+ ctx.body = {
45
+ token: cnbItem.token,
46
+ cookie: cnbItem.cookie,
47
+ }
48
+ }).addTo(app);
@@ -8,6 +8,8 @@ import './knowledge/index.ts'
8
8
  import './issues/index.ts'
9
9
  import './cnb-board/index.ts';
10
10
  import './share/index.ts';
11
+ import './cnb-manager/index.ts';
12
+
11
13
  /**
12
14
  * 验证上下文中的 App ID 是否与指定的 App ID 匹配
13
15
  * @param {any} ctx - 上下文对象,可能包含 appId 属性
@@ -32,6 +34,9 @@ app.route({
32
34
  }).define(async (ctx) => {
33
35
  // ctx.body = 'Auth Route';
34
36
  if (checkAppId(ctx, app.appId)) {
37
+ ctx.state.tokenUser = {
38
+ username: 'default',
39
+ }
35
40
  return;
36
41
  }
37
42
  }).addTo(app, { overwrite: false });
@@ -43,6 +48,9 @@ app.route({
43
48
  }).define(async (ctx) => {
44
49
  // ctx.body = 'Admin Auth Route';
45
50
  if (checkAppId(ctx, app.appId)) {
51
+ ctx.state.tokenUser = {
52
+ username: 'default',
53
+ }
46
54
  return;
47
55
  }
48
56
  }).addTo(app, { overwrite: false });
@@ -1,5 +1,5 @@
1
1
  import { createSkill, tool } from '@kevisual/router';
2
- import { app, cnb } from '../../app.ts';
2
+ import { app, cnbManager } from '../../app.ts';
3
3
  import { IssueItem } from '@/index.ts';
4
4
 
5
5
  // 创建cnb issue, 仓库为 kevisual/kevisual 标题为 "自动化测试创建issue", 内容为 "这是通过API创建的issue,用于测试目的", body: "这是通过API创建的issue,用于测试目的"
@@ -7,7 +7,7 @@ app.route({
7
7
  path: 'cnb',
8
8
  key: 'create-issue',
9
9
  description: '创建 Issue, 参数 repo, title, body, assignees, labels, priority',
10
- middleware: ['auth-admin'],
10
+ middleware: ['auth'],
11
11
  metadata: {
12
12
  tags: ['opencode'],
13
13
  ...createSkill({
@@ -25,6 +25,7 @@ app.route({
25
25
  })
26
26
  }
27
27
  }).define(async (ctx) => {
28
+ const cnb = await cnbManager.getContext(ctx);
28
29
  const repo = ctx.query?.repo;
29
30
  const title = ctx.query?.title;
30
31
  const body = ctx.query?.body;
@@ -51,7 +52,7 @@ app.route({
51
52
  path: 'cnb',
52
53
  key: 'complete-issue',
53
54
  description: '完成 Issue, 参数 repo, issueNumber',
54
- middleware: ['auth-admin'],
55
+ middleware: ['auth'],
55
56
  metadata: {
56
57
  tags: ['opencode'],
57
58
  ...createSkill({
@@ -66,6 +67,7 @@ app.route({
66
67
  })
67
68
  }
68
69
  }).define(async (ctx) => {
70
+ const cnb = await cnbManager.getContext(ctx);
69
71
  const repo = ctx.query?.repo;
70
72
  const issueNumber = ctx.query?.issueNumber;
71
73
  const state = ctx.query?.state ?? 'closed';
@@ -1,5 +1,5 @@
1
1
  import { createSkill, tool } from '@kevisual/router';
2
- import { app, cnb } from '../../app.ts';
2
+ import { app, cnbManager } from '../../app.ts';
3
3
  import { useKey } from '@kevisual/context';
4
4
 
5
5
  // 查询 Issue 列表 repo是 kevisual/kevisual
@@ -7,7 +7,7 @@ app.route({
7
7
  path: 'cnb',
8
8
  key: 'list-issues',
9
9
  description: '查询 Issue 列表, 参数 repo, state, keyword, labels, page, page_size 等',
10
- middleware: ['auth-admin'],
10
+ middleware: ['auth'],
11
11
  metadata: {
12
12
  tags: ['opencode'],
13
13
  ...createSkill({
@@ -26,6 +26,7 @@ app.route({
26
26
  })
27
27
  }
28
28
  }).define(async (ctx) => {
29
+ const cnb = await cnbManager.getContext(ctx);
29
30
  const repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
30
31
  const state = ctx.query?.state;
31
32
  const keyword = ctx.query?.keyword;
@@ -1,5 +1,5 @@
1
1
  import { createSkill, tool } from '@kevisual/router';
2
- import { app, cnb } from '../../app.ts';
2
+ import { app, cnbManager } from '../../app.ts';
3
3
  import { CNBChat } from '@kevisual/ai/browser'
4
4
  import { useKey } from '@kevisual/context';
5
5
 
@@ -13,7 +13,7 @@ app.route({
13
13
  path: 'cnb',
14
14
  key: 'cnb-ai-chat',
15
15
  description: '调用cnb的知识库ai对话功能进行聊天',
16
- middleware: ['auth-admin'],
16
+ middleware: ['auth'],
17
17
  metadata: {
18
18
  tags: ['opencode'],
19
19
  ...createSkill({
@@ -27,6 +27,7 @@ app.route({
27
27
  })
28
28
  }
29
29
  }).define(async (ctx) => {
30
+ const cnb = await cnbManager.getContext(ctx);
30
31
  const question = ctx.query?.question;
31
32
  if (!question) {
32
33
  ctx.body = { content: '请提供有效的消息内容' };
@@ -89,7 +90,7 @@ app.route({
89
90
  path: 'cnb',
90
91
  key: 'cnb-rag-query',
91
92
  description: '调用cnb的知识库RAG查询功能进行问答',
92
- middleware: ['auth-admin'],
93
+ middleware: ['auth'],
93
94
  metadata: {
94
95
  tags: ['opencode'],
95
96
  ...createSkill({
@@ -103,6 +104,7 @@ app.route({
103
104
  })
104
105
  }
105
106
  }).define(async (ctx) => {
107
+ const cnb = await cnbManager.getContext(ctx);
106
108
  const question = ctx.query?.question;
107
109
  if (!question) {
108
110
  ctx.body = { content: '请提供有效的消息内容' };
@@ -1,5 +1,5 @@
1
1
  import { createSkill, tool } from '@kevisual/router';
2
- import { app, cnb } from '../../app.ts';
2
+ import { app, cnbManager } from '../../app.ts';
3
3
 
4
4
  // "列出我的代码仓库,search blog"
5
5
  // 列出我的知识库的代码仓库
@@ -7,7 +7,7 @@ app.route({
7
7
  path: 'cnb',
8
8
  key: 'list-repos',
9
9
  description: '列出我的代码仓库',
10
- middleware: ['auth-admin'],
10
+ middleware: ['auth'],
11
11
  metadata: {
12
12
  tags: ['opencode'],
13
13
  ...createSkill({
@@ -22,6 +22,7 @@ app.route({
22
22
  })
23
23
  }
24
24
  }).define(async (ctx) => {
25
+ const cnb = await cnbManager.getContext(ctx);
25
26
  const search = ctx.query?.search;
26
27
  const pageSize = ctx.query?.pageSize || 9999;
27
28
  const flags = ctx.query?.flags;