Compare commits

..

60 Commits

Author SHA1 Message Date
xiongxiao
7582d8a6d3 udpate 2026-03-16 22:47:06 +08:00
xiongxiao
3971a428ff update 2026-03-16 22:24:58 +08:00
xiongxiao
f05bf93650 更新 check.ts 中的参数验证库为 zod,升级 package.json 版本并添加 zod 依赖 2026-03-16 22:23:54 +08:00
xiongxiao
95bcad7587 更新 .cnb.yml 中的命令行参数,修改 agent/npc.ts 以支持用户名称,更新 package.json 版本,完善 readme.md 环境变量说明 2026-03-16 22:20:39 +08:00
xiongxiao
c7a5200aa5 update 2026-03-16 22:02:20 +08:00
xiongxiao
296f893c22 up 2026-03-16 22:00:06 +08:00
xiongxiao
d019ee0158 update 2026-03-16 21:55:40 +08:00
xiongxiao
c7a0c6ac3c 更新 bun.config.ts 中 CLI 入口文件路径,升级依赖版本 2026-03-16 21:49:58 +08:00
xiongxiao
8813e06c61 更新 .cnb.yml,修复命令行工具依赖安装命令,使用 bun 替代 npm 2026-03-16 21:47:55 +08:00
xiongxiao
ae5565cda7 更新 NPC 相关逻辑,重构命令行工具,添加新的 CLI 入口,升级依赖版本 2026-03-16 21:45:37 +08:00
xiongxiao
fe89bdee5b update 2026-03-16 00:19:42 +08:00
a2702915d5 更新 openclaw URL 生成逻辑,修复 token 参数格式;升级依赖版本 2026-03-14 00:07:09 +08:00
xiongxiao
700e86a4d2 recover 2026-03-13 12:01:27 +08:00
xiongxiao
d9fda44e78 更新 CNB 配置获取逻辑,修复未授权问题,添加测试文件 2026-03-13 04:33:16 +08:00
xiongxiao
cda1d0dc8f test 2026-03-13 03:21:26 +08:00
xiongxiao
8249da006f update 2026-03-13 01:56:36 +08:00
xiongxiao
176c82f1ac 移除未使用的 cnb 导出,更新依赖版本,添加 opencode 客户端示例 2026-03-13 01:56:22 +08:00
xiongxiao
525f0af8ba 添加获取单个 Issue 的功能,更新相关类型和环境变量 2026-03-12 23:47:19 +08:00
2e630b2da9 udpate 2026-03-11 17:04:32 +08:00
79d82a1f8d update 2026-03-11 17:02:50 +08:00
f48568132a update 2026-03-11 16:55:36 +08:00
a551cbe79d update 2026-03-11 16:53:01 +08:00
xiongxiao
460979b577 update 2026-03-11 16:17:04 +08:00
xiongxiao
ef38fc0596 update 2026-03-11 16:15:48 +08:00
4e4f54b4cd update 2026-03-11 15:49:48 +08:00
07453f59df update 2026-03-11 15:46:51 +08:00
14719adbe7 update 2026-03-11 15:44:20 +08:00
3459066cd7 t 2026-03-11 15:40:37 +08:00
fc6a5bd73c test 2026-03-11 15:38:59 +08:00
8379be1630 temp 2026-03-11 15:15:29 +08:00
813005ab9c feat: 添加cnb智能对话接口,支持用户提问和消息列表处理 2026-03-11 15:15:29 +08:00
xiongxiao
38ee73e48f feat: update package version to 0.0.42 and add CNB version fetching functionality
- Updated package version in package.json from 0.0.40 to 0.0.42.
- Added getCNBVersion function to fetch CNB version information from the API.
- Enhanced Issue class with methods for managing comments (list, create, get, update).
- Introduced new NPC and comment environment hooks for better context management.
- Implemented Docker image synchronization route for CNB.
- Added tests for version fetching and issue comment functionalities.
2026-03-10 03:45:02 +08:00
7b8f6fbf9f update 2026-03-10 01:18:52 +08:00
d08345d81c feat: 添加云端构建功能,支持参数配置和环境变量 2026-03-10 01:18:27 +08:00
xiongxiao
7d227d3913 feat: 更新获取CNB配置的路由,优化参数处理并添加错误处理 2026-03-09 19:13:20 +08:00
xiongxiao
a40c2352bf chore: 更新版本号至0.0.38 2026-03-09 18:56:56 +08:00
xiongxiao
382c4809ea feat: 重构CNB管理模块,添加清理记录功能,更新中间件为统一认证方式,优化工作空间相关路由 2026-03-09 18:54:33 +08:00
21ba07e55b feat: 更新保持工作空间存活技能,优化错误处理并添加保活数据 2026-03-07 02:19:49 +08:00
81a3aae8ec fix 2026-03-07 02:08:40 +08:00
dd5331bbaa feat: 添加新的分享路由以获取cnb工作空间中助手的访问地址 2026-03-07 01:50:50 +08:00
2eecbe273e fix 2026-03-06 02:47:00 +08:00
a563f3e0d6 feat: 将所有中间件名称从'admin-auth'更新为'auth-admin',以统一认证方式 2026-03-06 02:45:29 +08:00
327db1e09a feat: 更新获取已关闭工作空间列表的请求,添加pageSize参数以限制返回数量 2026-03-06 02:42:38 +08:00
8465ba7182 feat: 更新端口默认值为51515,添加useKey以获取默认仓库ID,更新创建文件描述信息 2026-03-06 02:39:45 +08:00
841ed6ffa7 feat(cnb-board): add cnb-dev-env routes and related functionalities
- Implemented routes for cnb-board to fetch live environment configurations, repository info, build info, pull request info, NPC info, and comment info.
- Created a utility function to execute shell commands.
- Added a check to determine if the environment is a CNB environment.
- Introduced a method to retrieve live markdown content with detailed service access information.
- Enhanced system information retrieval with CPU, memory, and disk usage metrics.
- Established a module structure for better organization of cnb-board related functionalities.
2026-03-06 02:26:13 +08:00
5043392939 feat: 更新版本号和依赖项,重构keepAliveFilePath路径以支持新的目录结构 2026-02-28 04:15:06 +08:00
8a6bb9bbe9 feat: 更新createLiveData函数,修改pm2Name格式以包含前缀"keep_" 2026-02-25 18:53:38 +08:00
8ca6b77e4d feat: 更新保持工作空间存活功能,移除wsUrl参数并在createLiveData中生成 2026-02-25 18:31:35 +08:00
d3286e2766 feat: 重构保持工作空间存活功能,更新参数和添加数据管理,新增测试用例 2026-02-25 17:34:49 +08:00
dd691f7a59 feat: 更新版本号和依赖项,添加获取工作空间 Cookie 的功能,新增测试用例 2026-02-25 16:41:41 +08:00
e6042e025f feat: 添加buildByConfig2方法以支持新的构建配置,并创建build-log.ts文件 2026-02-25 15:50:06 +08:00
xiongxiao
43b61dc656 chore: 更新依赖项版本,提升兼容性和稳定性 2026-02-20 21:51:44 +08:00
xiongxiao
3ff9e4e374 update 2026-02-20 16:10:51 +08:00
xiongxiao
24ee793db1 up: 2026-02-20 14:58 2026-02-20 14:59:01 +08:00
xiongxiao
d16adc07fe Add test for fetching live metadata from cnb issue 2026-02-20 03:53:05 +08:00
xiongxiao
0ebc94a7d0 简化 cnb 配置,移除 gitea 同步相关配置 2026-02-19 21:26:04 +08:00
47229c6db9 feat: 更新WebSocket Keep-Alive客户端库以支持Bun和Node.js环境,添加统一的消息处理方法,并创建keep.ts文件以初始化KeepAlive 2026-02-15 20:48:45 +08:00
d231f3748a feat: 添加getRepo方法到Repo类以获取仓库信息,并更新相关导入 2026-02-14 18:12:37 +08:00
9bb9f447ec chore: 更新版本号至0.0.24,添加keep模块的外部依赖并更新相关依赖版本 2026-02-14 16:53:30 +08:00
ea137eb70b fix: 更新stopWorkspace方法的返回类型为Result 2026-02-09 19:55:02 +08:00
77 changed files with 3881 additions and 477 deletions

View File

@@ -4,11 +4,22 @@ include:
.common_env: &common_env .common_env: &common_env
env: env:
TO_REPO: kevisual/cnb USERNAME: root
TO_URL: git.xiongxiao.me
imports: imports:
- https://cnb.cool/kevisual/env/-/blob/main/.env.development - https://cnb.cool/kevisual/env/-/blob/main/.env.development
.npc: &npc
- docker:
image: docker.cnb.cool/kevisual/dev-env/ubuntu-bun:latest
services:
- docker
env: !reference [.common_env, env]
imports: !reference [.common_env, imports]
stages:
- name: "task"
script: |
git clone https://cnb.cool/kevisual/cnb cnb
cd cnb && bun i && bun run agent/npc.ts cnb npc --args owner="小熊猫呜呜呜"
$: $:
vscode: vscode:
- docker: - docker:
@@ -16,29 +27,9 @@ $:
services: services:
- vscode - vscode
- docker - docker
env: !reference [.common_env, env]
imports: !reference [.common_env, imports] imports: !reference [.common_env, imports]
# 开发环境启动后会执行的任务
# stages:
# - name: pnpm install
# script: pnpm install
stages: !reference [.dev_template, stages]
.common_sync_to_gitea: &common_sync_to_gitea issue.comment@npc: *npc
- <<: *common_env issue.open: *npc
services: !reference [.common_sync_to_gitea_template, services] issue.reopen: *npc
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

6
.cnb/settings.yml Normal file
View File

@@ -0,0 +1,6 @@
npc:
defaultRole: router
roles:
- name: router
prompt: |
你好

1
.env.example Normal file
View File

@@ -0,0 +1 @@
CNB_COOKIE=CNBSESSION=1771242023.1935321989751226368.8841cb77d609c050b1a19877644487b6543b587a80953cbdf3018a15b9948b48;csrfkey=309068260

5
.gitignore vendored
View File

@@ -1,8 +1,9 @@
.env .env*
.env.local !.env*example
node_modules node_modules
.pnpm-store .pnpm-store
dist dist
pack-dist
storage storage

1
.npmrc
View File

@@ -1,3 +1,2 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
//npm.cnb.cool/kevisual/registry/-/packages/:_authToken=${CNB_API_KEY} //npm.cnb.cool/kevisual/registry/-/packages/:_authToken=${CNB_API_KEY}
//registry.npmjs.org/:_authToken=${NPM_TOKEN} //registry.npmjs.org/:_authToken=${NPM_TOKEN}

5
.opencode/package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@opencode-ai/plugin": "1.2.26"
}
}

16
SKILL.md Normal file
View File

@@ -0,0 +1,16 @@
---
name: 'cloud-cnb'
description: cloud-cnb 是一个基于 cnb.cool 的自动化工具,提供了工作区管理、代码仓库创建和 kevisual assistant app 等功能,旨在提升开发效率和项目管理的便捷性。
---
# 执行命令
```sh
## help
cloud -h
```
## 运行查看cli列表
```sh
cloud cli list
```

View File

@@ -1,17 +1,35 @@
import { QueryRouterServer as App } from '@kevisual/router' import { QueryRouterServer as App } from '@kevisual/router'
import { useContextKey } from '@kevisual/context' 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 { CNB } from '../src/index.ts';
import { CNBManager } from './modules/cnb-manager.ts'
export const config = useConfig() export const cnbManager = new CNBManager()
export const cnb = useContextKey<CNB>('cnb', () => {
// CNB_TOKEN是降级兼容变量推荐使用CNB_API_KEY // CNB_TOKEN是降级兼容变量推荐使用CNB_API_KEY
// CNB_TOKEN 是流水线自己就有的变量,但是权限比较小 // CNB_TOKEN 是流水线自己就有的变量,但是权限比较小
const token = useKey('CNB_API_KEY') as string || useKey('CNB_TOKEN') as string const token = useKey('CNB_API_KEY') as string || useKey('CNB_TOKEN') as string
// cookie 变量是可选的 // cookie 变量是可选的
const cookie = useKey('CNB_COOKIE') as string const cookie = useKey('CNB_COOKIE') as string
return new CNB({ token: token, cookie: cookie }); try {
}) cnbManager.addCNB({
export const app = useContextKey<App>('app', () => { username: 'default',
token: token,
cookie: cookie,
cnb: new CNB({ token: token, cookie: cookie })
})
} catch (error) {
process.exit(1)
}
await new Promise(resolve => setTimeout(resolve, 1000))
export const app = await useContextKey<App>('app', () => {
return new App({}) return new App({})
}) })
export const notCNBCheck = (ctx: any) => {
const isCNB = useKey('CNB');
if (!isCNB) {
ctx.throw(400, '当前环境非 cnb-board 环境,无法获取内容');
}
return false;
}

4
agent/cli.ts Normal file
View File

@@ -0,0 +1,4 @@
import { app } from './index.ts';
import { parse } from '@kevisual/router/src/commander.ts';
parse({ app: app, description: 'CNB控制台命令行工具', parse: true })

18
agent/main.ts Normal file
View File

@@ -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();
// }

View File

@@ -0,0 +1,141 @@
import { Result } from '@kevisual/query';
import { CNB } from '../../src/index.ts';
import { useKey } from '@kevisual/context';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
export const getConfig = async (opts: { token?: string }) => {
const kevisualEnv = useKey('KEVISUAL_ENV')
const isCNB = useKey('CNB');
let isProduction = kevisualEnv !== 'development' || (isCNB && !kevisualEnv);
const baseUrl = isProduction ? 'https://kevisual.cn/api/router' : 'https://kevisual.xiongxiao.me/api/router';
const res = await fetch(baseUrl, {
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
cnbAi: ReturnType<typeof createOpenAICompatible>
}
// const repo = useKey('CNB_REPO_SLUG_LOWERCASE') as string || 'kevision/kevision';
// export const cnbAi = createOpenAICompatible({
// baseURL: `https://api.cnb.cool/${repo}/-/ai/`,
// name: 'custom-cnb',
// apiKey: token,
// });
export class CNBManager {
cnbMap: Map<string, CNBItem> = 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<CNBItem | null> {
const username = opts?.username
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 })
}
} else {
console.error('获取 CNB 配置失败', username, res)
}
return null
}
/**
* 通过上下文获取 CNB 实例(直接返回 cnb 对象)
* @param ctx
* @returns CNB 实例
*/
async getContext(ctx: any) {
const item = await this.getCNBItem(ctx)
return item.cnb
}
async getCNBItem(ctx: any) {
const tokenUser = ctx?.state?.tokenUser
const username = tokenUser?.username
if (!username) {
ctx.throw(403, 'Unauthorized')
}
if (username === 'default') {
return this.getDefaultCNB()
}
const kevisualToken = ctx.query?.token;
const item = await this.getCNB({ username, kevisualToken });
if (!item) {
ctx.throw(400, '不存在的 CNB 配置项,请检查 登录 Token 是否正确,或添加 CNB 配置')
}
return item;
}
addCNB(opts: Partial<CNBItem>) {
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()
const repoSlug = useKey('CNB_REPO_SLUG_LOWERCASE') as string || 'kevision/kevision';
opts.cnbAi = createOpenAICompatible({
baseURL: `https://api.cnb.cool/${repoSlug}/-/ai/`,
name: `custom-cnb-${opts.username}`,
apiKey: opts.token,
})
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)
}
}

125
agent/npc.ts Normal file
View File

@@ -0,0 +1,125 @@
import { app } from './index.ts';
import { parse } from '@kevisual/router/src/commander.ts';
import { useIssueEnv, useCommentEnv, useRepoInfoEnv, IssueLabel } from '../src/index.ts'
import { pick } from 'es-toolkit';
import z from 'zod';
import { useKey } from '@kevisual/context';
const writeToProcess = (message: string) => {
if (process.send) {
process.send(message);
} else {
console.log(message);
}
}
const getIssuesLabels = async () => {
const issueEnv = useIssueEnv();
const repoInfoEnv = useRepoInfoEnv();
const issueId = issueEnv.issueId;
const repoSlug = repoInfoEnv.repoSlug;
if (!issueId || !repoSlug) {
return [];
}
const res = await app.run({
path: 'cnb',
key: 'getIssue',
payload: {
repo: repoSlug,
issueNumber: issueId
}
});
if (res.code === 200) {
const issueData = res.data as any;
const labels = issueData.labels || [];
return labels as IssueLabel[];
}
console.error('获取 Issue 详情失败', res);
return []
}
const main = async ({ exit, question }: { exit: (code: number) => void, question?: string }) => {
const repoInfoEnv = useRepoInfoEnv();
const commentEnv = useCommentEnv();
const issueEnv = useIssueEnv();
const pickCommentEnv = pick(commentEnv, ['commentId', 'commentIdLabel']);
const pickIssueEnv = pick(issueEnv, ['issueId', 'issueIdLabel', 'issueIid', 'issueIidLabel', 'issueTitle', 'issueTitleLabel', 'issueDescription', 'issueDescriptionLabel']);
const pickRepoInfoEnv = pick(repoInfoEnv, ['repoId', 'repoIdLabel', 'repoName', 'repoNameLabel', 'repoSlug', 'repoSlugLabel']);
// const issueLabels = issueEnv.issueLabels || [];
const isComment = !!commentEnv.commentId;
const envList = [
...Object.entries(pickRepoInfoEnv).map(([key, value]) => `${key}: ${value}`),
...Object.entries(issueEnv).map(([key, value]) => `${key}: ${value}`),
...Object.entries(pickCommentEnv).map(([key, value]) => `${key}: ${value}`),
]
writeToProcess('当前环境变量:');
const issueLabels = await getIssuesLabels();
const issueLabelsNames = issueLabels.map(label => label.name) || [];
envList.forEach(item => writeToProcess(item));
if (!isComment && !issueLabelsNames.includes('Run')) {
writeToProcess('当前 Issue 不包含 Run 标签,跳过执行');
return exit(0);
}
const messages = [
{
role: 'system',
content: `你是一个智能的代码助手, 根据用户提供的上下文信息,提供有用的建议和帮助, 如果用户的要求和执行工具不一致请说出你不能这么做。并把最后的结果提交一个评论到对应的issue中,提交的内容必须不能包含 @ 提及。用户提供的上下文信息如下:`
},
{
role: 'system',
content: `相关变量:${JSON.stringify({ ...pickCommentEnv, ...pickIssueEnv, ...pickRepoInfoEnv })}`
}, {
role: 'user',
content: question || commentEnv.commentBody || pickIssueEnv.issueDescription || '无'
}
]
writeToProcess('输入消息:');
writeToProcess(JSON.stringify(messages, null, 2));
const result = await app.run({
path: 'cnb',
key: 'chat',
payload: {
messages
}
}, { appId: app.appId })
if (result.code === 200) {
let _message = result.data.message || []
writeToProcess('执行完成')
writeToProcess(JSON.stringify(_message, null, 2))
exit(0);
} else {
writeToProcess(result.message || '执行错误')
exit(1);
}
}
app.route({
path: 'cnb',
key: 'npc',
description: 'CNB智能助手,提供智能建议和帮助, 程序入口',
metadata: {
tags: ['notInNpcAgent'],
args: {
needExit: z.boolean().optional().describe('是否需要在执行完成后退出进程'),
owner: z.string().optional().describe('用户名称')
}
}
}).define(async (ctx) => {
const needExit = ctx.args.needExit ?? true;
const owner = ctx.args.owner || '';
const exit = (code: number) => {
if (needExit) {
process.exit(code);
}
}
const buildUserNickName = useKey('CNB_BUILD_USER_NICKNAME')
let admins = owner.split(',').map(item => item.trim());
if (owner && admins.includes(buildUserNickName)) {
await main({ exit });
} else {
await main({ exit, question: `你是${owner}的专属助手请生成一条评论说明你不具备其他用户能访问的能力。同时你需要提示说明fork当前仓库后即可成为你的专属助手` });
}
}).addTo(app)
parse({ app: app, description: 'CNB控制台命令行工具', parse: true })

View File

@@ -2,5 +2,5 @@ import { app } from './index.ts';
import { createRouterAgentPluginFn } from '@kevisual/router/opencode' import { createRouterAgentPluginFn } from '@kevisual/router/opencode'
export const CnbPlugin = createRouterAgentPluginFn({ export const CnbPlugin = createRouterAgentPluginFn({
router: app, router: app as any,
}) })

View File

@@ -0,0 +1,66 @@
import { execSync } from 'child_process';
import { app, notCNBCheck } from '../../app.ts'
import { z } from 'zod'
import { title } from 'process';
app.route({
path: 'cnb',
key: 'docker-sync',
middleware: ['auth'],
metadata: {
tag: ['opencode'],
skill: 'cnb-docker-sync',
title: 'CNB Docker 镜像同步',
args: {
image: z.string().describe('Docker 同步的具体的镜像名称.'),
toVersion: z.string().optional().describe('修改后的版本号.'),
}
}
}).define(async (ctx) => {
const { image, toVersion } = ctx.args;
notCNBCheck(ctx);
if (!image) {
ctx.body = {
message: '请提供 Docker 镜像名称.',
data: null,
}
return;
}
const config = {
registry: 'docker.cnb.cool/kevisual/dev-env',
dockers: [{ image, toVersion }]
}
// docker tag ghcr.io/esm-dev/esm.sh:v137 docker.cnb.cool/kevisual/dev-env/esm.sh:v137
// docker push docker.cnb.cool/kevisual/dev-env/esm.sh:v137
const run = async () => {
const dockers = config.dockers;
for (const { image, toVersion } of dockers) {
const imageName = image.split(':')[0].split('/').slice(-1)[0]
const tag = image.split(':')[1]
const newImage = `${config.registry}/${imageName}:${toVersion || tag}`
// console.log(`docker tag ${image} ${newImage}`)
// console.log(`docker push ${newImage}`)
const shell = `docker pull ${image} && docker tag ${image} ${newImage} && docker push ${newImage}`
console.log(shell)
console.log('\n-------------new---------------------------------\n')
console.log(`${newImage}`)
console.log('\n--------------------------------------------------\n')
try {
execSync(shell, { stdio: 'inherit' })
} catch (error) {
console.error(`Error: ${error}`)
}
}
}
run().then(() => {
// TODO: 通知用户同步完成
});;
ctx.body = {
message: 'Docker 镜像同步任务中,请稍后在目标仓库查看.',
data: {
registry: config.registry,
dockers: config.dockers,
}
}
}).addTo(app);

View File

@@ -0,0 +1 @@
import './docker.ts'

View File

@@ -1,34 +1,31 @@
import { createSkill } from '@kevisual/router' import { createSkill, tool } from '@kevisual/router'
import { app } from '../../app.ts' import { app } from '../../app.ts'
import { tool } from '@opencode-ai/plugin/tool'
if (!app.hasRoute('call')) { // "调用 path: cnb key: list-repos"
// "调用 path: cnb key: list-repos" app.route({
app.route({ path: 'call',
path: 'call', key: '',
key: '', description: '调用',
description: '调用', middleware: ['auth-admin'],
middleware: ['admin-auth'], metadata: {
metadata: { tags: ['opencode'],
tags: ['opencode'], ...createSkill({
...createSkill({ skill: 'call-app',
skill: 'call-app', title: '调用app应用',
title: '调用app应用', summary: '调用router的应用, 参数path, key, payload',
summary: '调用router的应用, 参数path, key, payload', args: {
args: { path: tool.schema.string().describe('应用路径,例如 cnb'),
path: tool.schema.string().describe('应用路径,例如 cnb'), key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
key: tool.schema.string().optional().describe('应用key例如 list-repos'), payload: tool.schema.object({}).optional().describe('调用参数'),
payload: tool.schema.object({}).optional().describe('调用参数'), }
} })
}) },
}, }).define(async (ctx) => {
}).define(async (ctx) => { const { path, key } = ctx.query;
const { path, key } = ctx.query; console.log('call app', ctx.query);
console.log('call app', ctx.query); if (!path) {
if (!path) { ctx.throw('路径path不能为空');
ctx.throw('路径path不能为空'); }
} const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
const res = await ctx.run({ path, key, payload: ctx.query.payload || {} }); ctx.forward(res);
ctx.forward(res); }).addTo(app, { overwrite: false })
}).addTo(app)
}

49
agent/routes/chat/chat.ts Normal file
View File

@@ -0,0 +1,49 @@
import { runAgent } from '@kevisual/ai/agent'
import { app, cnbManager } from '../../app.ts';
import z from 'zod';
app.route({
path: 'cnb',
key: 'chat',
description: 'cnb智能对话接口',
middleware: ['auth'],
metadata: {
args: {
question: z.string().describe('用户输入的问题'),
messages: z.array(z.object({
role: z.enum(['user', 'assistant']).describe('消息角色user表示用户输入assistant表示助手回复'),
content: z.string().describe('消息内容')
})).describe('对话消息列表,按照时间顺序排列,包含用户和助手的历史消息'),
model: z.string().optional().describe('默认auto')
}
}
}).define(async (ctx) => {
// notCNBCheck(ctx);
if (!ctx.args.question && !ctx.args.messages) {
ctx.throw(400, '缺少必要参数必须提供question或messages');
return;
}
const model = ctx.args?.model || 'auto'
const item = await cnbManager.getCNBItem(ctx);
const cnbAi = item.cnbAi;
const messages = ctx.args.messages || [{
role: 'user',
content: ctx.args.question
}]
const routes = app.routes.filter(route => {
const tags = route.metadata?.tags || [];
if (tags.includes('notInNpcAgent')) {
return false;
}
return true
});
const result = await runAgent({
app,
messages: messages,
routes,
languageModel: cnbAi(model),
token: '',
// token: ctx.query.token as string,
});
ctx.body = result;
}).addTo(app);

View File

@@ -0,0 +1,414 @@
import { app, notCNBCheck } from '../../app.ts';
import { useKey } from '@kevisual/context'
import { getLiveMdContent } from './live/live-content.ts';
import z from 'zod';
app.route({
path: 'cnb_board',
key: 'live',
description: '获取cnb-board live的mdContent内容',
middleware: ['auth'],
metadata: {
args: {
more: z.boolean().optional().describe('是否获取更多系统信息默认false'),
}
}
}).define(async (ctx) => {
const more = ctx.query?.more ?? false
if (notCNBCheck(ctx)) return;
const list = getLiveMdContent({ more: more });
ctx.body = {
title: '开发环境模式配置',
list,
};
}).addTo(app);
app.route({
path: 'cnb_board',
key: 'live_repo_info',
description: '获取cnb-board live的repo信息',
middleware: ['auth']
}).define(async (ctx) => {
const repoSlug = useKey('CNB_REPO_SLUG') || '';
const repoName = useKey('CNB_REPO_NAME') || '';
const repoId = useKey('CNB_REPO_ID') || '';
const repoUrlHttps = useKey('CNB_REPO_UR if (notCNBCheck(ctx)) return;L_HTTPS') || '';
if (notCNBCheck(ctx)) return;
// 从 repoSlug 提取仓库名称
const repoNameFromSlug = repoSlug.split('/').pop() || '';
const labels = [
{
title: 'CNB_REPO_SLUG',
value: repoSlug,
description: '目标仓库路径,格式为 group_slug / repo_namegroup_slug / sub_gourp_slug /.../repo_name'
},
{
title: 'CNB_REPO_SLUG_LOWERCASE',
value: repoSlug.toLowerCase(),
description: '目标仓库路径小写格式'
},
{
title: 'CNB_REPO_NAME',
value: repoName || repoNameFromSlug,
description: '目标仓库名称'
},
{
title: 'CNB_REPO_NAME_LOWERCASE',
value: (repoName || repoNameFromSlug).toLowerCase(),
description: '目标仓库名称小写格式'
},
{
title: 'CNB_REPO_ID',
value: repoId,
description: '目标仓库的 id'
},
{
title: 'CNB_REPO_URL_HTTPS',
value: repoUrlHttps,
description: '目标仓库 https 地址'
}
]
ctx.body = {
title: 'CNB_BOARD_LIVE_REPO_INFO',
list: labels
};
}).addTo(app);
// 构建类变量
app.route({
path: 'cnb_board',
key: 'live_build_info',
description: '获取cnb-board live的构建信息',
middleware: ['auth']
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const labels = [
{
title: 'CNB_BUILD_ID',
value: useKey('CNB_BUILD_ID') || '',
description: '当前构建的流水号,全局唯一'
},
{
title: 'CNB_BUILD_WEB_URL',
value: useKey('CNB_BUILD_WEB_URL') || '',
description: '当前构建的日志地址'
},
{
title: 'CNB_BUILD_START_TIME',
value: useKey('CNB_BUILD_START_TIME') || '',
description: '当前构建的开始时间UTC 格式,示例 2025-08-21T09:13:45.803Z'
},
{
title: 'CNB_BUILD_USER',
value: useKey('CNB_BUILD_USER') || '',
description: '当前构建的触发者用户名'
},
{
title: 'CNB_BUILD_USER_NICKNAME',
value: useKey('CNB_BUILD_USER_NICKNAME') || '',
description: '当前构建的触发者昵称'
},
{
title: 'CNB_BUILD_USER_EMAIL',
value: useKey('CNB_BUILD_USER_EMAIL') || '',
description: '当前构建的触发者邮箱'
},
{
title: 'CNB_BUILD_USER_ID',
value: useKey('CNB_BUILD_USER_ID') || '',
description: '当前构建的触发者 id'
},
{
title: 'CNB_BUILD_USER_NPC_SLUG',
value: useKey('CNB_BUILD_USER_NPC_SLUG') || '',
description: '当前构建若为 NPC 触发,则为 NPC 所属仓库的路径'
},
{
title: 'CNB_BUILD_USER_NPC_NAME',
value: useKey('CNB_BUILD_USER_NPC_NAME') || '',
description: '当前构建若为 NPC 触发,则为 NPC 角色名'
},
{
title: 'CNB_BUILD_STAGE_NAME',
value: useKey('CNB_BUILD_STAGE_NAME') || '',
description: '当前构建的 stage 名称'
},
{
title: 'CNB_BUILD_JOB_NAME',
value: useKey('CNB_BUILD_JOB_NAME') || '',
description: '当前构建的 job 名称'
},
{
title: 'CNB_BUILD_JOB_KEY',
value: useKey('CNB_BUILD_JOB_KEY') || '',
description: '当前构建的 job key同 stage 下唯一'
},
{
title: 'CNB_BUILD_WORKSPACE',
value: useKey('CNB_BUILD_WORKSPACE') || '',
description: '自定义 shell 脚本执行的工作空间根目录'
},
{
title: 'CNB_BUILD_FAILED_MSG',
value: useKey('CNB_BUILD_FAILED_MSG') || '',
description: '流水线构建失败的错误信息,可在 failStages 中使用'
},
{
title: 'CNB_BUILD_FAILED_STAGE_NAME',
value: useKey('CNB_BUILD_FAILED_STAGE_NAME') || '',
description: '流水线构建失败的 stage 的名称,可在 failStages 中使用'
},
{
title: 'CNB_PIPELINE_NAME',
value: useKey('CNB_PIPELINE_NAME') || '',
description: '当前 pipeline 的 name没声明时为空'
},
{
title: 'CNB_PIPELINE_KEY',
value: useKey('CNB_PIPELINE_KEY') || '',
description: '当前 pipeline 的索引 key例如 pipeline-0'
},
{
title: 'CNB_PIPELINE_ID',
value: useKey('CNB_PIPELINE_ID') || '',
description: '当前 pipeline 的 id全局唯一字符串'
},
{
title: 'CNB_PIPELINE_DOCKER_IMAGE',
value: useKey('CNB_PIPELINE_DOCKER_IMAGE') || '',
description: '当前 pipeline 所使用的 docker imagealpine:latest'
},
{
title: 'CNB_PIPELINE_STATUS',
value: useKey('CNB_PIPELINE_STATUS') || '',
description: '当前流水线的构建状态,可在 endStages 中查看其可能的值包括success、error、cancel'
},
{
title: 'CNB_PIPELINE_MAX_RUN_TIME',
value: useKey('CNB_PIPELINE_MAX_RUN_TIME') || '',
description: '流水线最大运行时间,单位为毫秒'
},
{
title: 'CNB_RUNNER_IP',
value: useKey('CNB_RUNNER_IP') || '',
description: '当前 pipeline 所在 Runner 的 ip'
},
{
title: 'CNB_CPUS',
value: useKey('CNB_CPUS') || '',
description: '当前构建流水线可以使用的最大 CPU 核数'
},
{
title: 'CNB_MEMORY',
value: useKey('CNB_MEMORY') || '',
description: '当前构建流水线可以使用的最大内存大小,单位为 GiB'
},
{
title: 'CNB_IS_RETRY',
value: useKey('CNB_IS_RETRY') || '',
description: '当前构建是否由 rebuild 触发'
},
{
title: 'HUSKY_SKIP_INSTALL',
value: useKey('HUSKY_SKIP_INSTALL') || '',
description: '兼容 ci 环境下 husky'
}
]
ctx.body = {
title: 'CNB_BOARD_LIVE_BUILD_INFO',
list: labels
};
}).addTo(app);
// PR/合并类变量
app.route({
path: 'cnb_board',
key: 'live_pull_info',
description: '获取cnb-board live的PR信息',
middleware: ['auth-admin']
}).define(async (ctx) => {
const labels = [
{
title: 'CNB_PULL_REQUEST',
value: useKey('CNB_PULL_REQUEST') || '',
description: '对于由 pull_request、pull_request.update、pull_request.target 触发的构建,值为 true否则为 false'
},
{
title: 'CNB_PULL_REQUEST_LIKE',
value: useKey('CNB_PULL_REQUEST_LIKE') || '',
description: '对于由 合并类事件 触发的构建,值为 true否则为 false'
},
{
title: 'CNB_PULL_REQUEST_PROPOSER',
value: useKey('CNB_PULL_REQUEST_PROPOSER') || '',
description: '对于由 合并类事件 触发的构建,值为提出 PR 者名称,否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_TITLE',
value: useKey('CNB_PULL_REQUEST_TITLE') || '',
description: '对于由 合并类事件 触发的构建,值为提 PR 时候填写的标题,否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_BRANCH',
value: useKey('CNB_PULL_REQUEST_BRANCH') || '',
description: '对于由 合并类事件 触发的构建,值为发起 PR 的源分支名称,否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_SHA',
value: useKey('CNB_PULL_REQUEST_SHA') || '',
description: '对于由 合并类事件 触发的构建,值为当前 PR 源分支最新的提交 sha否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_TARGET_SHA',
value: useKey('CNB_PULL_REQUEST_TARGET_SHA') || '',
description: '对于由 合并类事件 触发的构建,值为当前 PR 目标分支最新的提交 sha否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_MERGE_SHA',
value: useKey('CNB_PULL_REQUEST_MERGE_SHA') || '',
description: '对于由 pull_request.merged 触发的构建,值为合并后的 sha对于 pull_request 等触发的构建,值为预合并后的 sha否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_SLUG',
value: useKey('CNB_PULL_REQUEST_SLUG') || '',
description: '对于由 合并类事件 触发的构建,值为源仓库的仓库 slug如 group_slug/repo_name否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_ACTION',
value: useKey('CNB_PULL_REQUEST_ACTION') || '',
description: '对于由 合并类事件 触发的构建可能的值有created(新建PR)、code_update(源分支push)、status_update(评审通过或CI状态变更),否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_ID',
value: useKey('CNB_PULL_REQUEST_ID') || '',
description: '对于由 合并类事件 触发的构建,值为当前或者关联 PR 的全局唯一 id否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_IID',
value: useKey('CNB_PULL_REQUEST_IID') || '',
description: '对于由 合并类事件 触发的构建,值为当前或者关联 PR 在仓库中的编号 iid否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_REVIEWERS',
value: useKey('CNB_PULL_REQUEST_REVIEWERS') || '',
description: '对于由 合并类事件 触发的构建,值为评审人列表,多个以 , 分隔,否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_REVIEW_STATE',
value: useKey('CNB_PULL_REQUEST_REVIEW_STATE') || '',
description: '对于由 合并类事件 触发的构建,有评审者且有人通过评审为 approve有评审者但无人通过评审为 unapprove否则为空字符串'
},
{
title: 'CNB_REVIEW_REVIEWED_BY',
value: useKey('CNB_REVIEW_REVIEWED_BY') || '',
description: '对于由 合并类事件 触发的构建,值为同意评审的评审人列表,多个以 , 分隔,否则为空字符串'
},
{
title: 'CNB_REVIEW_LAST_REVIEWED_BY',
value: useKey('CNB_REVIEW_LAST_REVIEWED_BY') || '',
description: '对于由 合并类事件 触发的构建,值为最后一个同意评审的评审人,否则为空字符串'
},
{
title: 'CNB_PULL_REQUEST_IS_WIP',
value: useKey('CNB_PULL_REQUEST_IS_WIP') || '',
description: '对于由 合并类事件 触发的构建,值为 true、false表示 PR 是否被设置为 [WIP],否则为空字符串'
}
]
ctx.body = {
title: 'CNB_BOARD_LIVE_PULL_INFO',
list: labels
};
}).addTo(app);
// NPC 类变量
app.route({
path: 'cnb_board',
key: 'live_npc_info',
description: '获取cnb-board live的NPC信息',
middleware: ['auth-admin']
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const labels = [
{
title: 'CNB_NPC_SLUG',
value: useKey('CNB_NPC_SLUG') || '',
description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库路径,否则为空字符串'
},
{
title: 'CNB_NPC_NAME',
value: useKey('CNB_NPC_NAME') || '',
description: '对于 NPC 事件触发的构建,值为 NPC 角色名,否则为空字符串'
},
{
title: 'CNB_NPC_SHA',
value: useKey('CNB_NPC_SHA') || '',
description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库默认分支最新提交的 sha否则为空字符串'
},
{
title: 'CNB_NPC_PROMPT',
value: useKey('CNB_NPC_PROMPT') || '',
description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色 Prompt否则为空字符串'
},
{
title: 'CNB_NPC_AVATAR',
value: useKey('CNB_NPC_AVATAR') || '',
description: '对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色头像,否则为空字符串'
},
{
title: 'CNB_NPC_ENABLE_THINKING',
value: useKey('CNB_NPC_ENABLE_THINKING') || '',
description: '对于 @npc 事件触发的构建,值为 NPC 角色是否开启思考,否则为空字符串'
}
]
ctx.body = {
title: 'CNB_BOARD_LIVE_NPC_INFO',
list: labels
};
}).addTo(app);
// 评论类变量
app.route({
path: 'cnb_board',
key: 'live_comment_info',
description: '获取cnb-board live的评论信息',
middleware: ['auth-admin']
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const labels = [
{
title: 'CNB_COMMENT_ID',
value: useKey('CNB_COMMENT_ID') || '',
description: '对于评论事件触发的构建,值为评论全局唯一 ID否则为空字符串'
},
{
title: 'CNB_COMMENT_BODY',
value: useKey('CNB_COMMENT_BODY') || '',
description: '对于评论事件触发的构建,值为评论内容,否则为空字符串'
},
{
title: 'CNB_COMMENT_TYPE',
value: useKey('CNB_COMMENT_TYPE') || '',
description: '对于 PR 代码评审评论,值为 diff_note对于 PR 非代码评审评论以及 Issue 评论,值为 note否则为空字符串'
},
{
title: 'CNB_COMMENT_FILE_PATH',
value: useKey('CNB_COMMENT_FILE_PATH') || '',
description: '对于 PR 代码评审评论,值为评论所在文件,否则为空字符串'
},
{
title: 'CNB_COMMENT_RANGE',
value: useKey('CNB_COMMENT_RANGE') || '',
description: '对于 PR 代码评审评论,值为评论所在代码行。如,单行为 L12多行为 L13-L16否则为空字符串'
},
{
title: 'CNB_REVIEW_ID',
value: useKey('CNB_REVIEW_ID') || '',
description: '对于 PR 代码评审,值为评审 ID否则为空字符串'
}
]
ctx.body = {
title: 'CNB_BOARD_LIVE_COMMENT_INFO',
list: labels
};
}).addTo(app);

View File

View File

@@ -0,0 +1,39 @@
import { app, notCNBCheck } from '../../app.ts';
import './cnb-dev-env.ts';
import { useKey } from '@kevisual/context';
import { spawnSync } from 'node:child_process';
export const execCommand = (command: string, options: { cwd?: string } = {}) => {
const { cwd } = options;
return spawnSync(command, {
stdio: 'inherit',
shell: true,
cwd: cwd,
env: process.env,
});
};
app.route({
path: 'cnb_board',
key: 'is-cnb-board',
description: '检查是否是 cnb-board 环境',
middleware: ['auth-admin']
}).define(async (ctx) => {
const isCNB = useKey('CNB');
ctx.body = {
isCNB: !!isCNB,
};
}).addTo(app);
app.route({
path: 'cnb_board',
key: 'exit',
description: 'cnb的工作环境退出程序',
middleware: ['auth'],
}).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const cmd = 'kill 1';
execCommand(cmd);
}).addTo(app);

View File

@@ -0,0 +1,341 @@
import { useKey } from "@kevisual/context"
import os from 'node:os';
import { execSync } from 'node:child_process';
import dayjs from 'dayjs';
export const getLiveMdContent = (opts?: { more?: boolean }) => {
const more = opts?.more ?? false
const url = useKey('CNB_VSCODE_PROXY_URI') || ''
const token = useKey('CNB_TOKEN') || ''
const openclawPort = useKey('OPENCLAW_PORT') || '80'
const openclawUrl = url.replace('{{port}}', openclawPort)
const openclawUrlSecret = openclawUrl + '/openclaw#token=' + token
const opencodePort = useKey('OPENCODE_PORT') || '100'
const opencodeUrl = url.replace('{{port}}', opencodePort)
// btoa('root:password'); //
const _opencodeURL = new URL(opencodeUrl)
_opencodeURL.username = 'root'
_opencodeURL.password = token
const opencodeUrlSecret = _opencodeURL.toString()
// console.log('btoa opencode auth: ', Buffer.from(`root:${token}`).toString('base64'))
const kevisualUrl = url.replace('{{port}}', '51515')
const openWebUrl = url.replace('{{port}}', '200')
const vscodeWebUrl = useKey('CNB_VSCODE_WEB_URL') || ''
const TEMPLATE = `# 开发环境模式配置
### 服务访问地址
#### nginx 反向代理访问(推荐)
- OpenClaw: ${openclawUrl + '/openclaw'}
- OpenCode: ${opencodeUrl}
- VSCode Web: ${vscodeWebUrl}
- OpenWebUI: ${openWebUrl}
- Kevisual: ${kevisualUrl}
### 密码访问
- OpenClaw: ${openclawUrlSecret}
- OpenCode: ${opencodeUrlSecret}
### 环境变量
- CNB_TOKEN: ${token}
### 其他说明
1. 保活说明
使用插件访问vscode web获取wss进行保活,避免长时间不操作导致的自动断开连接。
方法1 使用插件访问vscode web获取wss进行保活,避免长时间不操作导致的自动断开连接。
1. 安装插件[CNB LIVE](https://chromewebstore.google.com/detail/cnb-live/iajpiophkcdghonpijkcgpjafbcjhkko?pli=1)
2. 打开vscode web获取,点击插件获取json数据替换keep.json中的数据保持在线状态。
3. keep.json中的数据结构说明
- wss: vscode web的websocket地址
- cookie: vscode web的cookie保持和浏览器一致
- url: vscode web的访问地址可以直接访问vscode web
4. 运行cli命令ev cnb live -c /workspace/live/keep.json.(直接对话opencode或者openclaw调用cnb-live技能即可)
方法2环境变量设置CNB_COOKIE直接opencode或者openclaw的ui界面对话说cnb-keep-live保活他会自动调用保活同时不需要点cnb-lie插件获取配置。
2. Opencode web访问说明
Opencode打开web地址需要在浏览器输入用户名和密码用户名固定为root密码为CNB_TOKEN的值. 纯连接打开包含账号密码第一次点击后需要把账号密码清理掉才能访问opencode的bug导致的。
`
const labels = [
{
key: 'vscodeWebUrl',
title: 'VSCode Web 地址',
value: vscodeWebUrl,
description: 'VSCode Web 的访问地址'
},
{
key: 'kevisualUrl',
title: 'Kevisual 地址',
value: kevisualUrl,
description: 'Kevisual 的访问地址,可以通过该地址访问 Kevisual 服务'
},
{
key: 'cnbTempToken',
title: 'CNB Token',
value: token,
description: 'CNB 临时 Token保持和环境变量 CNB_TOKEN 一致'
},
{
key: 'openWebUrl',
title: 'OpenWebUI 地址',
value: openWebUrl,
description: 'OpenWebUI 的访问地址,可以通过该地址访问 OpenWebUI 服务'
},
{
key: 'openclawUrl',
title: 'OpenClaw 地址',
value: openclawUrl + '/openclaw',
description: 'OpenClaw 的访问地址,可以通过该地址访问 OpenClaw 服务'
},
{
key: 'openclawUrlSecret',
title: 'OpenClaw 访问地址(含 Token)',
value: openclawUrlSecret,
description: 'OpenClaw 的访问地址,包含 token 参数,可以直接访问 OpenClaw 服务'
},
{
key: 'opencodeUrl',
title: 'OpenCode 地址',
value: opencodeUrl,
description: 'OpenCode 的访问地址,可以通过该地址访问 OpenCode 服务'
},
{
key: 'opencodeUrlSecret',
title: 'OpenCode 访问地址(含 Token)',
value: opencodeUrlSecret,
description: 'OpenCode 的访问地址,包含 token 参数,可以直接访问 OpenCode 服务'
},
{
key: 'docs',
title: '配置说明文档',
value: TEMPLATE,
description: '开发环境模式配置说明文档'
}
]
const osInfoList = createOSInfo(more)
labels.push(...osInfoList)
return labels
}
const createOSInfo = (more = false) => {
const labels: Array<{ key: string; title: string; value: string | number; description: string }> = []
const startTimer = useKey('CNB_BUILD_START_TIME') || ''
// CPU 使用率
const cpus = os.cpus()
let totalIdle = 0
let totalTick = 0
cpus.forEach((cpu) => {
for (const type in cpu.times) {
totalTick += cpu.times[type as keyof typeof cpu.times]
}
totalIdle += cpu.times.idle
})
const cpuUsage = ((1 - totalIdle / totalTick) * 100).toFixed(2)
// 内存使用情况 (使用 free 命令)
let memUsed = 0
let memTotal = 0
let memFree = 0
try {
const freeOutput = execSync('free -b', { encoding: 'utf-8' })
const lines = freeOutput.trim().split('\n')
const memLine = lines.find(line => line.startsWith('Mem:'))
if (memLine) {
const parts = memLine.split(/\s+/)
memTotal = parseInt(parts[1])
memUsed = parseInt(parts[2])
memFree = parseInt(parts[3])
}
} catch (e) {
// 如果 free 命令失败,使用 os 模块
memTotal = os.totalmem()
memFree = os.freemem()
memUsed = memTotal - memFree
}
const memUsage = memTotal > 0 ? ((memUsed / memTotal) * 100).toFixed(2) : '0.00'
// 格式化字节为人类可读格式
const formatBytes = (bytes: number) => {
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
if (bytes === 0) return '0 B'
const i = Math.floor(Math.log(bytes) / Math.log(1024))
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i]
}
// 运行时间格式化
const formatUptime = (seconds: number) => {
const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = Math.floor(seconds % 60)
let uptimeStr = ''
if (days > 0) uptimeStr += `${days}`
if (hours > 0) uptimeStr += `${hours}小时 `
if (minutes > 0) uptimeStr += `${minutes}分钟 `
return `${uptimeStr}${secs}`
}
// 磁盘使用情况 (使用 du 命令,获取当前目录)
let diskUsage = ''
try {
const duOutput = execSync('du -sh .', { encoding: 'utf-8' })
diskUsage = duOutput.trim().split('\t')[0]
} catch (e) {
diskUsage = '获取失败'
}
labels.push(
{
key: 'cpuUsage',
title: 'CPU 使用率',
value: `${cpuUsage}%`,
description: 'CPU 使用率'
},
{
key: 'cpuCores',
title: 'CPU 核心数',
value: cpus.length,
description: 'CPU 核心数'
},
{
key: 'memoryUsed',
title: '已使用内存',
value: formatBytes(memUsed),
description: '已使用内存'
},
{
key: 'memoryTotal',
title: '总内存',
value: formatBytes(memTotal),
description: '总内存'
},
{
key: 'memoryFree',
title: '空闲内存',
value: formatBytes(memFree),
description: '空闲内存'
},
{
key: 'memoryUsage',
title: '内存使用率',
value: `${memUsage}%`,
description: '内存使用率'
},
{
key: 'diskUsage',
title: '磁盘使用',
value: diskUsage,
description: '当前目录磁盘使用情况'
},
)
// 如果有 CNB_BUILD_START_TIME添加构建启动时间
if (startTimer) {
// startTimer 是日期字符串格式
const buildStartTime = dayjs(startTimer as string).format('YYYY-MM-DD HH:mm:ss')
const buildStartTimestamp = dayjs(startTimer as string).valueOf()
const buildUptime = Date.now() - buildStartTimestamp
const buildUptimeStr = formatUptime(Math.floor(buildUptime / 1000))
const maxRunTime = useKey('CNB_PIPELINE_MAX_RUN_TIME') || 0 // 毫秒
labels.push(
{
key: 'buildStartTime',
title: '构建启动时间',
value: buildStartTime,
description: '构建启动时间'
},
{
key: 'buildUptime',
title: '构建已运行时间',
value: buildUptime,
description: `构建已运行时间: ${buildUptimeStr}`
}
)
if (maxRunTime > 0) {
// 计算到达4点的倒计时
const now = dayjs()
const today4am = now.hour(4).minute(0).second(0).millisecond(0)
let timeTo4 = today4am.valueOf() - now.valueOf()
if (timeTo4 < 0) {
// 如果已经过了4点计算到明天4点
timeTo4 = today4am.add(1, 'day').valueOf() - now.valueOf()
}
const timeTo4Str = `[距离晚上4点重启时间: ${formatUptime(Math.floor(timeTo4 / 1000))}]`
labels.push({
key: 'buildMaxRunTime',
title: '最大运行时间',
value: formatUptime(Math.floor(maxRunTime / 1000)),
description: '构建最大运行时间(限制时间)'
})
labels.unshift({
key: 'remainingTime',
title: '剩余时间',
value: maxRunTime - buildUptime,
description: '构建剩余时间' + formatUptime(Math.floor((maxRunTime - buildUptime) / 1000)) + ' ' + timeTo4Str
})
}
}
// more 为 true 时添加更多系统信息
if (more) {
const loadavg = os.loadavg()
labels.push(
{
key: 'hostname',
title: '主机名',
value: os.hostname(),
description: '主机名'
},
{
key: 'platform',
title: '运行平台',
value: os.platform(),
description: '运行平台'
},
{
key: 'arch',
title: '系统架构',
value: os.arch(),
description: '系统架构'
},
{
key: 'osType',
title: '操作系统类型',
value: os.type(),
description: '操作系统类型'
},
{
key: 'loadavg1m',
title: '系统负载 (1分钟)',
value: loadavg[0].toFixed(2),
description: '系统负载 (1分钟)'
},
{
key: 'loadavg5m',
title: '系统负载 (5分钟)',
value: loadavg[1].toFixed(2),
description: '系统负载 (5分钟)'
},
{
key: 'loadavg15m',
title: '系统负载 (15分钟)',
value: loadavg[2].toFixed(2),
description: '系统负载 (15分钟)'
}
)
}
return labels
}

View File

@@ -0,0 +1 @@
export * from './is-cnb.ts';

View File

@@ -0,0 +1,6 @@
import { useKey } from "@kevisual/context";
export const isCnb = () => {
const CNB = useKey('CNB');
return !!CNB;
}

View File

@@ -1,13 +1,13 @@
import { createSkill } from '@kevisual/router'; import { createSkill } from '@kevisual/router';
import { app, cnb } from '../../app.ts'; import { app, cnbManager } from '../../app.ts';
import { tool } from '@opencode-ai/plugin/tool'; import { z } from 'zod';
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'user-check', key: 'user-check',
description: '检查用户登录状态参数checkToken,default true; checkCookie, default false', description: '检查用户登录状态参数checkToken,default true; checkCookie, default false',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -15,8 +15,8 @@ app.route({
title: 'CNB 登录验证信息', title: 'CNB 登录验证信息',
summary: '验证 CNB 登录信息是否有效', summary: '验证 CNB 登录信息是否有效',
args: { args: {
checkToken: tool.schema.boolean().describe('是否检查 Token 的有效性').default(true), checkToken: z.boolean().describe('是否检查 Token 的有效性').default(true),
checkCookie: tool.schema.boolean().describe('是否检查 Cookie 的有效性').default(false), checkCookie: z.boolean().describe('是否检查 Cookie 的有效性').default(false),
}, },
}) })
} }
@@ -24,6 +24,7 @@ app.route({
const checkToken = ctx.query?.checkToken ?? true; const checkToken = ctx.query?.checkToken ?? true;
const checkCookie = ctx.query?.checkCookie ?? false; const checkCookie = ctx.query?.checkCookie ?? false;
let content = ''; let content = '';
const cnb = await cnbManager.getContext(ctx);
if (checkToken) { if (checkToken) {
const res = await cnb.user.getUser(); const res = await cnb.user.getUser();
if (res?.code !== 200) { if (res?.code !== 200) {

View File

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

View File

@@ -1,5 +1,5 @@
import { createSkill, tool } from '@kevisual/router'; 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"; import { CNB_ENV } from "@/common/cnb-env.ts";
@@ -11,7 +11,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'get-cnb-port-uri', key: 'get-cnb-port-uri',
description: '获取当前cnb工作空间的port代理uri', description: '获取当前cnb工作空间的port代理uri',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -19,12 +19,13 @@ app.route({
title: '获取当前cnb工作空间的port代理uri', title: '获取当前cnb工作空间的port代理uri',
summary: '获取当前cnb工作空间的port代理uri用于端口转发', summary: '获取当前cnb工作空间的port代理uri用于端口转发',
args: { args: {
port: tool.schema.number().optional().describe('端口号,默认为4096'), port: tool.schema.number().optional().describe('端口号,默认为51515'),
} }
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const port = ctx.query?.port || 4096; if (notCNBCheck(ctx)) return;
const port = ctx.query?.port || 51515;
const uri = CNB_ENV?.CNB_VSCODE_PROXY_URI as string || ''; const uri = CNB_ENV?.CNB_VSCODE_PROXY_URI as string || '';
const finalUri = uri.replace('{{port}}', port.toString()); const finalUri = uri.replace('{{port}}', port.toString());
let content = ` let content = `
@@ -40,7 +41,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'get-cnb-vscode-uri', key: 'get-cnb-vscode-uri',
description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh', description: '获取当前cnb工作空间的vscode代理uri, 包括多种访问方式, 如web、vscode、codebuddy、cursor、ssh',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -58,6 +59,7 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
if (notCNBCheck(ctx)) return;
const web = ctx.query?.web ?? false; const web = ctx.query?.web ?? false;
const vscode = ctx.query?.vscode ?? true; // 默认true const vscode = ctx.query?.vscode ?? true; // 默认true
const codebuddy = ctx.query?.codebuddy ?? false; const codebuddy = ctx.query?.codebuddy ?? false;

View File

@@ -0,0 +1,49 @@
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.state?.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);
app.route({
path: 'cnb',
key: 'get-my-config',
description: '获取我的cnb配置',
middleware: ['auth'],
}).define(async (ctx) => {
const tokenUser = ctx.state?.tokenUser;
const username = tokenUser?.username;
const token = ctx.query?.token;
if (!username) {
ctx.throw(400, '未授权');
}
if (!token) {
ctx.throw(400, '缺少token参数');
}
const cnbItem = await cnbManager.getCNB({ username, kevisualToken: token });
if (!cnbItem) {
ctx.throw(404, '未找到cnb-manager记录');
}
ctx.body = {
token: cnbItem.token,
cookie: cnbItem.cookie,
}
}).addTo(app);

View File

@@ -0,0 +1,6 @@
const token = 'st_logh1b3ozq2resntxlnk4bccao0bon8e'
import { CNBManager } from "../../modules/cnb-manager.ts"
const cnbManager = new CNBManager()
const cnbItem = await cnbManager.getCNB({ username: 'root', kevisualToken: token });
console.log('cnbItem', cnbItem)

View File

@@ -6,6 +6,11 @@ import './call/index.ts'
import './cnb-env/index.ts' import './cnb-env/index.ts'
import './knowledge/index.ts' import './knowledge/index.ts'
import './issues/index.ts' import './issues/index.ts'
import './cnb-board/index.ts';
import './share/index.ts';
import './cnb-manager/index.ts';
import './build/index.ts';
import './chat/chat.ts';
/** /**
* 验证上下文中的 App ID 是否与指定的 App ID 匹配 * 验证上下文中的 App ID 是否与指定的 App ID 匹配
@@ -25,25 +30,29 @@ const checkAppId = (ctx: any, appId: string) => {
return false; return false;
} }
if (!app.hasRoute('auth')) { app.route({
app.route({ id: 'auth',
id: 'auth', path: 'auth',
path: 'auth', }).define(async (ctx) => {
}).define(async (ctx) => { // ctx.body = 'Auth Route';
// ctx.body = 'Auth Route'; if (checkAppId(ctx, app.appId)) {
if (checkAppId(ctx, app.appId)) { ctx.state.tokenUser = {
return; username: 'default',
} }
}).addTo(app); return;
}
}).addTo(app, { overwrite: false });
app.route({ app.route({
id: 'admin-auth', id: 'auth-admin',
path: 'admin-auth', path: 'auth-admin',
middleware: ['auth'], middleware: ['auth'],
}).define(async (ctx) => { }).define(async (ctx) => {
// ctx.body = 'Admin Auth Route'; // ctx.body = 'Admin Auth Route';
if (checkAppId(ctx, app.appId)) { if (checkAppId(ctx, app.appId)) {
return; ctx.state.tokenUser = {
username: 'default',
} }
}).addTo(app); return;
} }
}).addTo(app, { overwrite: false });

View File

@@ -0,0 +1,177 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnbManager } from '../../app.ts';
import { useKey } from '@kevisual/context';
// 查询 Issue 评论列表
app.route({
path: 'cnb',
key: 'list-issue-comments',
description: '查询 Issue 评论列表, 参数 repo, issueNumber, page, page_size',
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'list-issue-comments',
title: '查询 Issue 评论列表',
args: {
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
issueNumber: tool.schema.number().describe('Issue 编号'),
page: tool.schema.number().optional().describe('分页页码,默认: 1'),
page_size: tool.schema.number().optional().describe('分页每页大小,默认: 30'),
},
summary: '查询 Issue 评论列表',
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
const issueNumber = ctx.query?.issueNumber;
const page = ctx.query?.page ? Number(ctx.query.page) : undefined;
const page_size = ctx.query?.page_size ? Number(ctx.query.page_size) : undefined;
if (!repo) {
ctx.throw(400, '缺少参数 repo');
}
if (!issueNumber) {
ctx.throw(400, '缺少参数 issueNumber');
}
const params: Record<string, any> = {};
if (page) params.page = page;
if (page_size) params.page_size = page_size;
const res = await cnb.issue.getCommentList(repo, issueNumber, params);
ctx.forward(res);
}).addTo(app);
// 创建 Issue 评论
app.route({
path: 'cnb',
key: 'create-issue-comment',
description: '创建 Issue 评论, 参数 repo, issueNumber, body',
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'create-issue-comment',
title: '创建 Issue 评论',
args: {
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
issueNumber: tool.schema.number().describe('Issue 编号'),
body: tool.schema.string().describe('评论内容'),
clearAt: tool.schema.boolean().optional().describe('是否清除评论内容中的 @ 提及,默认: true'),
},
summary: '创建 Issue 评论',
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
const issueNumber = ctx.query?.issueNumber;
let body = ctx.query?.body;
const clearAt = ctx.query?.clearAt ?? true;
if (!repo) {
ctx.throw(400, '缺少参数 repo');
}
if (!issueNumber) {
ctx.throw(400, '缺少参数 issueNumber');
}
if (!body) {
ctx.throw(400, '缺少参数 body');
}
if (clearAt && body) {
// 清除评论内容中的 @ 提及
body = body.replace(/@/g, '');
}
const res = await cnb.issue.createComment(repo, issueNumber, body);
ctx.forward(res);
}).addTo(app);
// 获取 Issue 指定评论
app.route({
path: 'cnb',
key: 'get-issue-comment',
description: '获取 Issue 指定评论, 参数 repo, issueNumber, commentId',
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'get-issue-comment',
title: '获取 Issue 评论',
args: {
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
issueNumber: tool.schema.number().describe('Issue 编号'),
commentId: tool.schema.number().describe('评论 ID'),
},
summary: '获取 Issue 评论',
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
const issueNumber = ctx.query?.issueNumber;
const commentId = ctx.query?.commentId;
if (!repo) {
ctx.throw(400, '缺少参数 repo');
}
if (!issueNumber) {
ctx.throw(400, '缺少参数 issueNumber');
}
if (!commentId) {
ctx.throw(400, '缺少参数 commentId');
}
const res = await cnb.issue.getComment(repo, issueNumber, commentId);
ctx.forward(res);
}).addTo(app);
// 修改 Issue 评论
app.route({
path: 'cnb',
key: 'update-issue-comment',
description: '修改 Issue 评论, 参数 repo, issueNumber, commentId, body',
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'update-issue-comment',
title: '修改 Issue 评论',
args: {
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
issueNumber: tool.schema.number().describe('Issue 编号'),
commentId: tool.schema.number().describe('评论 ID'),
body: tool.schema.string().describe('评论内容'),
clearAt: tool.schema.boolean().optional().describe('是否清除评论内容中的 @ 提及,默认: true'),
},
summary: '修改 Issue 评论',
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
const issueNumber = ctx.query?.issueNumber;
const commentId = ctx.query?.commentId;
let body = ctx.query?.body;
const clearAt = ctx.query?.clearAt ?? true;
if (!repo) {
ctx.throw(400, '缺少参数 repo');
}
if (!issueNumber) {
ctx.throw(400, '缺少参数 issueNumber');
}
if (!commentId) {
ctx.throw(400, '缺少参数 commentId');
}
if (!body) {
ctx.throw(400, '缺少参数 body');
}
if (clearAt && body) {
// 清除评论内容中的 @ 提及
body = body.replace(/@/g, '');
}
const res = await cnb.issue.updateComment(repo, issueNumber, commentId, body);
ctx.forward(res);
}).addTo(app);

View File

@@ -1,2 +1,3 @@
import './list.ts' import './list.ts'
import './issue.ts' import './issue.ts'
import './comments.ts'

View File

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

View File

@@ -1,19 +1,20 @@
import { createSkill, tool } from '@kevisual/router'; 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 // 查询 Issue 列表 repo是 kevisual/kevisual
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'list-issues', key: 'list-issues',
description: '查询 Issue 列表, 参数 repo, state, keyword, labels, page, page_size 等', description: '查询 Issue 列表, 参数 repo, state, keyword, labels, page, page_size 等',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
skill: 'list-issues', skill: 'list-issues',
title: '查询 Issue 列表', title: '查询 Issue 列表',
args: { args: {
repo: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'), repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
state: tool.schema.string().optional().describe('Issue 状态open 或 closed'), state: tool.schema.string().optional().describe('Issue 状态open 或 closed'),
keyword: tool.schema.string().optional().describe('问题搜索关键词'), keyword: tool.schema.string().optional().describe('问题搜索关键词'),
labels: tool.schema.string().optional().describe('问题标签,多个用逗号分隔'), labels: tool.schema.string().optional().describe('问题标签,多个用逗号分隔'),
@@ -25,7 +26,8 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const repo = ctx.query?.repo; const cnb = await cnbManager.getContext(ctx);
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
const state = ctx.query?.state; const state = ctx.query?.state;
const keyword = ctx.query?.keyword; const keyword = ctx.query?.keyword;
const labels = ctx.query?.labels; const labels = ctx.query?.labels;
@@ -47,4 +49,37 @@ app.route({
const res = await cnb.issue.getList(repo, params); const res = await cnb.issue.getList(repo, params);
ctx.forward(res); ctx.forward(res);
}).addTo(app);
app.route({
path: 'cnb',
key: 'getIssue',
description: '获取 单个 Issue',
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'getIssue',
title: '获取 单个 Issue',
args: {
repo: tool.schema.string().optional().describe('代码仓库名称, 如 my-user/my-repo'),
issueNumber: tool.schema.union([tool.schema.string(), tool.schema.number()]).describe('Issue 编号'),
},
summary: '获取 单个 Issue',
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
const issueNumber = ctx.query?.issueNumber;
if (!repo) {
ctx.throw(400, '缺少参数 repo');
}
if (!issueNumber) {
ctx.throw(400, '缺少参数 issueNumber');
}
const res = await cnb.issue.getItem(repo, issueNumber);
ctx.forward(res);
}).addTo(app); }).addTo(app);

View File

@@ -1,6 +1,7 @@
import { createSkill, tool } from '@kevisual/router'; 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 { CNBChat } from '@kevisual/ai/browser'
import { useKey } from '@kevisual/context';
/** /**
@@ -12,7 +13,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'cnb-ai-chat', key: 'cnb-ai-chat',
description: '调用cnb的知识库ai对话功能进行聊天', description: '调用cnb的知识库ai对话功能进行聊天',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -26,6 +27,7 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const question = ctx.query?.question; const question = ctx.query?.question;
if (!question) { if (!question) {
ctx.body = { content: '请提供有效的消息内容' }; ctx.body = { content: '请提供有效的消息内容' };
@@ -88,7 +90,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'cnb-rag-query', key: 'cnb-rag-query',
description: '调用cnb的知识库RAG查询功能进行问答', description: '调用cnb的知识库RAG查询功能进行问答',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -102,12 +104,13 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const question = ctx.query?.question; const question = ctx.query?.question;
if (!question) { if (!question) {
ctx.body = { content: '请提供有效的消息内容' }; ctx.body = { content: '请提供有效的消息内容' };
return; return;
} }
let repo = ctx.query?.repo; let repo = ctx.query?.repo || useKey('CNB_REPO_SLUG_LOWERCASE');
if (!repo) { if (!repo) {
// 如果未指定知识库仓库ID则使用默认知识库 // 如果未指定知识库仓库ID则使用默认知识库
const res = await cnb.repo.getRepoList({ flags: 'KnowledgeBase' }); const res = await cnb.repo.getRepoList({ flags: 'KnowledgeBase' });

View File

@@ -0,0 +1,12 @@
import { createOpencodeClient } from "@opencode-ai/sdk"
const client = await createOpencodeClient({
// baseUrl: "https://yccb64t1z-100.cnb.run",
// auth: async () => {
// return 'cm9vdDozR0I2MDg5ZGpYOE5oMDFjM1FteE5DWDd0ZkI='
// }
baseUrl: "http://localhost:4096",
})
const sessionList = await client.session.list()
console.log(sessionList.data)

View File

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

View File

@@ -1,13 +1,12 @@
import { app, cnb } from '../../app.ts'; import { app, cnbManager } from '../../app.ts';
import { createSkill, Skill } from '@kevisual/router' import { createSkill, Skill, tool } from '@kevisual/router'
import { tool } from "@opencode-ai/plugin/tool"
// 创建一个仓库 kevisual/test-repo // 创建一个仓库 kevisual/test-repo
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'create-repo', key: 'create-repo',
description: '创建代码仓库, 参数name, visibility, description', description: '创建代码仓库, 参数name, visibility, description',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -22,6 +21,7 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const name = ctx.query?.name; const name = ctx.query?.name;
const visibility = ctx.query?.visibility ?? 'public'; const visibility = ctx.query?.visibility ?? 'public';
const description = ctx.query?.description ?? ''; const description = ctx.query?.description ?? '';
@@ -43,11 +43,32 @@ app.route({
} }
}).addTo(app); }).addTo(app);
app.route({
path: 'cnb',
key: 'get-repo',
description: '获取代码仓库详情, 参数name',
middleware: ['auth'],
metadata: {
args: {
name: tool.schema.string().describe('代码仓库名称, 如 my-user/my-repo'),
}
}
}).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.getRepo(name);
ctx.forward(res);
}).addTo(app);
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'create-repo-file', key: 'create-repo-file',
description: '在代码仓库中创建文件, repoName, filePath, content, encoding', description: '在代码仓库中创建文件, repoName, filePath, content, encoding。使用CNB_COOKIE进行鉴权',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -63,6 +84,7 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repoName = ctx.query?.repoName; const repoName = ctx.query?.repoName;
const filePath = ctx.query?.filePath; const filePath = ctx.query?.filePath;
const content = ctx.query?.content; const content = ctx.query?.content;
@@ -86,7 +108,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'delete-repo', key: 'delete-repo',
description: '删除代码仓库, 参数name', description: '删除代码仓库, 参数name',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -99,12 +121,62 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const name = ctx.query?.name; const name = ctx.query?.name;
if (!name) { if (!name) {
ctx.throw(400, '缺少参数 name'); ctx.throw(400, '缺少参数 name');
} }
try {
const resCookie = await cnb.user.checkCookieValid()
if (resCookie.code !== 200) {
ctx.throw(401, 'Cookie 无效或已过期');
}
const res = await cnb.repo.deleteRepoCookie(name);
ctx.forward(res);
} catch (error) {
ctx.code = 200
ctx.body = { content: '已经删除' }
}
}).addTo(app);
const res = await cnb.repo.deleteRepo(name);
app.route({
path: 'cnb',
key: 'update-repo-info',
description: '更新代码仓库信息, 参数name, description',
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'update-repo-info',
title: '更新代码仓库信息',
args: {
name: tool.schema.string().describe('代码仓库名称'),
description: tool.schema.string().describe('代码仓库描述'),
license: tool.schema.string().describe('代码仓库许可证类型,如 MIT').optional(),
site: tool.schema.string().describe('代码仓库主页链接').optional(),
topics: tool.schema.array(tool.schema.string()).describe('代码仓库话题标签列表').optional(),
},
summary: '更新代码仓库的信息',
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const name = ctx.query?.name;
const description = ctx.query?.description;
const license = ctx.query?.license;
const site = ctx.query?.site;
const topics = ctx.query?.topics;
if (!name) {
ctx.throw(400, '缺少参数 name');
}
if (!description) {
ctx.throw(400, '缺少参数 description');
}
const res = await cnb.repo.updateRepoInfo(name, { description, license, site, topics });
ctx.forward(res); ctx.forward(res);
}).addTo(app); }).addTo(app);

View File

@@ -0,0 +1,48 @@
import { useKey } from '@kevisual/context';
import { app } from '../../app.ts';
import z from 'zod';
app.route({
path: 'cnb',
key: 'get-assistant-url',
description: '获取cnb工作空间中部署的各个助手的访问地址',
middleware: ['auth'],
metadata: {
args: {
more: z.boolean().describe('需要更多信息')
}
}
}).define(async (ctx) => {
const uri = useKey('CNB_VSCODE_PROXY_URI') as string || '';
const base = {
base: uri,
link: uri.replace('{{port}}', '51515'),
kevisual: uri.replace('{{port}}', '51515'),
openclaw: uri.replace('{{port}}', '80'),
opencode: uri.replace('{{port}}', '100'),
openwebui: uri.replace('{{port}}', '200'),
note: uri.replace('{{port}}', '3000'),
uptime: uri.replace('{{port}}', '3001'),
immich: uri.replace('{{port}}', '2283'),
nocodb: uri.replace('{{port}}', '4000'),
openlist: uri.replace('{{port}}', '5244'),
xiaoyao: uri.replace('{{port}}', '5678'),
meilisearch: uri.replace('{{port}}', '7700'),
bark: uri.replace('{{port}}', '9111'),
vaultwarden: uri.replace('{{port}}', '8180'),
music: uri.replace('{{port}}', '8096'),
jellyfin: uri.replace('{{port}}', '8096'),
homeassistant: uri.replace('{{port}}', '8123'),
cloudreve: uri.replace('{{port}}', '5212'),
filebrowser: uri.replace('{{port}}', '8081'),
// newapi: uri.replace('{{port}}', '8080'),
vscode: useKey('CNB_VSCODE_WEB_URL') as string || '',
codeServer: uri.replace('{{port}}', '10000'),
gitea: uri.replace('{{port}}', '3000'),
calibre: uri.replace('{{port}}', '8083'),
searXNG: uri.replace('{{port}}', '8888'),
}
ctx.body = {
...base,
}
}).addTo(app);

View File

@@ -0,0 +1,43 @@
import { createSkill, tool } from '@kevisual/router';
import { app, cnbManager, notCNBCheck } from '../../app.ts';
// 启动工作空间
app.route({
path: 'cnb',
key: 'cloud-build',
description: '云端构建,参数 event, repo, branch, ref, config, env',
middleware: ['auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'cloud-build',
title: '云端构建',
summary: '在云端构建代码仓库,参数包括 event, repo, branch, ref, config, env',
args: {
env: tool.schema.any().optional().describe('构建环境变量,格式为 { "KEY": "VALUE" }'),
event: tool.schema.string().optional().describe('触发事件类型,例如 api_trigger_event'),
branch: tool.schema.string().optional().describe('分支名称,默认主分支'),
config: tool.schema.string().describe('构建config文件内容例如 cloudbuild.yaml对应的yml的内容'),
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
},
})
}
}).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo;
const branch = ctx.query?.branch || 'main';
const config = ctx.query?.config;
const event = ctx.query?.event || 'api_trigger_event';
const env = ctx.query?.env ?? {};
if (!repo) {
ctx.throw(400, '缺少参数 repo');
}
const res = await cnb.build.startBuild(repo, {
branch,
config,
event,
env,
});
ctx.forward(res);
}).addTo(app);

View File

@@ -1,15 +1,16 @@
import { createSkill, tool } from '@kevisual/router'; import { createSkill, tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts'; import { app, cnbManager, notCNBCheck } from '../../app.ts';
import z from 'zod'; import z from 'zod';
import './skills.ts'; import './skills.ts';
import './keep.ts'; import './keep.ts';
import './build.ts';
// 启动工作空间 // 启动工作空间
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'start-workspace', key: 'start-workspace',
description: '启动开发工作空间, 参数 repo', description: '启动开发工作空间, 参数 repo',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -24,6 +25,7 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo; const repo = ctx.query?.repo;
const branch = ctx.query?.branch; const branch = ctx.query?.branch;
const ref = ctx.query?.ref; const ref = ctx.query?.ref;
@@ -42,7 +44,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'list-workspace', key: 'list-workspace',
description: '获取cnb开发工作空间列表可选参数 status=running 获取运行中的环境', description: '获取cnb开发工作空间列表可选参数 status=running 获取运行中的环境',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -59,13 +61,14 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const { status = 'running', page, pageSize, slug, branch } = ctx.query || {}; const { status = 'running', page, pageSize, slug, branch } = ctx.query || {};
const res = await cnb.workspace.list({ const res = await cnb.workspace.list({
status: status as 'running' | 'closed' | undefined, status: status as 'running' | 'closed' | undefined,
page: page ?? 1, page: page ?? 1,
pageSize: pageSize ?? 100, pageSize: pageSize ?? 100,
}); });
ctx.forward({ code: 200, message: 'success', data: res }); ctx.forward(res);
}).addTo(app); }).addTo(app);
// 获取工作空间详情 // 获取工作空间详情
@@ -73,7 +76,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'get-workspace', key: 'get-workspace',
description: '获取工作空间详情,通过 repo 和 sn 获取', description: '获取工作空间详情,通过 repo 和 sn 获取',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -87,6 +90,7 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const repo = ctx.query?.repo; const repo = ctx.query?.repo;
const sn = ctx.query?.sn; const sn = ctx.query?.sn;
if (!repo) { if (!repo) {
@@ -96,7 +100,7 @@ app.route({
ctx.throw(400, '缺少参数 sn'); ctx.throw(400, '缺少参数 sn');
} }
const res = await cnb.workspace.getDetail(repo, sn); const res = await cnb.workspace.getDetail(repo, sn);
ctx.forward({ code: 200, message: 'success', data: res }); ctx.forward(res);
}).addTo(app); }).addTo(app);
// 删除工作空间 // 删除工作空间
@@ -104,7 +108,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'delete-workspace', key: 'delete-workspace',
description: '删除工作空间,通过 pipelineId 或 sn', description: '删除工作空间,通过 pipelineId 或 sn',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -119,6 +123,7 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const pipelineId = ctx.query?.pipelineId; const pipelineId = ctx.query?.pipelineId;
const sn = ctx.query?.sn; const sn = ctx.query?.sn;
const sns = ctx.query?.sns; const sns = ctx.query?.sns;
@@ -143,7 +148,7 @@ app.route({
path: 'cnb', path: 'cnb',
key: 'stop-workspace', key: 'stop-workspace',
description: '停止工作空间,通过 pipelineId 或 sn', description: '停止工作空间,通过 pipelineId 或 sn',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -157,12 +162,14 @@ app.route({
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const cnb = await cnbManager.getContext(ctx);
const pipelineId = ctx.query?.pipelineId; const pipelineId = ctx.query?.pipelineId;
const sn = ctx.query?.sn; const sn = ctx.query?.sn;
if (!pipelineId && !sn) { if (!pipelineId && !sn) {
ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个'); ctx.throw(400, 'pipelineId 和 sn 必须提供其中一个');
} }
const res = await cnb.workspace.stopWorkspace({ pipelineId, sn }); const res = await cnb.workspace.stopWorkspace({ pipelineId, sn });
ctx.forward({ code: 200, message: 'success', data: res }); ctx.forward(res);
}).addTo(app); }).addTo(app);

View File

@@ -1,214 +1,101 @@
import { createSkill, tool } from '@kevisual/router'; import { tool } from '@kevisual/router';
import { app, cnb } from '../../app.ts'; import { app, cnbManager, notCNBCheck } from '../../app.ts';
import { nanoid } from 'nanoid'; import { addKeepAliveData, KeepAliveData, removeKeepAliveData, createLiveData } from '../../../src/workspace/keep-file-live.ts';
import dayjs from 'dayjs'; import { useKey } from '@kevisual/context';
import { createKeepAlive } from '../../../src/keep.ts';
type AliveInfo = {
startTime: number;
updatedTime?: number;
KeepAlive: ReturnType<typeof createKeepAlive>;
id: string;// 6位唯一标识符
}
const keepAliveMap = new Map<string, AliveInfo>();
// 保持工作空间存活技能 // 保持工作空间存活技能
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'keep-workspace-alive', key: 'keep-workspace-alive',
description: '保持工作空间存活技能,参数wsUrl:工作空间访问URLcookie:访问工作空间所需的cookie', description: '保持工作空间存活技能参数repo:代码仓库路径,例如 user/repopipelineId:流水线ID例如 cnb-708-1ji9sog7o-001',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: [], tags: [],
...({ ...({
args: { args: {
wsUrl: tool.schema.string().describe('工作空间的访问URL'), repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
cookie: tool.schema.string().describe('访问工作空间所需的cookie') pipelineId: tool.schema.string().describe('流水线ID例如 cnb-708-1ji9sog7o-001'),
} }
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const wsUrl = ctx.query?.wsUrl as string;
const cookie = ctx.query?.cookie as string; const cnb = await cnbManager.getContext(ctx);
if (!wsUrl) { const repo = ctx.query?.repo as string;
ctx.throw(400, '缺少工作空间访问URL参数'); const pipelineId = ctx.query?.pipelineId as string;
if (notCNBCheck(ctx)) return;
if (!repo || !pipelineId) {
ctx.throw(400, '缺少参数 repo 或 pipelineId');
} }
if (!cookie) { const validCookie = await cnb.user.checkCookieValid()
ctx.throw(400, '缺少访问工作空间所需的cookie参数'); if (validCookie.code !== 200) {
ctx.throw(401, 'CNB_COOKIE 环境变量无效或已过期请重新登录获取新的cookie');
}
const res = await cnb.workspace.getWorkspaceCookie(repo, pipelineId);
if (res.code !== 200 || !res.data?.cookie) {
ctx.throw(500, `获取工作空间 Cookie 失败: ${res.message}`);
} }
// 检测是否已在运行(通过 wsUrl 遍历检查) // 添加保活数据
const existing = Array.from(keepAliveMap.values()).find(info => (info as AliveInfo).id && (info as any).KeepAlive?.wsUrl === wsUrl); const liveData = createLiveData({
if (existing) { repo,
ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中`, id: (existing as AliveInfo).id }; pipelineId,
return; cookie: res.data.cookie
}
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
const keep = createKeepAlive({
wsUrl,
cookie,
onConnect: () => {
console.log(`工作空间 ${wsUrl} 保持存活任务已连接`);
},
onMessage: (data) => {
// 可选:处理收到的消息
// console.log(`工作空间 ${wsUrl} 收到消息: ${data}`);
// 通过 wsUrl 找到对应的 id 并更新时间
for (const info of keepAliveMap.values()) {
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
info.updatedTime = Date.now();
break;
}
}
},
debug: true,
onExit: (code) => {
console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`);
// 通过 wsUrl 找到对应的 id 并删除
for (const [id, info] of keepAliveMap.entries()) {
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
keepAliveMap.delete(id);
break;
}
}
}
}); });
addKeepAliveData(liveData);
console.log('已添加 keep-alive 数据');
const id = nanoid(6).toLowerCase(); ctx.body = { content: `已启动保持工作空间 ${repo}/${pipelineId} 存活的任务`, data: liveData };
keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, id };
}).addTo(app);
// 获取保持工作空间存活任务列表技能
app.route({
path: 'cnb',
key: 'list-keep-alive-tasks',
description: '获取保持工作空间存活任务列表技能',
middleware: ['admin-auth'],
metadata: {
tags: [],
}
}).define(async (ctx) => {
const list = Array.from(keepAliveMap.entries()).map(([id, info]) => {
const now = Date.now();
const duration = Math.floor((now - info.startTime) / 60000); // 分钟
return {
id,
wsUrl: (info as any).KeepAlive?.wsUrl,
startTime: info.startTime,
startTimeStr: dayjs(info.startTime).format('YYYY-MM-DD HH:mm'),
updatedTime: info.updatedTime,
updatedTimeStr: dayjs(info.updatedTime).format('YYYY-MM-DD HH:mm'),
duration,
}
});
ctx.body = { list };
}).addTo(app); }).addTo(app);
// 停止保持工作空间存活技能 // 停止保持工作空间存活技能
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'stop-keep-workspace-alive', key: 'stop-keep-workspace-alive',
description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL或者id', description: '停止保持工作空间存活技能, 参数repo:代码仓库路径,例如 user/repopipelineId:流水线ID例如 cnb-708-1ji9sog7o-001',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: [], tags: [],
...({ ...({
args: { args: {
wsUrl: tool.schema.string().optional().describe('工作空间的访问URL'), repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
id: tool.schema.string().optional().describe('保持存活任务的唯一标识符'), pipelineId: tool.schema.string().describe('流水线ID例如 cnb-708-1ji9sog7o-001'),
} }
}) })
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const wsUrl = ctx.query?.wsUrl as string; if (notCNBCheck(ctx)) return;
const id = ctx.query?.id as string; const repo = ctx.query?.repo as string;
if (!wsUrl && !id) { const pipelineId = ctx.query?.pipelineId as string;
ctx.throw(400, '缺少工作空间访问URL参数或唯一标识符');
}
let targetId: string | undefined; if (!repo || !pipelineId) {
let wsUrlFound: string | undefined; ctx.throw(400, '缺少参数 repo 或 pipelineId');
if (id) {
const info = keepAliveMap.get(id);
if (info) {
targetId = id;
wsUrlFound = (info as any).KeepAlive?.wsUrl;
}
} else if (wsUrl) {
for (const [key, info] of keepAliveMap.entries()) {
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
targetId = key;
wsUrlFound = wsUrl;
break;
}
}
}
if (targetId) {
const keepAlive = keepAliveMap.get(targetId);
const endTime = Date.now();
const duration = endTime - keepAlive!.startTime;
keepAlive?.KeepAlive?.disconnect();
keepAliveMap.delete(targetId);
ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
} else {
ctx.body = { content: `没有找到对应的工作空间保持存活任务` };
} }
removeKeepAliveData(repo, pipelineId);
ctx.body = { content: `已停止保持工作空间 ${repo}/${pipelineId} 存活的任务` };
}).addTo(app); }).addTo(app);
app.route({ app.route({
path: 'cnb', path: 'cnb',
key: 'reset-keep-workspace-alive', key: 'keep-alive-current-workspace',
description: '对存活的工作空间startTime进行重置', description: '保持当前工作空间存活技能',
middleware: ['admin-auth'], middleware: ['auth'],
metadata: { metadata: {
tags: [], tags: ['opencode'],
skill: 'keep-alive-current-workspace',
title: '保持当前工作空间存活',
summary: '保持当前工作空间存活,防止被关闭或释放资源',
} }
}).define(async (ctx) => { }).define(async (ctx) => {
const now = Date.now(); if (notCNBCheck(ctx)) return;
for (const info of keepAliveMap.values()) { const pipelineId = useKey('CNB_PIPELINE_ID');
info.startTime = now; const repo = useKey('CNB_REPO_SLUG_LOWERCASE');
if (!pipelineId || !repo) {
ctx.throw(400, '当前环境缺少 CNB_PIPELINE_ID 或 CNB_REPO_SLUG_LOWERCASE 环境变量,无法保持工作空间存活');
} }
ctx.body = { content: `已重置所有存活工作空间的开始时间` }; const res = await app.run({ path: 'cnb', key: 'keep-workspace-alive', payload: { repo, pipelineId } }, ctx);
}).addTo(app); ctx.forward(res);
}).addTo(app);
app.route({
path: 'cnb',
key: 'clear-keep-workspace-alive',
description: '对存活的工作空间超过5小时的进行清理',
middleware: ['admin-auth'],
metadata: {
tags: [],
}
}).define(async (ctx) => {
const res = clearKeepAlive();
ctx.body = {
content: `已清理所有存活工作空间中超过5小时的任务` + (res.length ? `,清理项:${res.map(i => i.wsUrl).join(', ')}` : ''),
list: res
};
}).addTo(app);
const clearKeepAlive = () => {
const now = Date.now();
let clearedArr: { id: string; wsUrl: string }[] = [];
for (const [id, info] of keepAliveMap.entries()) {
if (now - info.startTime > FIVE_HOURS) {
console.log(`工作空间 ${(info as any).KeepAlive?.wsUrl} 超过5小时自动停止`);
info.KeepAlive?.disconnect?.();
keepAliveMap.delete(id);
clearedArr.push({ id, wsUrl: (info as any).KeepAlive?.wsUrl });
}
}
return clearedArr;
}
// 每5小时自动清理超时的keepAlive任务
const FIVE_HOURS = 5 * 60 * 60 * 1000;
setInterval(() => {
clearKeepAlive();
}, FIVE_HOURS);

View File

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

2
bin/index.js Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bun
import '../dist/cli.js';

2
bin/npc.js Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bun
import '../dist/npc.js';

View File

@@ -1,4 +1,7 @@
import { buildWithBun } from '@kevisual/code-builder' import { buildWithBun } from '@kevisual/code-builder'
await buildWithBun({ naming: 'opencode', entry: 'agent/opencode.ts', dts: true }); await buildWithBun({ naming: 'opencode', entry: 'agent/opencode.ts', dts: true });
await buildWithBun({ naming: 'keep', entry: 'src/keep.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: 'routes', entry: 'agent/index.ts', dts: true });
await buildWithBun({ naming: 'npc', entry: 'agent/npc.ts', dts: true });
await buildWithBun({ naming: 'cli', entry: 'agent/cli.ts', dts: true, target: 'node' });

302
bun.lock Normal file
View File

@@ -0,0 +1,302 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "@kevisual/cnb",
"dependencies": {
"@kevisual/query": "^0.0.53",
"@kevisual/router": "^0.1.2",
"@kevisual/use-config": "^1.0.30",
"@opencode-ai/sdk": "^1.2.27",
"es-toolkit": "^1.45.1",
"nanoid": "^5.1.7",
"unstorage": "^1.17.4",
"ws": "npm:@kevisual/ws",
"zod": "^4.3.6",
},
"devDependencies": {
"@ai-sdk/openai-compatible": "^2.0.35",
"@kevisual/ai": "^0.0.28",
"@kevisual/api": "^0.0.64",
"@kevisual/code-builder": "^0.0.6",
"@kevisual/context": "^0.0.8",
"@kevisual/dts": "^0.0.4",
"@kevisual/remote-app": "^0.0.7",
"@kevisual/types": "^0.0.12",
"@opencode-ai/plugin": "^1.2.27",
"@types/bun": "^1.3.10",
"@types/node": "^25.5.0",
"@types/ws": "^8.18.1",
"ai": "^6.0.116",
"commander": "^14.0.3",
"dayjs": "^1.11.20",
"dotenv": "^17.3.1",
},
},
},
"overrides": {
"zod": "^4.3.6",
},
"packages": {
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.58", "https://registry.npmmirror.com/@ai-sdk/anthropic/-/anthropic-3.0.58.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/53SACgmVukO4bkms4dpxpRlYhW8Ct6QZRe6sj1Pi5H00hYhxIrqfiLbZBGxkdRvjsBQeP/4TVGsXgH5rQeb8Q=="],
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.66", "https://registry.npmmirror.com/@ai-sdk/gateway/-/gateway-3.0.66.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A=="],
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.41", "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-3.0.41.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-IZ42A+FO+vuEQCVNqlnAPYQnnUpUfdJIwn1BEDOBywiEHa23fw7PahxVtlX9zm3/zMvTW4JKPzWyvAgDu+SQ2A=="],
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.35", "https://registry.npmmirror.com/@ai-sdk/openai-compatible/-/openai-compatible-2.0.35.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-g3wA57IAQFb+3j4YuFndgkUdXyRETZVvbfAWM+UX7bZSxA3xjes0v3XKgIdKdekPtDGsh4ZX2byHD0gJIMPfiA=="],
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.8.tgz", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.19.tgz", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
"@babel/code-frame": ["@babel/code-frame@7.29.0", "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@kevisual/ai": ["@kevisual/ai@0.0.28", "https://registry.npmmirror.com/@kevisual/ai/-/ai-0.0.28.tgz", { "dependencies": { "@ai-sdk/anthropic": "^3.0.58", "@ai-sdk/openai": "^3.0.41", "@ai-sdk/openai-compatible": "^2.0.35", "@kevisual/js-filter": "^0.0.6", "@kevisual/logger": "^0.0.4", "@kevisual/permission": "^0.0.4", "@kevisual/query": "^0.0.53", "ai": "^6.0.116", "zod": "^4.3.6" } }, "sha512-GLwCNXfopDvOj+hEZwEIwOV2/3VGd+TCPgBClaYuAv30KzhgehlCW05HPjBducSg+uPcdKacEzZsecHjo5fMUQ=="],
"@kevisual/api": ["@kevisual/api@0.0.64", "http://mirrors.tencent.com/npm/@kevisual/api/-/api-0.0.64.tgz", { "dependencies": { "@kevisual/context": "^0.0.8", "@kevisual/js-filter": "^0.0.6", "@kevisual/load": "^0.0.6", "@paralleldrive/cuid2": "^3.3.0", "es-toolkit": "^1.45.1", "eventemitter3": "^5.0.4", "fuse.js": "^7.1.0", "nanoid": "^5.1.6", "path-browserify-esm": "^1.0.6", "sonner": "^2.0.7", "spark-md5": "^3.0.2", "zustand": "^5.0.11" } }, "sha512-y7wP8ucvi/rflVGd6uJpvuEUTwI7wMef8+ITQzv4flg7a2pwWZYe/DT0TOyaqDAqKOTlXaVIdBeI15jXuUxIIg=="],
"@kevisual/code-builder": ["@kevisual/code-builder@0.0.6", "https://registry.npmmirror.com/@kevisual/code-builder/-/code-builder-0.0.6.tgz", { "bin": { "code-builder": "bin/code.js", "builder": "bin/code.js" } }, "sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw=="],
"@kevisual/context": ["@kevisual/context@0.0.8", "https://registry.npmmirror.com/@kevisual/context/-/context-0.0.8.tgz", {}, "sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA=="],
"@kevisual/dts": ["@kevisual/dts@0.0.4", "https://registry.npmmirror.com/@kevisual/dts/-/dts-0.0.4.tgz", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-typescript": "^12.3.0", "rollup": "^4.57.1", "rollup-plugin-dts": "^6.3.0", "tslib": "^2.8.1" }, "bin": { "dts": "bin/dts.mjs" } }, "sha512-FVUaH/0nyhbHWpEVjFTGP54PLMm4Hf06aqWLdHOYHNPIgr1aK1C26kOH7iumklGFGk9w93IGxj8Zxe5fap5N2A=="],
"@kevisual/js-filter": ["@kevisual/js-filter@0.0.6", "https://registry.npmmirror.com/@kevisual/js-filter/-/js-filter-0.0.6.tgz", {}, "sha512-FcbOsmS1inhwrfgXMM/XLFTGTHUxBCss32JEMYdEFWQDYCar5rN8cxD1W8FuKDTVRlpA+zBpQ/BE6XT4UaeljA=="],
"@kevisual/load": ["@kevisual/load@0.0.6", "https://registry.npmmirror.com/@kevisual/load/-/load-0.0.6.tgz", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA=="],
"@kevisual/logger": ["@kevisual/logger@0.0.4", "https://registry.npmmirror.com/@kevisual/logger/-/logger-0.0.4.tgz", {}, "sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw=="],
"@kevisual/permission": ["@kevisual/permission@0.0.4", "https://registry.npmmirror.com/@kevisual/permission/-/permission-0.0.4.tgz", {}, "sha512-zwBYPnT/z21W4q2wkklJrxvoYBYWG/+a3iXFDKqXQAnDOcxm/SU1f1N6FQb9KxGKl36/fclVlhxlxqszvKCenQ=="],
"@kevisual/query": ["@kevisual/query@0.0.53", "https://registry.npmmirror.com/@kevisual/query/-/query-0.0.53.tgz", {}, "sha512-PAhpCLBr0emz0lGNlTVHMbJiC5wrtGLbInPddRzgKE35fiyNt+SWSsUWABiD0DeNrLN/OxWyAFobt880Z/e5MQ=="],
"@kevisual/remote-app": ["@kevisual/remote-app@0.0.7", "http://mirrors.tencent.com/npm/@kevisual/remote-app/-/remote-app-0.0.7.tgz", {}, "sha512-d0P8uyxoMnmyT8x1J9XC9ecDBbqW+jOP0ZM5fCgQRDUhWw35V/MnbCD4hNG4b6EmvoiS6a/PBC7RC5JGm3wpCg=="],
"@kevisual/router": ["@kevisual/router@0.1.2", "http://mirrors.tencent.com/npm/@kevisual/router/-/router-0.1.2.tgz", { "dependencies": { "crypto-js": "^4.2.0", "es-toolkit": "^1.45.1", "zod": "^4.3.6" } }, "sha512-GLLJMZXtv3nUQKJXyE+vJFiCuntpuBc0VT8hMQyGvxwzqN8BY8rX6yS9TNDWhSXLwLYed8BJtG+azEONDjFCpw=="],
"@kevisual/types": ["@kevisual/types@0.0.12", "https://registry.npmmirror.com/@kevisual/types/-/types-0.0.12.tgz", {}, "sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q=="],
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "https://registry.npmmirror.com/@kevisual/use-config/-/use-config-1.0.30.tgz", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
"@noble/hashes": ["@noble/hashes@2.0.1", "https://registry.npmmirror.com/@noble/hashes/-/hashes-2.0.1.tgz", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.2.27", "http://mirrors.tencent.com/npm/@opencode-ai/plugin/-/plugin-1.2.27.tgz", { "dependencies": { "@opencode-ai/sdk": "1.2.27", "zod": "4.1.8" } }, "sha512-h+8Bw9v9nghMg7T+SUCTzxlIhOrsTqXW7U0HVLGQST5DjbN7uyCUM51roZWZ8LRjGxzbzFhvPnY1bj8i+ioZyw=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.2.27", "http://mirrors.tencent.com/npm/@opencode-ai/sdk/-/sdk-1.2.27.tgz", {}, "sha512-Wk0o/I+Fo+wE3zgvlJDs8Fb67KlKqX0PrV8dK5adSDkANq6r4Z25zXJg2iOir+a8ntg3rAcpel1OY4FV/TwRUA=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@paralleldrive/cuid2": ["@paralleldrive/cuid2@3.3.0", "https://registry.npmmirror.com/@paralleldrive/cuid2/-/cuid2-3.3.0.tgz", { "dependencies": { "@noble/hashes": "^2.0.1", "bignumber.js": "^9.3.1", "error-causes": "^3.0.2" }, "bin": { "cuid2": "bin/cuid2.js" } }, "sha512-OqiFvSOF0dBSesELYY2CAMa4YINvlLpvKOz/rv6NeZEqiyttlHgv98Juwv4Ch+GrEV7IZ8jfI2VcEoYUjXXCjw=="],
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.0", "https://registry.npmmirror.com/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ=="],
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
"@rollup/plugin-typescript": ["@rollup/plugin-typescript@12.3.0", "https://registry.npmmirror.com/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" }, "optionalPeers": ["rollup", "tslib"] }, "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@types/bun": ["@types/bun@1.3.10", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.10.tgz", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
"@types/estree": ["@types/estree@1.0.8", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/node": ["@types/node@25.5.0", "http://mirrors.tencent.com/npm/@types/node/-/node-25.5.0.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"@types/resolve": ["@types/resolve@1.20.2", "https://registry.npmmirror.com/@types/resolve/-/resolve-1.20.2.tgz", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
"@types/ws": ["@types/ws@8.18.1", "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"@vercel/oidc": ["@vercel/oidc@3.1.0", "https://registry.npmmirror.com/@vercel/oidc/-/oidc-3.1.0.tgz", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
"ai": ["ai@6.0.116", "https://registry.npmmirror.com/ai/-/ai-6.0.116.tgz", { "dependencies": { "@ai-sdk/gateway": "3.0.66", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA=="],
"anymatch": ["anymatch@3.1.3", "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"bignumber.js": ["bignumber.js@9.3.1", "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.1.tgz", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"bun-types": ["bun-types@1.3.10", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.10.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
"chokidar": ["chokidar@5.0.0", "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"commander": ["commander@14.0.3", "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
"commondir": ["commondir@1.0.1", "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
"cookie-es": ["cookie-es@1.2.2", "https://registry.npmmirror.com/cookie-es/-/cookie-es-1.2.2.tgz", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
"crossws": ["crossws@0.3.5", "https://registry.npmmirror.com/crossws/-/crossws-0.3.5.tgz", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
"crypto-js": ["crypto-js@4.2.0", "http://mirrors.tencent.com/npm/crypto-js/-/crypto-js-4.2.0.tgz", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
"dayjs": ["dayjs@1.11.20", "http://mirrors.tencent.com/npm/dayjs/-/dayjs-1.11.20.tgz", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="],
"deepmerge": ["deepmerge@4.3.1", "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"defu": ["defu@6.1.4", "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"destr": ["destr@2.0.5", "https://registry.npmmirror.com/destr/-/destr-2.0.5.tgz", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
"dotenv": ["dotenv@17.3.1", "https://registry.npmmirror.com/dotenv/-/dotenv-17.3.1.tgz", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"error-causes": ["error-causes@3.0.2", "https://registry.npmmirror.com/error-causes/-/error-causes-3.0.2.tgz", {}, "sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw=="],
"es-toolkit": ["es-toolkit@1.45.1", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.45.1.tgz", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
"estree-walker": ["estree-walker@2.0.2", "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"eventemitter3": ["eventemitter3@5.0.4", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"eventsource-parser": ["eventsource-parser@3.0.6", "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
"fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"fuse.js": ["fuse.js@7.1.0", "https://registry.npmmirror.com/fuse.js/-/fuse.js-7.1.0.tgz", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
"h3": ["h3@1.15.5", "https://registry.npmmirror.com/h3/-/h3-1.15.5.tgz", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg=="],
"hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "https://registry.npmmirror.com/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
"is-core-module": ["is-core-module@2.16.1", "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
"is-module": ["is-module@1.0.0", "https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
"is-reference": ["is-reference@1.2.1", "https://registry.npmmirror.com/is-reference/-/is-reference-1.2.1.tgz", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
"js-tokens": ["js-tokens@4.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"json-schema": ["json-schema@0.4.0", "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"lru-cache": ["lru-cache@11.2.6", "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.6.tgz", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"magic-string": ["magic-string@0.30.21", "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"nanoid": ["nanoid@5.1.7", "http://mirrors.tencent.com/npm/nanoid/-/nanoid-5.1.7.tgz", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-1.6.7.tgz", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
"node-mock-http": ["node-mock-http@1.0.4", "https://registry.npmmirror.com/node-mock-http/-/node-mock-http-1.0.4.tgz", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
"normalize-path": ["normalize-path@3.0.0", "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"ofetch": ["ofetch@1.5.1", "https://registry.npmmirror.com/ofetch/-/ofetch-1.5.1.tgz", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
"path-browserify-esm": ["path-browserify-esm@1.0.6", "https://registry.npmmirror.com/path-browserify-esm/-/path-browserify-esm-1.0.6.tgz", {}, "sha512-9nUwYvvu/yq1PYrUyYCihNWmpzacaRYF6gGbjLWErrZ4MRDWyfPN7RpE8E7tsw8eqBU/rr7mcoTXbS+Vih8uUA=="],
"path-parse": ["path-parse@1.0.7", "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"radix3": ["radix3@1.1.2", "https://registry.npmmirror.com/radix3/-/radix3-1.1.2.tgz", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"react": ["react@19.2.4", "https://registry.npmmirror.com/react/-/react-19.2.4.tgz", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react-dom": ["react-dom@19.2.4", "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.4.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"readdirp": ["readdirp@5.0.0", "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"resolve": ["resolve@1.22.11", "https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"rollup": ["rollup@4.57.1", "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
"rollup-plugin-dts": ["rollup-plugin-dts@6.3.0", "https://registry.npmmirror.com/rollup-plugin-dts/-/rollup-plugin-dts-6.3.0.tgz", { "dependencies": { "magic-string": "^0.30.21" }, "optionalDependencies": { "@babel/code-frame": "^7.27.1" }, "peerDependencies": { "rollup": "^3.29.4 || ^4", "typescript": "^4.5 || ^5.0" } }, "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA=="],
"scheduler": ["scheduler@0.27.0", "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"sonner": ["sonner@2.0.7", "https://registry.npmmirror.com/sonner/-/sonner-2.0.7.tgz", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
"spark-md5": ["spark-md5@3.0.2", "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz", {}, "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"ufo": ["ufo@1.6.3", "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
"uncrypto": ["uncrypto@0.1.3", "https://registry.npmmirror.com/uncrypto/-/uncrypto-0.1.3.tgz", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici-types": ["undici-types@7.18.2", "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"unstorage": ["unstorage@1.17.4", "https://registry.npmmirror.com/unstorage/-/unstorage-1.17.4.tgz", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
"ws": ["@kevisual/ws@8.19.0", "https://registry.npmmirror.com/@kevisual/ws/-/ws-8.19.0.tgz", {}, "sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw=="],
"zod": ["zod@4.3.6", "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zustand": ["zustand@5.0.11", "https://registry.npmmirror.com/zustand/-/zustand-5.0.11.tgz", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="],
"@types/ws/@types/node": ["@types/node@25.3.0", "https://registry.npmmirror.com/@types/node/-/node-25.3.0.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
"anymatch/picomatch": ["picomatch@2.3.1", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"bun-types/@types/node": ["@types/node@25.3.5", "https://registry.npmmirror.com/@types/node/-/node-25.3.5.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
}
}

13
keep.ts Normal file
View File

@@ -0,0 +1,13 @@
import { createKeepAlive } from "@kevisual/cnb/keep";
const config = {
"wss": "wss://cnb-tmm-1jhgl3i0m-001.cnb.space:443/stable-3c0b449c6e6e37b44a8a7938c0d8a3049926a64c?reconnectionToken=26ba6a08-1c57-41cc-8099-1f6e64863bf6&reconnection=false&skipWebSocketFrames=false",
"cookie": "orange:workspace:cookie-session:cnb-tmm-1jhgl3i0m-001=93d7bc9b-9ca0-4867-963d-1928ad3038c7",
"url": "https://cnb-tmm-1jhgl3i0m-001.cnb.space/?folder=/workspace"
}
createKeepAlive({
wsUrl: config.wss,
cookie: config.cookie,
debug: true,
});

1
mod.ts
View File

@@ -1 +0,0 @@
export * from './src/index.ts'

View File

@@ -1,35 +1,53 @@
{ {
"name": "@kevisual/cnb", "name": "@kevisual/cnb",
"version": "0.0.22", "version": "0.0.51",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"basename": "/root/cnb",
"app": {
"type": "system-app",
"entry": "./dist/routes.js",
"engine": "bun"
},
"scripts": { "scripts": {
"build": "bun bun.config.ts", "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",
"compile": "bun build --compile --minify agent/commander.ts --outfile=./dist/cnb ",
"pub": "ev pack -u -m false -c -p"
}, },
"keywords": [], "keywords": [],
"bin": {
"cnb": "bin/index.js",
"cloud": "bin/index.js",
"cloud-npc": "bin/npc.js"
},
"files": [ "files": [
"dist", "dist",
"src", "src",
"mod.ts",
"agent" "agent"
], ],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)", "author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT", "license": "MIT",
"packageManager": "pnpm@10.29.1", "packageManager": "pnpm@10.32.1",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@kevisual/ai": "^0.0.24", "@ai-sdk/openai-compatible": "^2.0.35",
"@kevisual/ai": "^0.0.28",
"@kevisual/api": "^0.0.64",
"@kevisual/code-builder": "^0.0.6", "@kevisual/code-builder": "^0.0.6",
"@kevisual/dts": "^0.0.3", "@kevisual/context": "^0.0.8",
"@kevisual/context": "^0.0.4", "@kevisual/dts": "^0.0.4",
"@kevisual/remote-app": "^0.0.7",
"@kevisual/types": "^0.0.12", "@kevisual/types": "^0.0.12",
"@opencode-ai/plugin": "^1.1.53", "@opencode-ai/plugin": "^1.2.27",
"@types/bun": "^1.3.8", "@types/bun": "^1.3.10",
"@types/node": "^25.2.2", "@types/node": "^25.5.0",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"dayjs": "^1.11.19", "ai": "^6.0.116",
"dotenv": "^17.2.4" "commander": "^14.0.3",
"dayjs": "^1.11.20",
"dotenv": "^17.3.1",
"zod": "^4.3.6"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -38,19 +56,21 @@
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
"dependencies": { "dependencies": {
"@kevisual/query": "^0.0.40", "@kevisual/query": "^0.0.53",
"@kevisual/router": "^0.0.70", "@kevisual/router": "^0.1.2",
"@kevisual/use-config": "^1.0.30", "@kevisual/use-config": "^1.0.30",
"es-toolkit": "^1.44.0", "@opencode-ai/sdk": "^1.2.27",
"nanoid": "^5.1.6", "es-toolkit": "^1.45.1",
"nanoid": "^5.1.7",
"unstorage": "^1.17.4", "unstorage": "^1.17.4",
"ws": "npm:@kevisual/ws", "ws": "npm:@kevisual/ws"
"zod": "^4.3.6"
}, },
"exports": { "exports": {
".": "./mod.ts", ".": "./src/index.ts",
"./opencode": "./dist/opencode.js", "./opencode": "./dist/opencode.js",
"./keep": "./dist/keep.js", "./keep": "./dist/keep.js",
"./keep.ts": "./src/keep.ts",
"./keep-file-live.ts": "./src/workspace/keep-file-live.ts",
"./routes": "./dist/routes.js", "./routes": "./dist/routes.js",
"./src/*": "./src/*", "./src/*": "./src/*",
"./agent/*": "./agent/*" "./agent/*": "./agent/*"

446
pnpm-lock.yaml generated
View File

@@ -12,17 +12,20 @@ importers:
.: .:
dependencies: dependencies:
'@kevisual/query': '@kevisual/query':
specifier: ^0.0.40 specifier: ^0.0.53
version: 0.0.40 version: 0.0.53
'@kevisual/router': '@kevisual/router':
specifier: ^0.0.70 specifier: ^0.1.1
version: 0.0.70 version: 0.1.1
'@kevisual/use-config': '@kevisual/use-config':
specifier: ^1.0.30 specifier: ^1.0.30
version: 1.0.30(dotenv@17.2.4) version: 1.0.30(dotenv@17.3.1)
'@opencode-ai/sdk':
specifier: ^1.2.25
version: 1.2.25
es-toolkit: es-toolkit:
specifier: ^1.44.0 specifier: ^1.45.1
version: 1.44.0 version: 1.45.1
nanoid: nanoid:
specifier: ^5.1.6 specifier: ^5.1.6
version: 5.1.6 version: 5.1.6
@@ -36,42 +39,91 @@ importers:
specifier: ^4.3.6 specifier: ^4.3.6
version: 4.3.6 version: 4.3.6
devDependencies: devDependencies:
'@ai-sdk/openai-compatible':
specifier: ^2.0.35
version: 2.0.35(zod@4.3.6)
'@kevisual/ai': '@kevisual/ai':
specifier: ^0.0.24 specifier: ^0.0.28
version: 0.0.24 version: 0.0.28
'@kevisual/api':
specifier: ^0.0.64
version: 0.0.64(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@kevisual/code-builder': '@kevisual/code-builder':
specifier: ^0.0.6 specifier: ^0.0.6
version: 0.0.6 version: 0.0.6
'@kevisual/context': '@kevisual/context':
specifier: ^0.0.4 specifier: ^0.0.8
version: 0.0.4 version: 0.0.8
'@kevisual/dts': '@kevisual/dts':
specifier: ^0.0.3 specifier: ^0.0.4
version: 0.0.3(typescript@5.9.3) version: 0.0.4(typescript@5.9.3)
'@kevisual/remote-app':
specifier: ^0.0.7
version: 0.0.7
'@kevisual/types': '@kevisual/types':
specifier: ^0.0.12 specifier: ^0.0.12
version: 0.0.12 version: 0.0.12
'@opencode-ai/plugin': '@opencode-ai/plugin':
specifier: ^1.1.53 specifier: ^1.2.25
version: 1.1.53 version: 1.2.25
'@types/bun': '@types/bun':
specifier: ^1.3.8 specifier: ^1.3.10
version: 1.3.8 version: 1.3.10
'@types/node': '@types/node':
specifier: ^25.2.2 specifier: ^25.5.0
version: 25.2.2 version: 25.5.0
'@types/ws': '@types/ws':
specifier: ^8.18.1 specifier: ^8.18.1
version: 8.18.1 version: 8.18.1
ai:
specifier: ^6.0.116
version: 6.0.116(zod@4.3.6)
commander:
specifier: ^14.0.3
version: 14.0.3
dayjs: dayjs:
specifier: ^1.11.19 specifier: ^1.11.20
version: 1.11.19 version: 1.11.20
dotenv: dotenv:
specifier: ^17.2.4 specifier: ^17.3.1
version: 17.2.4 version: 17.3.1
packages: packages:
'@ai-sdk/anthropic@3.0.58':
resolution: {integrity: sha512-/53SACgmVukO4bkms4dpxpRlYhW8Ct6QZRe6sj1Pi5H00hYhxIrqfiLbZBGxkdRvjsBQeP/4TVGsXgH5rQeb8Q==}
engines: {node: '>=18'}
peerDependencies:
zod: ^4.3.6
'@ai-sdk/gateway@3.0.66':
resolution: {integrity: sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A==}
engines: {node: '>=18'}
peerDependencies:
zod: ^4.3.6
'@ai-sdk/openai-compatible@2.0.35':
resolution: {integrity: sha512-g3wA57IAQFb+3j4YuFndgkUdXyRETZVvbfAWM+UX7bZSxA3xjes0v3XKgIdKdekPtDGsh4ZX2byHD0gJIMPfiA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^4.3.6
'@ai-sdk/openai@3.0.41':
resolution: {integrity: sha512-IZ42A+FO+vuEQCVNqlnAPYQnnUpUfdJIwn1BEDOBywiEHa23fw7PahxVtlX9zm3/zMvTW4JKPzWyvAgDu+SQ2A==}
engines: {node: '>=18'}
peerDependencies:
zod: ^4.3.6
'@ai-sdk/provider-utils@4.0.19':
resolution: {integrity: sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^4.3.6
'@ai-sdk/provider@3.0.8':
resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==}
engines: {node: '>=18'}
'@babel/code-frame@7.29.0': '@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -83,37 +135,43 @@ packages:
'@jridgewell/sourcemap-codec@1.5.5': '@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@kevisual/ai@0.0.24': '@kevisual/ai@0.0.28':
resolution: {integrity: sha512-7jvZk1/L//VIClK7usuNgN4ZA9Etgbooka1Sj5quE/0UywR+NNnwqXVZ89Y1fBhI1TkhauDsdJBAtcQ7r/vbVw==} resolution: {integrity: sha512-GLwCNXfopDvOj+hEZwEIwOV2/3VGd+TCPgBClaYuAv30KzhgehlCW05HPjBducSg+uPcdKacEzZsecHjo5fMUQ==}
'@kevisual/api@0.0.64':
resolution: {integrity: sha512-y7wP8ucvi/rflVGd6uJpvuEUTwI7wMef8+ITQzv4flg7a2pwWZYe/DT0TOyaqDAqKOTlXaVIdBeI15jXuUxIIg==}
'@kevisual/code-builder@0.0.6': '@kevisual/code-builder@0.0.6':
resolution: {integrity: sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw==} resolution: {integrity: sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw==}
hasBin: true hasBin: true
'@kevisual/context@0.0.4': '@kevisual/context@0.0.8':
resolution: {integrity: sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ==} resolution: {integrity: sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA==}
'@kevisual/dts@0.0.3': '@kevisual/dts@0.0.4':
resolution: {integrity: sha512-4T/m2LqhtwWEW+lWmg7jLxKFW7VtIAftsWFDDZvh10bZunqFf8iXxChHcVSQWikghJb4cq1IkWzPkvc2l+Asdw==} resolution: {integrity: sha512-FVUaH/0nyhbHWpEVjFTGP54PLMm4Hf06aqWLdHOYHNPIgr1aK1C26kOH7iumklGFGk9w93IGxj8Zxe5fap5N2A==}
hasBin: true hasBin: true
'@kevisual/js-filter@0.0.6':
resolution: {integrity: sha512-FcbOsmS1inhwrfgXMM/XLFTGTHUxBCss32JEMYdEFWQDYCar5rN8cxD1W8FuKDTVRlpA+zBpQ/BE6XT4UaeljA==}
'@kevisual/load@0.0.6': '@kevisual/load@0.0.6':
resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==} resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==}
'@kevisual/logger@0.0.4': '@kevisual/logger@0.0.4':
resolution: {integrity: sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw==} resolution: {integrity: sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw==}
'@kevisual/permission@0.0.3': '@kevisual/permission@0.0.4':
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==} resolution: {integrity: sha512-zwBYPnT/z21W4q2wkklJrxvoYBYWG/+a3iXFDKqXQAnDOcxm/SU1f1N6FQb9KxGKl36/fclVlhxlxqszvKCenQ==}
'@kevisual/query@0.0.38': '@kevisual/query@0.0.53':
resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==} resolution: {integrity: sha512-PAhpCLBr0emz0lGNlTVHMbJiC5wrtGLbInPddRzgKE35fiyNt+SWSsUWABiD0DeNrLN/OxWyAFobt880Z/e5MQ==}
'@kevisual/query@0.0.40': '@kevisual/remote-app@0.0.7':
resolution: {integrity: sha512-7m5BgDzd01m51hCHUId6ugQHdwgrLTb6fI7DSuMY17VjWb0+zGnkYmvRBqkTXzoIjjYbP5iwtRnrooEoToQfhg==} resolution: {integrity: sha512-d0P8uyxoMnmyT8x1J9XC9ecDBbqW+jOP0ZM5fCgQRDUhWw35V/MnbCD4hNG4b6EmvoiS6a/PBC7RC5JGm3wpCg==}
'@kevisual/router@0.0.70': '@kevisual/router@0.1.1':
resolution: {integrity: sha512-vXlIj9jRufhcIfeuPWemjSI+dxdzSmIBq5eRxQzqEfAJ7k+mBPhoI4KxH8vHnwyL30bqm8EdODL/p6Wg8uBw3g==} resolution: {integrity: sha512-+uaJc+Bf/T1mfxyfy9PmwuxJGPOLhVqrmsli2xUPqkkFvizrFIGB1vBTITuo5XP/FnwGqxgbjsitG57AMubm3w==}
'@kevisual/types@0.0.12': '@kevisual/types@0.0.12':
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==} resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
@@ -127,14 +185,26 @@ packages:
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==} resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
'@opencode-ai/plugin@1.1.53': '@noble/hashes@2.0.1':
resolution: {integrity: sha512-9ye7Wz2kESgt02AUDaMea4hXxj6XhWwKAG8NwFhrw09Ux54bGaMJFt1eIS8QQGIMaD+Lp11X4QdyEg96etEBJw==} resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==}
engines: {node: '>= 20.19.0'}
'@opencode-ai/sdk@1.1.53': '@opencode-ai/plugin@1.2.25':
resolution: {integrity: sha512-RUIVnPOP1CyyU32FrOOYuE7Ge51lOBuhaFp2NSX98ncApT7ffoNetmwzqrhOiJQgZB1KrbCHLYOCK6AZfacxag==} resolution: {integrity: sha512-IQnjkcN7cvI/zoiDNx1d2qnGzn5BR/Bu95Kq05/vdd8oX4ARgYkqfaJgKkNSpjUVoNBKHTd8m9q1TtzuKlyGUg==}
'@rollup/plugin-commonjs@28.0.9': '@opencode-ai/sdk@1.2.25':
resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==} resolution: {integrity: sha512-ikuGWob48OM7LTgfXFqGOZKVOqh50FEjvtIBhXGhGowJhifmjZ+xuq/ypP8nPjTwUX73pbu1C3X9ZBWVkCN9mA==}
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
'@paralleldrive/cuid2@3.3.0':
resolution: {integrity: sha512-OqiFvSOF0dBSesELYY2CAMa4YINvlLpvKOz/rv6NeZEqiyttlHgv98Juwv4Ch+GrEV7IZ8jfI2VcEoYUjXXCjw==}
hasBin: true
'@rollup/plugin-commonjs@29.0.0':
resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==}
engines: {node: '>=16.0.0 || 14 >= 14.17'} engines: {node: '>=16.0.0 || 14 >= 14.17'}
peerDependencies: peerDependencies:
rollup: ^2.68.0||^3.0.0||^4.0.0 rollup: ^2.68.0||^3.0.0||^4.0.0
@@ -311,14 +381,17 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@types/bun@1.3.8': '@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA==} resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@types/bun@1.3.10':
resolution: {integrity: sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ==}
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/node@25.2.2': '@types/node@25.5.0':
resolution: {integrity: sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==} resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
'@types/resolve@1.20.2': '@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@@ -326,17 +399,34 @@ packages:
'@types/ws@8.18.1': '@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@vercel/oidc@3.1.0':
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
engines: {node: '>= 20'}
ai@6.0.116:
resolution: {integrity: sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^4.3.6
anymatch@3.1.3: anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
bun-types@1.3.8: bignumber.js@9.3.1:
resolution: {integrity: sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q==} resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
bun-types@1.3.10:
resolution: {integrity: sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg==}
chokidar@5.0.0: chokidar@5.0.0:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'} engines: {node: '>= 20.19.0'}
commander@14.0.3:
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
engines: {node: '>=20'}
commondir@1.0.1: commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
@@ -346,8 +436,8 @@ packages:
crossws@0.3.5: crossws@0.3.5:
resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
dayjs@1.11.19: dayjs@1.11.20:
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
deepmerge@4.3.1: deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
@@ -359,12 +449,15 @@ packages:
destr@2.0.5: destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
dotenv@17.2.4: dotenv@17.3.1:
resolution: {integrity: sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==} resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
engines: {node: '>=12'} engines: {node: '>=12'}
es-toolkit@1.44.0: error-causes@3.0.2:
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} resolution: {integrity: sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw==}
es-toolkit@1.45.1:
resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==}
estree-walker@2.0.2: estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
@@ -372,6 +465,13 @@ packages:
eventemitter3@5.0.1: eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
eventemitter3@5.0.4:
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
eventsource-parser@3.0.6:
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
engines: {node: '>=18.0.0'}
fdir@6.5.0: fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -389,6 +489,10 @@ packages:
function-bind@1.1.2: function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
fuse.js@7.1.0:
resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==}
engines: {node: '>=10'}
h3@1.15.5: h3@1.15.5:
resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}
@@ -412,6 +516,9 @@ packages:
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
lru-cache@11.2.5: lru-cache@11.2.5:
resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==}
engines: {node: 20 || >=22} engines: {node: 20 || >=22}
@@ -437,6 +544,9 @@ packages:
ofetch@1.5.1: ofetch@1.5.1:
resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
path-browserify-esm@1.0.6:
resolution: {integrity: sha512-9nUwYvvu/yq1PYrUyYCihNWmpzacaRYF6gGbjLWErrZ4MRDWyfPN7RpE8E7tsw8eqBU/rr7mcoTXbS+Vih8uUA==}
path-parse@1.0.7: path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@@ -454,6 +564,15 @@ packages:
radix3@1.1.2: radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
react-dom@19.2.4:
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies:
react: ^19.2.4
react@19.2.4:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
readdirp@5.0.0: readdirp@5.0.0:
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
engines: {node: '>= 20.19.0'} engines: {node: '>= 20.19.0'}
@@ -475,6 +594,18 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
sonner@2.0.7:
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
spark-md5@3.0.2:
resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
supports-preserve-symlinks-flag@1.0.0: supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -493,8 +624,8 @@ packages:
uncrypto@0.1.3: uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
undici-types@7.16.0: undici-types@7.18.2:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
unstorage@1.17.4: unstorage@1.17.4:
resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==}
@@ -561,8 +692,62 @@ packages:
zod@4.3.6: zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
zustand@5.0.11:
resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots: snapshots:
'@ai-sdk/anthropic@3.0.58(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.19(zod@4.3.6)
zod: 4.3.6
'@ai-sdk/gateway@3.0.66(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.19(zod@4.3.6)
'@vercel/oidc': 3.1.0
zod: 4.3.6
'@ai-sdk/openai-compatible@2.0.35(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.19(zod@4.3.6)
zod: 4.3.6
'@ai-sdk/openai@3.0.41(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.19(zod@4.3.6)
zod: 4.3.6
'@ai-sdk/provider-utils@4.0.19(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 3.0.8
'@standard-schema/spec': 1.1.0
eventsource-parser: 3.0.6
zod: 4.3.6
'@ai-sdk/provider@3.0.8':
dependencies:
json-schema: 0.4.0
'@babel/code-frame@7.29.0': '@babel/code-frame@7.29.0':
dependencies: dependencies:
'@babel/helper-validator-identifier': 7.28.5 '@babel/helper-validator-identifier': 7.28.5
@@ -575,19 +760,46 @@ snapshots:
'@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/sourcemap-codec@1.5.5': {}
'@kevisual/ai@0.0.24': '@kevisual/ai@0.0.28':
dependencies: dependencies:
'@ai-sdk/anthropic': 3.0.58(zod@4.3.6)
'@ai-sdk/openai': 3.0.41(zod@4.3.6)
'@ai-sdk/openai-compatible': 2.0.35(zod@4.3.6)
'@kevisual/js-filter': 0.0.6
'@kevisual/logger': 0.0.4 '@kevisual/logger': 0.0.4
'@kevisual/permission': 0.0.3 '@kevisual/permission': 0.0.4
'@kevisual/query': 0.0.38 '@kevisual/query': 0.0.53
ai: 6.0.116(zod@4.3.6)
zod: 4.3.6
'@kevisual/api@0.0.64(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@kevisual/context': 0.0.8
'@kevisual/js-filter': 0.0.6
'@kevisual/load': 0.0.6
'@paralleldrive/cuid2': 3.3.0
es-toolkit: 1.45.1
eventemitter3: 5.0.4
fuse.js: 7.1.0
nanoid: 5.1.6
path-browserify-esm: 1.0.6
sonner: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
spark-md5: 3.0.2
zustand: 5.0.11(react@19.2.4)
transitivePeerDependencies:
- '@types/react'
- immer
- react
- react-dom
- use-sync-external-store
'@kevisual/code-builder@0.0.6': {} '@kevisual/code-builder@0.0.6': {}
'@kevisual/context@0.0.4': {} '@kevisual/context@0.0.8': {}
'@kevisual/dts@0.0.3(typescript@5.9.3)': '@kevisual/dts@0.0.4(typescript@5.9.3)':
dependencies: dependencies:
'@rollup/plugin-commonjs': 28.0.9(rollup@4.57.1) '@rollup/plugin-commonjs': 29.0.0(rollup@4.57.1)
'@rollup/plugin-node-resolve': 16.0.3(rollup@4.57.1) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.57.1)
'@rollup/plugin-typescript': 12.3.0(rollup@4.57.1)(tslib@2.8.1)(typescript@5.9.3) '@rollup/plugin-typescript': 12.3.0(rollup@4.57.1)(tslib@2.8.1)(typescript@5.9.3)
rollup: 4.57.1 rollup: 4.57.1
@@ -596,43 +808,51 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
'@kevisual/js-filter@0.0.6': {}
'@kevisual/load@0.0.6': '@kevisual/load@0.0.6':
dependencies: dependencies:
eventemitter3: 5.0.1 eventemitter3: 5.0.1
'@kevisual/logger@0.0.4': {} '@kevisual/logger@0.0.4': {}
'@kevisual/permission@0.0.3': {} '@kevisual/permission@0.0.4': {}
'@kevisual/query@0.0.38': '@kevisual/query@0.0.53': {}
dependencies:
tslib: 2.8.1
'@kevisual/query@0.0.40': '@kevisual/remote-app@0.0.7': {}
dependencies:
tslib: 2.8.1
'@kevisual/router@0.0.70': '@kevisual/router@0.1.1':
dependencies: dependencies:
es-toolkit: 1.44.0 es-toolkit: 1.45.1
'@kevisual/types@0.0.12': {} '@kevisual/types@0.0.12': {}
'@kevisual/use-config@1.0.30(dotenv@17.2.4)': '@kevisual/use-config@1.0.30(dotenv@17.3.1)':
dependencies: dependencies:
'@kevisual/load': 0.0.6 '@kevisual/load': 0.0.6
dotenv: 17.2.4 dotenv: 17.3.1
'@kevisual/ws@8.19.0': {} '@kevisual/ws@8.19.0': {}
'@opencode-ai/plugin@1.1.53': '@noble/hashes@2.0.1': {}
'@opencode-ai/plugin@1.2.25':
dependencies: dependencies:
'@opencode-ai/sdk': 1.1.53 '@opencode-ai/sdk': 1.2.25
zod: 4.3.6 zod: 4.3.6
'@opencode-ai/sdk@1.1.53': {} '@opencode-ai/sdk@1.2.25': {}
'@rollup/plugin-commonjs@28.0.9(rollup@4.57.1)': '@opentelemetry/api@1.9.0': {}
'@paralleldrive/cuid2@3.3.0':
dependencies:
'@noble/hashes': 2.0.1
bignumber.js: 9.3.1
error-causes: 3.0.2
'@rollup/plugin-commonjs@29.0.0(rollup@4.57.1)':
dependencies: dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.57.1) '@rollup/pluginutils': 5.3.0(rollup@4.57.1)
commondir: 1.0.1 commondir: 1.0.1
@@ -746,35 +966,51 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.57.1': '@rollup/rollup-win32-x64-msvc@4.57.1':
optional: true optional: true
'@types/bun@1.3.8': '@standard-schema/spec@1.1.0': {}
'@types/bun@1.3.10':
dependencies: dependencies:
bun-types: 1.3.8 bun-types: 1.3.10
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/node@25.2.2': '@types/node@25.5.0':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.18.2
'@types/resolve@1.20.2': {} '@types/resolve@1.20.2': {}
'@types/ws@8.18.1': '@types/ws@8.18.1':
dependencies: dependencies:
'@types/node': 25.2.2 '@types/node': 25.5.0
'@vercel/oidc@3.1.0': {}
ai@6.0.116(zod@4.3.6):
dependencies:
'@ai-sdk/gateway': 3.0.66(zod@4.3.6)
'@ai-sdk/provider': 3.0.8
'@ai-sdk/provider-utils': 4.0.19(zod@4.3.6)
'@opentelemetry/api': 1.9.0
zod: 4.3.6
anymatch@3.1.3: anymatch@3.1.3:
dependencies: dependencies:
normalize-path: 3.0.0 normalize-path: 3.0.0
picomatch: 2.3.1 picomatch: 2.3.1
bun-types@1.3.8: bignumber.js@9.3.1: {}
bun-types@1.3.10:
dependencies: dependencies:
'@types/node': 25.2.2 '@types/node': 25.5.0
chokidar@5.0.0: chokidar@5.0.0:
dependencies: dependencies:
readdirp: 5.0.0 readdirp: 5.0.0
commander@14.0.3: {}
commondir@1.0.1: {} commondir@1.0.1: {}
cookie-es@1.2.2: {} cookie-es@1.2.2: {}
@@ -783,7 +1019,7 @@ snapshots:
dependencies: dependencies:
uncrypto: 0.1.3 uncrypto: 0.1.3
dayjs@1.11.19: {} dayjs@1.11.20: {}
deepmerge@4.3.1: {} deepmerge@4.3.1: {}
@@ -791,14 +1027,20 @@ snapshots:
destr@2.0.5: {} destr@2.0.5: {}
dotenv@17.2.4: {} dotenv@17.3.1: {}
es-toolkit@1.44.0: {} error-causes@3.0.2: {}
es-toolkit@1.45.1: {}
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
eventemitter3@5.0.1: {} eventemitter3@5.0.1: {}
eventemitter3@5.0.4: {}
eventsource-parser@3.0.6: {}
fdir@6.5.0(picomatch@4.0.3): fdir@6.5.0(picomatch@4.0.3):
optionalDependencies: optionalDependencies:
picomatch: 4.0.3 picomatch: 4.0.3
@@ -808,6 +1050,8 @@ snapshots:
function-bind@1.1.2: {} function-bind@1.1.2: {}
fuse.js@7.1.0: {}
h3@1.15.5: h3@1.15.5:
dependencies: dependencies:
cookie-es: 1.2.2 cookie-es: 1.2.2
@@ -839,6 +1083,8 @@ snapshots:
js-tokens@4.0.0: js-tokens@4.0.0:
optional: true optional: true
json-schema@0.4.0: {}
lru-cache@11.2.5: {} lru-cache@11.2.5: {}
magic-string@0.30.21: magic-string@0.30.21:
@@ -859,6 +1105,8 @@ snapshots:
node-fetch-native: 1.6.7 node-fetch-native: 1.6.7
ufo: 1.6.3 ufo: 1.6.3
path-browserify-esm@1.0.6: {}
path-parse@1.0.7: {} path-parse@1.0.7: {}
picocolors@1.1.1: picocolors@1.1.1:
@@ -870,6 +1118,13 @@ snapshots:
radix3@1.1.2: {} radix3@1.1.2: {}
react-dom@19.2.4(react@19.2.4):
dependencies:
react: 19.2.4
scheduler: 0.27.0
react@19.2.4: {}
readdirp@5.0.0: {} readdirp@5.0.0: {}
resolve@1.22.11: resolve@1.22.11:
@@ -917,6 +1172,15 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.57.1 '@rollup/rollup-win32-x64-msvc': 4.57.1
fsevents: 2.3.3 fsevents: 2.3.3
scheduler@0.27.0: {}
sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
spark-md5@3.0.2: {}
supports-preserve-symlinks-flag@1.0.0: {} supports-preserve-symlinks-flag@1.0.0: {}
tslib@2.8.1: {} tslib@2.8.1: {}
@@ -927,7 +1191,7 @@ snapshots:
uncrypto@0.1.3: {} uncrypto@0.1.3: {}
undici-types@7.16.0: {} undici-types@7.18.2: {}
unstorage@1.17.4: unstorage@1.17.4:
dependencies: dependencies:
@@ -941,3 +1205,7 @@ snapshots:
ufo: 1.6.3 ufo: 1.6.3
zod@4.3.6: {} zod@4.3.6: {}
zustand@5.0.11(react@19.2.4):
optionalDependencies:
react: 19.2.4

View File

@@ -1,5 +0,0 @@
packages:
- web
onlyBuiltDependencies:
- esbuild

View File

@@ -1,5 +1,13 @@
# cnb.cool 能做什么 # cnb.cool 能做什么
所有的代码仓库,只基于一个准则 `group/repo`
## 环境变量
```sh
CNB_API_KEY
CNB_COOKIE
```
## 简介 ## 简介
纯粹调用api的模式去使用cnb.cool自动化方案。 纯粹调用api的模式去使用cnb.cool自动化方案。

View File

@@ -67,4 +67,17 @@ export * from './user/index.ts'
export * from './build/index.ts' export * from './build/index.ts'
export * from './issue/index.ts' export * from './issue/index.ts'
export * from './mission/index.ts' export * from './mission/index.ts'
export * from './ai/index.ts' export * from './ai/index.ts'
export const getCNBVersion = () => {
const url = 'https://cnb.cool/api/version';
// {"version":"1.18.8-2e3e01f0-20260309","commitID":"2e3e01f0","hash":"897b088418dccd05"}
return fetch(url).then(res => res.json()) as Promise<VersionInfo>;
}
type VersionInfo = {
version: string;
commitID: string;
hash: string;
}
export * from './issue/npc/env.ts'

View File

@@ -1,15 +1,27 @@
import { CNBCore, CNBCoreOptions, RequestOptions, Result } from "../cnb-core.ts"; import { CNBCore, CNBCoreOptions, RequestOptions, Result } from "../cnb-core.ts";
import { extractAliveInfo } from "./issue-alive.ts";
import { useNPCEnv, useCommentEnv, usePullRequestEnv, useRepoInfoEnv } from "./npc/env.ts";
export type IssueAssignee = { export type IssueAssignee = {
nickname: string; nickname: string;
username: string; username: string;
}; };
export type IssueLabel = { export type IssueLabel = {
color: string; color: string;
description: string; description: string;
id: string; id: string;
name: string; name: string;
creator?: {
username: string;
nickname: string;
email: string;
is_npc: boolean;
};
applied_by?: {
username: string;
nickname: string;
email: string;
is_npc: boolean;
}
}; };
export type IssueState = 'open' | 'closed'; export type IssueState = 'open' | 'closed';
@@ -21,6 +33,26 @@ export type IssueAuthor = {
ended_at: string; ended_at: string;
}; };
export type IssueCommentUser = {
username: string;
nickname: string;
avatar: string;
is_npc: boolean;
};
export type IssueCommentReaction = {
// 根据实际返回数据补充
};
export type IssueComment = {
id: string;
body: string;
author: IssueCommentUser;
reactions: IssueCommentReaction[];
created_at: string;
updated_at: string;
};
export type IssueItem = { export type IssueItem = {
assignees: IssueAssignee[]; assignees: IssueAssignee[];
author: IssueAuthor; author: IssueAuthor;
@@ -41,14 +73,14 @@ export class Issue extends CNBCore {
super(options); super(options);
} }
createIssue(repo: string, data: Partial<IssueItem>): Promise<any> { createIssue(repo: string, data: Partial<IssueItem>): Promise<Result<IssueItem>> {
const url = `/${repo}/-/issues`; const url = `/${repo}/-/issues`;
let postData = { let postData = {
...data, ...data,
}; };
return this.post({ url, data: postData }); return this.post({ url, data: postData });
} }
updateIssue(repo: string, issueNumber: string | number, data: Partial<IssueItem>): Promise<any> { updateIssue(repo: string, issueNumber: string | number, data: Partial<IssueItem>): Promise<Result<IssueItem>> {
const url = `/${repo}/-/issues/${issueNumber}`; const url = `/${repo}/-/issues/${issueNumber}`;
let postData = { let postData = {
...data, ...data,
@@ -75,10 +107,34 @@ export class Issue extends CNBCore {
} }
}); });
} }
getCommentList(repo: string, issueNumber: string | number): Promise<any> { getCommentList(repo: string, issueNumber: string | number, params?: { page?: number; page_size?: number }): Promise<Result<IssueComment[]>> {
const url = `/${repo}/-/issues/${issueNumber}/comments`; const url = `/${repo}/-/issues/${issueNumber}/comments`;
return this.get({ return this.get({
url, url,
params,
});
}
getComment(repo: string, issueNumber: string | number, commentId: string | number): Promise<Result<IssueComment>> {
const url = `/${repo}/-/issues/${issueNumber}/comments/${commentId}`;
return this.get({
url,
});
}
createComment(repo: string, issueNumber: string | number, body: string): Promise<Result<IssueComment>> {
const url = `/${repo}/-/issues/${issueNumber}/comments`;
return this.post({
url,
data: { body },
});
}
updateComment(repo: string, issueNumber: string | number, commentId: string | number, body: string): Promise<Result<IssueComment>> {
const url = `/${repo}/-/issues/${issueNumber}/comments/${commentId}`;
return this.patch({
url,
data: { body },
}); });
} }
setIssueProperty(repo: string, issueNumber: string | number, properties: { [key: string]: any }[]): Promise<any> { setIssueProperty(repo: string, issueNumber: string | number, properties: { [key: string]: any }[]): Promise<any> {
@@ -88,6 +144,63 @@ export class Issue extends CNBCore {
}; };
return this.post({ url, data: postData }); return this.post({ url, data: postData });
} }
/**
* 获取alive issue的元数据
* @param repo
* @param issueNumber
* @returns
*/
async getAliveMetadata(repo: string, issueNumber: string | number): Promise<Result<AliveMetadata>> {
const url = this.hackURL + `/${repo}/-/issues/${issueNumber}`;
const resHtml = await this.get({
url,
useCookie: true,
headers: {
'Content-Type': 'text/html; charset=utf-8',
Accept: "text/html; charset=utf-8",
}
});
if (resHtml.code !== 200) {
return resHtml;
}
const html = resHtml.data as string;
const { aliveSessionID, aliveChannelID } = extractAliveInfo(html);
if (!aliveSessionID || !aliveChannelID) {
return {
code: 500,
message: 'Failed to extract alive metadata',
data: null,
};
}
const off = Date.now();
return {
code: 200,
message: 'success',
data: {
aliveSessionID,
aliveChannelID,
domain: 'alive.cnb.cool',
subscriptions: [
{ event: 'subscribe', data: { id: `${aliveChannelID}--issue:info-update`, off } },
{ event: 'subscribe', data: { id: `${aliveChannelID}--issue:add-comment`, off } },
{ event: 'subscribe', data: { id: `${aliveChannelID}--issue:comment-update`, off } },
{ event: 'subscribe', data: { id: `${aliveChannelID}--issue:invisible`, off } },
],
},
};
}
useNPCEnv() {
return useNPCEnv();
}
useCommentEnv() {
return useCommentEnv();
}
usePullRequestEnv() {
return usePullRequestEnv();
}
useRepoInfoEnv() {
return useRepoInfoEnv();
}
} }
type GetListParams = { type GetListParams = {
@@ -119,4 +232,17 @@ type GetListParams = {
updated_time_begin?: string; updated_time_begin?: string;
/** 问题更新时间过滤-结束,例如: 2022-01-31 */ /** 问题更新时间过滤-结束,例如: 2022-01-31 */
updated_time_end?: string; updated_time_end?: string;
}
export type AliveMetadata = {
aliveSessionID: string;
aliveChannelID: string;
domain: string;
subscriptions: {
event: 'subscribe',
data: {
id: string;
off: number;
}
}[]
} }

15
src/issue/issue-alive.ts Normal file
View File

@@ -0,0 +1,15 @@
export function extractAliveInfo(html: string): { aliveSessionID: string | null; aliveChannelID: string | null } {
const match = html.match(/<script[^>]*id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);
if (!match || !match[1]) {
return { aliveSessionID: null, aliveChannelID: null };
}
try {
const data = JSON.parse(match[1]);
const aliveSessionID = data?.props?.pageProps?.aliveSessionID ?? null;
const aliveChannelID = data?.props?.pageProps?.aliveChannelID ?? null;
return { aliveSessionID, aliveChannelID };
} catch {
return { aliveSessionID: null, aliveChannelID: null };
}
}

View File

@@ -0,0 +1,24 @@
```js
fetch("wss://alive.cnb.cool/?id=26354bd0-3e00-4869-93c9-b687f19d96c1", {
"headers": {
"accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"cache-control": "no-cache",
"pragma": "no-cache",
"sec-websocket-extensions": "permessage-deflate; client_max_window_bits",
"sec-websocket-key": "vXnvsfo4splpfeDSnJLxKA==",
"sec-websocket-protocol": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiY29va2llIiwic2NvcGUiOiIiLCJtZXRhIjp7IkxpY2Vuc2UiOnsiY29tbW9uTmFtZSI6IiouY25iLmNvb2wiLCJub3RCZWZvcmUiOiIyMDI1LTA2LTE4VDA5OjE3OjE5WiIsIm5vdEFmdGVyIjoiMjAzNS0wNi0xNlQwOToxNzoxOVoiLCJpc3N1ZXIiOiJUZW5jZW50LCBJbmMiLCJvcmdhbml6YXRpb24iOiLohb7orq_kupEiLCJtZW1iZXJzIjoxMDAwMDAwLCJjaGFubmVsIjoiU2FhUyJ9LCJyb290X29yZ2FuaXphdGlvbl9zZXR0aW5nIjp7ImhpZGVfbWVtYmVycyI6MCwiaGlkZV9zdWJfZ3JvdXBzIjowLCJzaG93X3ByaXZhdGVfcmVwb193YXRlcm1hcmsiOjAsImdyb3VwX3Byb3RlY3Rpb24iOjEsImVtYWlsX3ZlcmlmaWNhdGlvbiI6IiIsInZhbHVlcyI6IiJ9fSwicGxhdGZvcm0iOiIiLCJ1c2VyX2lkIjoiIiwidXNlcl9lbWFpbCI6IiIsIm5pY2tuYW1lIjoiIiwidXNlcm5hbWUiOiIiLCJ2ZXJpZmllZCI6ZmFsc2UsImZyZWV6ZSI6ZmFsc2UsImJhbiI6ZmFsc2UsImxvY2tlZCI6ZmFsc2UsInVzZXJfZGV2aWNlX3R5cGUiOjAsInNsdWciOiJrZXZpc3VhbC9rZXZpc3VhbCIsInNsdWdfaWQiOiIxOTE1NDE1NDE0NTE4NjU3MDI0Iiwic2x1Z190eXBlIjoxLCJzbHVnX3N0YXR1cyI6MCwic2x1Z19mcmVlemUiOmZhbHNlLCJzbHVnX3Zpc2liaWxpdHkiOiJQdWJsaWMiLCJzbHVnX3Jvb3RfaWQiOiIxOTE1MzUzNzE5MDE4MzM2MjU2Iiwic2x1Z19yb2xlIjoiVW5rbm93biIsImxhbmd1YWdlIjoiZW4tVVMiLCJjb250ZXh0Ijoie30iLCJpc3MiOiJhY2Nlc3Mtcm91dGVyLTVmNzg5ZGM3N2ItN3Y1bnMiLCJpYXQiOjE3NzE1Mjk1MTQsImp0aSI6Ijk2MjYzIn0.PrlpGLNw7-1ucm3CiTQsPH7nIYFgvhZqQGhlws3R4ME",
"sec-websocket-version": "13"
},
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "omit"
});
```
```json
{"event":"subscribe","data":{"id":"495b92b2fbcd908e6ca1c809a3873bb373a7a6c6b814dfce5ff60b5f87dd0944--issue:info-update","off":1771529514922}}
{"event":"subscribe","data":{"id":"495b92b2fbcd908e6ca1c809a3873bb373a7a6c6b814dfce5ff60b5f87dd0944--issue:add-comment","off":1771529514922}}
{"event":"subscribe","data":{"id":"495b92b2fbcd908e6ca1c809a3873bb373a7a6c6b814dfce5ff60b5f87dd0944--issue:comment-update","off":1771529514922}}
{"event":"subscribe","data":{"id":"495b92b2fbcd908e6ca1c809a3873bb373a7a6c6b814dfce5ff60b5f87dd0944--issue:invisible","off":1771529514922}}
```

File diff suppressed because one or more lines are too long

217
src/issue/npc/build-env.ts Normal file
View File

@@ -0,0 +1,217 @@
import { useKey } from "@kevisual/context";
// CNB_BUILD_ID cnb-75b-1jj9hnk99 当前构建的流水号,全局唯一
// CNB_BUILD_WEB_URL https://cnb.cool/kevision/dev-cnb/-/build/logs/cnb-75b-1jj9hnk99 当前构建的日志地址
// CNB_BUILD_START_TIME 2026-03-09T14:59:01.550Z 当前构建的开始时间UTC 格式,示例 2025-08-21T09:13:45.803Z
// CNB_BUILD_USER xiongxiao 当前构建的触发者用户名
// CNB_BUILD_USER_NICKNAME 小熊猫呜呜呜 当前构建的触发者昵称
// CNB_BUILD_USER_EMAIL kevisual@xiongxiao.me 当前构建的触发者邮箱
// CNB_BUILD_USER_ID 1935321989751226368 当前构建的触发者 id
// CNB_BUILD_USER_NPC_SLUG 当前构建若为 NPC 触发,则为 NPC 所属仓库的路径
// CNB_BUILD_USER_NPC_NAME 当前构建若为 NPC 触发,则为 NPC 角色名
// CNB_BUILD_STAGE_NAME 初始化开发机 当前构建的 stage 名称
// CNB_BUILD_JOB_NAME 初始化开发机 当前构建的 job 名称
// CNB_BUILD_JOB_KEY job-0 当前构建的 job key同 stage 下唯一
// CNB_BUILD_WORKSPACE /workspace/ 自定义 shell 脚本执行的工作空间根目录
// CNB_BUILD_FAILED_MSG 流水线构建失败的错误信息,可在 failStages 中使用
// CNB_BUILD_FAILED_STAGE_NAME 流水线构建失败的 stage 的名称,可在 failStages 中使用
// CNB_PIPELINE_NAME pipeline-1 当前 pipeline 的 name没声明时为空
// CNB_PIPELINE_KEY pipeline-1 当前 pipeline 的索引 key例如 pipeline-0
// CNB_PIPELINE_ID cnb-75b-1jj9hnk99-001 当前 pipeline 的 id全局唯一字符串
// CNB_PIPELINE_DOCKER_IMAGE docker.cnb.cool/kevisual/dev-env:latest 当前 pipeline 所使用的 docker imagealpine:latest
// CNB_PIPELINE_STATUS 当前流水线的构建状态,可在 endStages 中查看其可能的值包括success、error、cancel
// CNB_PIPELINE_MAX_RUN_TIME 72000000 流水线最大运行时间,单位为毫秒
// CNB_RUNNER_IP 10.235.16.3 当前 pipeline 所在 Runner 的 ip
// CNB_CPUS 16 当前构建流水线可以使用的最大 CPU 核数
// CNB_MEMORY 32 当前构建流水线可以使用的最大内存大小,单位为 GiB
// CNB_IS_RETRY false 当前构建是否由 rebuild 触发
// HUSKY_SKIP_INSTALL 1 兼容 ci 环境下 husky
export const useBuildEnv = () => {
const buildId = useKey("CNB_BUILD_ID");
const buildWebUrl = useKey("CNB_BUILD_WEB_URL");
const buildStartTime = useKey("CNB_BUILD_START_TIME");
const buildUser = useKey("CNB_BUILD_USER");
const buildUserNickname = useKey("CNB_BUILD_USER_NICKNAME");
const buildUserEmail = useKey("CNB_BUILD_USER_EMAIL");
const buildUserId = useKey("CNB_BUILD_USER_ID");
const buildUserNpcSlug = useKey("CNB_BUILD_USER_NPC_SLUG");
const buildUserNpcName = useKey("CNB_BUILD_USER_NPC_NAME");
const buildStageName = useKey("CNB_BUILD_STAGE_NAME");
const buildJobName = useKey("CNB_BUILD_JOB_NAME");
const buildJobKey = useKey("CNB_BUILD_JOB_KEY");
const buildWorkspace = useKey("CNB_BUILD_WORKSPACE");
const buildFailedMsg = useKey("CNB_BUILD_FAILED_MSG");
const buildFailedStageName = useKey("CNB_BUILD_FAILED_STAGE_NAME");
const pipelineName = useKey("CNB_PIPELINE_NAME");
const pipelineKey = useKey("CNB_PIPELINE_KEY");
const pipelineId = useKey("CNB_PIPELINE_ID");
const pipelineDockerImage = useKey("CNB_PIPELINE_DOCKER_IMAGE");
const pipelineStatus = useKey("CNB_PIPELINE_STATUS");
const pipelineMaxRunTime = useKey("CNB_PIPELINE_MAX_RUN_TIME");
const runnerIp = useKey("CNB_RUNNER_IP");
const cpus = useKey("CNB_CPUS");
const memory = useKey("CNB_MEMORY");
const isRetry = useKey("CNB_IS_RETRY");
const huskySkipInstall = useKey("HUSKY_SKIP_INSTALL");
return {
/**
* @key CNB_BUILD_ID
* @description:当前构建的流水号,全局唯一
*/
buildId,
buildIdLabel: "当前构建的流水号,全局唯一",
/**
* @key CNB_BUILD_WEB_URL
* @description:当前构建的日志地址
*/
buildWebUrl,
buildWebUrlLabel: "当前构建的日志地址",
/**
* @key CNB_BUILD_START_TIME
* @description:当前构建的开始时间UTC 格式
*/
buildStartTime,
buildStartTimeLabel: "当前构建的开始时间UTC 格式",
/**
* @key CNB_BUILD_USER
* @description:当前构建的触发者用户名
*/
buildUser,
buildUserLabel: "当前构建的触发者用户名",
/**
* @key CNB_BUILD_USER_NICKNAME
* @description:当前构建的触发者昵称
*/
buildUserNickname,
buildUserNicknameLabel: "当前构建的触发者昵称",
/**
* @key CNB_BUILD_USER_EMAIL
* @description:当前构建的触发者邮箱
*/
buildUserEmail,
buildUserEmailLabel: "当前构建的触发者邮箱",
/**
* @key CNB_BUILD_USER_ID
* @description:当前构建的触发者 id
*/
buildUserId,
buildUserIdLabel: "当前构建的触发者 id",
/**
* @key CNB_BUILD_USER_NPC_SLUG
* @description:当前构建若为 NPC 触发,则为 NPC 所属仓库的路径
*/
buildUserNpcSlug,
buildUserNpcSlugLabel: "当前构建若为 NPC 触发,则为 NPC 所属仓库的路径",
/**
* @key CNB_BUILD_USER_NPC_NAME
* @description:当前构建若为 NPC 触发,则为 NPC 角色名
*/
buildUserNpcName,
buildUserNpcNameLabel: "当前构建若为 NPC 触发,则为 NPC 角色名",
/**
* @key CNB_BUILD_STAGE_NAME
* @description:当前构建的 stage 名称
*/
buildStageName,
buildStageNameLabel: "当前构建的 stage 名称",
/**
* @key CNB_BUILD_JOB_NAME
* @description:当前构建的 job 名称
*/
buildJobName,
buildJobNameLabel: "当前构建的 job 名称",
/**
* @key CNB_BUILD_JOB_KEY
* @description:当前构建的 job key同 stage 下唯一
*/
buildJobKey,
buildJobKeyLabel: "当前构建的 job key同 stage 下唯一",
/**
* @key CNB_BUILD_WORKSPACE
* @description:自定义 shell 脚本执行的工作空间根目录
*/
buildWorkspace,
buildWorkspaceLabel: "自定义 shell 脚本执行的工作空间根目录",
/**
* @key CNB_BUILD_FAILED_MSG
* @description:流水线构建失败的错误信息
*/
buildFailedMsg,
buildFailedMsgLabel: "流水线构建失败的错误信息",
/**
* @key CNB_BUILD_FAILED_STAGE_NAME
* @description:流水线构建失败的 stage 的名称
*/
buildFailedStageName,
buildFailedStageNameLabel: "流水线构建失败的 stage 的名称",
/**
* @key CNB_PIPELINE_NAME
* @description:当前 pipeline 的 name
*/
pipelineName,
pipelineNameLabel: "当前 pipeline 的 name",
/**
* @key CNB_PIPELINE_KEY
* @description:当前 pipeline 的索引 key
*/
pipelineKey,
pipelineKeyLabel: "当前 pipeline 的索引 key",
/**
* @key CNB_PIPELINE_ID
* @description:当前 pipeline 的 id
*/
pipelineId,
pipelineIdLabel: "当前 pipeline 的 id",
/**
* @key CNB_PIPELINE_DOCKER_IMAGE
* @description:当前 pipeline 所使用的 docker image
*/
pipelineDockerImage,
pipelineDockerImageLabel: "当前 pipeline 所使用的 docker image",
/**
* @key CNB_PIPELINE_STATUS
* @description:当前流水线的构建状态可能的值包括success、error、cancel
*/
pipelineStatus,
pipelineStatusLabel: "当前流水线的构建状态可能的值包括success、error、cancel",
/**
* @key CNB_PIPELINE_MAX_RUN_TIME
* @description:流水线最大运行时间,单位为毫秒
*/
pipelineMaxRunTime,
pipelineMaxRunTimeLabel: "流水线最大运行时间,单位为毫秒",
/**
* @key CNB_RUNNER_IP
* @description:当前 pipeline 所在 Runner 的 ip
*/
runnerIp,
runnerIpLabel: "当前 pipeline 所在 Runner 的 ip",
/**
* @key CNB_CPUS
* @description:当前构建流水线可以使用的最大 CPU 核数
*/
cpus,
cpusLabel: "当前构建流水线可以使用的最大 CPU 核数",
/**
* @key CNB_MEMORY
* @description:当前构建流水线可以使用的最大内存大小,单位为 GiB
*/
memory,
memoryLabel: "当前构建流水线可以使用的最大内存大小,单位为 GiB",
/**
* @key CNB_IS_RETRY
* @description:当前构建是否由 rebuild 触发
*/
isRetry,
isRetryLabel: "当前构建是否由 rebuild 触发",
/**
* @key HUSKY_SKIP_INSTALL
* @description:兼容 ci 环境下 husky
*/
huskySkipInstall,
huskySkipInstallLabel: "兼容 ci 环境下 husky"
};
}

201
src/issue/npc/env.ts Normal file
View File

@@ -0,0 +1,201 @@
// CNB_NPC_SLUG 对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库路径,否则为空字符串
// CNB_NPC_NAME 对于 NPC 事件触发的构建,值为 NPC 角色名,否则为空字符串
// CNB_NPC_SHA 对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库默认分支最新提交的 sha否则为空字符串
// CNB_NPC_PROMPT 对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色 Prompt否则为空字符串
// CNB_NPC_AVATAR 对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色头像,否则为空字符串
// CNB_NPC_ENABLE_THINKING 对于 @npc 事件触发的构建,值为 NPC 角色是否开启思考,否则为空字符串
import { useKey } from "@kevisual/context";
export function useNPCEnv() {
const npcSlug = useKey("CNB_NPC_SLUG");
const npcName = useKey("CNB_NPC_NAME");
const npcSha = useKey("CNB_NPC_SHA");
const npcPrompt = useKey("CNB_NPC_PROMPT");
const npcAvatar = useKey("CNB_NPC_AVATAR");
const npcEnableThinking = useKey("CNB_NPC_ENABLE_THINKING");
return {
/**
* @key CNB_NPC_SLUG
* @description:对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库路径,否则为空字符串
*/
npcSlug,
npcSlugLabel: "对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库路径,否则为空字符串",
/**
* @key CNB_NPC_NAME
* @description:对于 NPC 事件触发的构建,值为 NPC 角色名,否则为空字符串
*/
npcName,
npcNameLabel: "对于 NPC 事件触发的构建,值为 NPC 角色名,否则为空字符串",
/**
* @key CNB_NPC_SHA
* @description:对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库默认分支最新提交的 sha否则为空字符串
*/
npcSha,
npcShaLabel: "对于 @ 知识库角色触发的 NPC 事件,值为 NPC 所属仓库默认分支最新提交的 sha否则为空字符串",
/**
* @key CNB_NPC_PROMPT
* @description:对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色 Prompt否则为空字符串
*/
npcPrompt,
npcPromptLabel: "对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色 Prompt否则为空字符串",
/**
* @key CNB_NPC_AVATAR
* @description:对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色头像,否则为空字符串
*/
npcAvatar,
npcAvatarLabel: "对于 @ 知识库角色触发的 NPC 事件,值为 NPC 角色头像,否则为空字符串",
/**
* @key CNB_NPC_ENABLE_THINKING
* @description:对于 @npc 事件触发的构建,值为 NPC 角色是否开启思考,否则为空字符串
*/
npcEnableThinking,
npcEnableThinkingLabel: "对于 @npc 事件触发的构建,值为 NPC 角色是否开启思考,否则为空字符串"
};
}
// CNB_COMMENT_ID 对于评论事件触发的构建,值为评论全局唯一 ID否则为空字符串
// CNB_COMMENT_BODY 对于评论事件触发的构建,值为评论内容,否则为空字符串
// CNB_COMMENT_TYPE note 对于 PR 代码评审评论,值为 diff_note对于 PR 非代码评审评论以及 Issue 评论,值为 note否则为空字符串
// CNB_COMMENT_FILE_PATH 对于 PR 代码评审评论,值为评论所在文件,否则为空字符串
// CNB_COMMENT_RANGE 对于 PR 代码评审评论,值为评论所在代码行。如,单行为 L12多行为 L13-L16否则为空字符串
// CNB_REVIEW_ID 对于 PR 代码评审,值为评审 ID否则为空字符串
export function useCommentEnv() {
const commentId = useKey("CNB_COMMENT_ID");
const commentBody = useKey("CNB_COMMENT_BODY");
const commentType = useKey("CNB_COMMENT_TYPE");
const commentFilePath = useKey("CNB_COMMENT_FILE_PATH");
const commentRange = useKey("CNB_COMMENT_RANGE");
const reviewId = useKey("CNB_REVIEW_ID");
return {
/**
* @key CNB_COMMENT_ID
* @description:对于评论事件触发的构建,值为评论全局唯一 ID否则为空字符串
*/
commentId,
commentIdLabel: "对于评论事件触发的构建,值为评论全局唯一 ID否则为空字符串",
/**
* @key CNB_COMMENT_BODY
* @description:对于评论事件触发的构建,值为评论内容,否则为空字符串
*/
commentBody,
commentBodyLabel: "对于评论事件触发的构建,值为评论内容,否则为空字符串",
/**
* @key CNB_COMMENT_TYPE
* @description:note 对于 PR 代码评审评论,值为 diff_note对于 PR 非代码评审评论以及 Issue 评论,值为 note否则为空字符串
*/
commentType,
commentTypeLabel: "对于 PR 代码评审评论,值为 diff_note对于 PR 非代码评审评论以及 Issue 评论,值为 note否则为空字符串",
/**
* @key CNB_COMMENT_FILE_PATH
* @description:对于 PR 代码评审评论,值为评论所在文件,否则为空字符串
*/
commentFilePath,
commentFilePathLabel: "对于 PR 代码评审评论,值为评论所在文件,否则为空字符串",
/**
* @key CNB_COMMENT_RANGE
* @description:对于 PR 代码评审评论,值为评论所在代码行。如,单行为 L12多行为 L13-L16否则为空字符串
*/
commentRange,
commentRangeLabel: "对于 PR 代码评审评论,值为评论所在代码行。如,单行为 L12多行为 L13-L16否则为空字符串",
/**
* @key CNB_REVIEW_ID
* @description:对于 PR 代码评审,值为评审 ID否则为空字符串
*/
reviewId,
reviewIdLabel: "对于 PR 代码评审,值为评审 ID否则为空字符串"
};
}
// CNB_ISSUE_ID Issue 全局唯一 ID
// CNB_ISSUE_IID Issue 仓库编号
// CNB_ISSUE_TITLE Issue 标题
// CNB_ISSUE_DESCRIPTION Issue 描述
// CNB_ISSUE_OWNER Issue 作者
// CNB_ISSUE_STATE Issue 状态
// CNB_ISSUE_IS_RESOLVED Issue 是否已解决
// CNB_ISSUE_ASSIGNEES Issue 处理人列表
// CNB_ISSUE_LABELS Issue 标签列表
// CNB_ISSUE_PRIORITY Issue 优先级
export const useIssueEnv = () => {
const issueId = useKey("CNB_ISSUE_ID");
const issueIid = useKey("CNB_ISSUE_IID");
const issueTitle = useKey("CNB_ISSUE_TITLE");
const issueDescription = useKey("CNB_ISSUE_DESCRIPTION");
const issueOwner = useKey("CNB_ISSUE_OWNER");
const issueState = useKey("CNB_ISSUE_STATE");
const issueIsResolved = useKey("CNB_ISSUE_IS_RESOLVED");
const issueAssignees = useKey("CNB_ISSUE_ASSIGNEES");
const issueLabels = useKey("CNB_ISSUE_LABELS");
const issuePriority = useKey("CNB_ISSUE_PRIORITY");
return {
/**
* @key CNB_ISSUE_ID
* @description:Issue 全局唯一 ID
*/
issueId,
issueIdLabel: "Issue 全局唯一 ID",
/**
* @key CNB_ISSUE_IID
* @description:Issue 仓库编号
*/
issueIid,
issueIidLabel: "Issue 仓库编号",
/**
* @key CNB_ISSUE_TITLE
* @description:Issue 标题
*/
issueTitle,
issueTitleLabel: "Issue 标题",
/**
* @key CNB_ISSUE_DESCRIPTION
* @description:Issue 描述
*/
issueDescription,
issueDescriptionLabel: "Issue 描述",
/**
* @key CNB_ISSUE_OWNER
* @description:Issue 作者
*/
issueOwner,
issueOwnerLabel: "Issue 作者",
/**
* @key CNB_ISSUE_STATE
* @description:Issue 状态
*/
issueState,
issueStateLabel: "Issue 状态",
/**
* @key CNB_ISSUE_IS_RESOLVED
* @description:Issue 是否已解决
*/
issueIsResolved,
issueIsResolvedLabel: "Issue 是否已解决",
/**
* @key CNB_ISSUE_ASSIGNEES
* @description:Issue 处理人列表
*/
issueAssignees,
issueAssigneesLabel: "Issue 处理人列表",
/**
* @key CNB_ISSUE_LABELS
* @description:Issue 标签列表, 多个以 , 分隔。
*/
issueLabels,
issueLabelsLabel: "Issue 标签列表, 多个以 , 分隔。",
/**
* @key CNB_ISSUE_PRIORITY
* @description:Issue 优先级
*/
issuePriority,
issuePriorityLabel: "Issue 优先级"
};
}
export * from './build-env.ts'
export * from './pr-env.ts'
export * from './repo-env.ts'

95
src/issue/npc/pr-env.ts Normal file
View File

@@ -0,0 +1,95 @@
import { useKey } from "@kevisual/context";
// CNB_PULL_REQUEST false 对于由 pull_request、pull_request.update、pull_request.target 触发的构建,值为 true否则为 false
// CNB_PULL_REQUEST_LIKE false 对于由 合并类事件 触发的构建,值为 true否则为 false
// CNB_PULL_REQUEST_PROPOSER 对于由 合并类事件 触发的构建,值为提出 PR 者名称,否则为空字符串
// CNB_PULL_REQUEST_TITLE 对于由 合并类事件 触发的构建,值为提 PR 时候填写的标题,否则为空字符串
// CNB_PULL_REQUEST_BRANCH 对于由 合并类事件 触发的构建,值为发起 PR 的源分支名称,否则为空字符串
// CNB_PULL_REQUEST_SHA 对于由 合并类事件 触发的构建,值为当前 PR 源分支最新的提交 sha否则为空字符串
// CNB_PULL_REQUEST_TARGET_SHA 对于由 合并类事件 触发的构建,值为当前 PR 目标分支最新的提交 sha否则为空字符串
// CNB_PULL_REQUEST_MERGE_SHA 对于由 pull_request.merged 触发的构建,值为合并后的 sha对于 pull_request 等触发的构建,值为预合并后的 sha否则为空字符串
// CNB_PULL_REQUEST_SLUG 对于由 合并类事件 触发的构建,值为源仓库的仓库 slug如 group_slug/repo_name否则为空字符串
// CNB_PULL_REQUEST_ACTION 对于由 合并类事件 触发的构建可能的值有created(新建PR)、code_update(源分支push)、status_update(评审通过或CI状态变更),否则为空字符串
// CNB_PULL_REQUEST_ID 对于由 合并类事件 触发的构建,值为当前或者关联 PR 的全局唯一 id否则为空字符串
export const usePullRequestEnv = () => {
const pullRequest = useKey("CNB_PULL_REQUEST");
const pullRequestLike = useKey("CNB_PULL_REQUEST_LIKE");
const pullRequestProposer = useKey("CNB_PULL_REQUEST_PROPOSER");
const pullRequestTitle = useKey("CNB_PULL_REQUEST_TITLE");
const pullRequestBranch = useKey("CNB_PULL_REQUEST_BRANCH");
const pullRequestSha = useKey("CNB_PULL_REQUEST_SHA");
const pullRequestTargetSha = useKey("CNB_PULL_REQUEST_TARGET_SHA");
const pullRequestMergeSha = useKey("CNB_PULL_REQUEST_MERGE_SHA");
const pullRequestSlug = useKey("CNB_PULL_REQUEST_SLUG");
const pullRequestAction = useKey("CNB_PULL_REQUEST_ACTION");
const pullRequestId = useKey("CNB_PULL_REQUEST_ID");
return {
/**
* @key CNB_PULL_REQUEST
* @description:对于由 pull_request、pull_request.update、pull_request.target 触发的构建,值为 true否则为 false
*/
pullRequest,
pullRequestLabel: "对于由 pull_request、pull_request.update、pull_request.target 触发的构建,值为 true否则为 false",
/**
* @key CNB_PULL_REQUEST_LIKE
* @description:对于由 合并类事件 触发的构建,值为 true否则为 false
*/
pullRequestLike,
pullRequestLikeLabel: "对于由 合并类事件 触发的构建,值为 true否则为 false",
/**
* @key CNB_PULL_REQUEST_PROPOSER
* @description:对于由 合并类事件 触发的构建,值为提出 PR 者名称,否则为空字符串
*/
pullRequestProposer,
pullRequestProposerLabel: "对于由 合并类事件 触发的构建,值为提出 PR 者名称,否则为空字符串",
/**
* @key CNB_PULL_REQUEST_TITLE
* @description:对于由 合并类事件 触发的构建,值为提 PR 时候填写的标题,否则为空字符串
*/
pullRequestTitle,
pullRequestTitleLabel: "对于由 合并类事件 触发的构建,值为提 PR 时候填写的标题,否则为空字符串",
/**
* @key CNB_PULL_REQUEST_BRANCH
* @description:对于由 合并类事件 触发的构建,值为发起 PR 的源分支名称,否则为空字符串
*/
pullRequestBranch,
pullRequestBranchLabel: "对于由 合并类事件 触发的构建,值为发起 PR 的源分支名称,否则为空字符串",
/**
* @key CNB_PULL_REQUEST_SHA
* @description:对于由 合并类事件 触发的构建,值为当前 PR 源分支最新的提交 sha否则为空字符串
*/
pullRequestSha,
pullRequestShaLabel: "对于由 合并类事件 触发的构建,值为当前 PR 源分支最新的提交 sha否则为空字符串",
/**
* @key CNB_PULL_REQUEST_TARGET_SHA
* @description:对于由 合并类事件 触发的构建,值为当前 PR 目标分支最新的提交 sha否则为空字符串
*/
pullRequestTargetSha,
pullRequestTargetShaLabel: "对于由 合并类事件 触发的构建,值为当前 PR 目标分支最新的提交 sha否则为空字符串",
/**
* @key CNB_PULL_REQUEST_MERGE_SHA
* @description:对于由 pull_request.merged 触发的构建,值为合并后的 sha对于 pull_request 等触发的构建,值为预合并后的 sha否则为空字符串
*/
pullRequestMergeSha,
pullRequestMergeShaLabel: "对于由 pull_request.merged 触发的构建,值为合并后的 sha对于 pull_request 等触发的构建,值为预合并后的 sha否则为空字符串",
/**
* @key CNB_PULL_REQUEST_SLUG
* @description:对于由 合并类事件 触发的构建,值为源仓库的仓库 slug如 group_slug/repo_name否则为空字符串
*/
pullRequestSlug,
pullRequestSlugLabel: "对于由 合并类事件 触发的构建,值为源仓库的仓库 slug如 group_slug/repo_name否则为空字符串",
/**
* @key CNB_PULL_REQUEST_ACTION
* @description:对于由 合并类事件 触发的构建可能的值有created(新建PR)、code_update(源分支push)、status_update(评审通过或CI状态变更),否则为空字符串
*/
pullRequestAction,
pullRequestActionLabel: "对于由 合并类事件 触发的构建可能的值有created(新建PR)、code_update(源分支push)、status_update(评审通过或CI状态变更),否则为空字符串",
/**
* @key CNB_PULL_REQUEST_ID
* @description:对于由 合并类事件 触发的构建,值为当前或者关联 PR 的全局唯一 id否则为空字符串
*/
pullRequestId,
pullRequestIdLabel: "对于由 合并类事件 触发的构建,值为当前或者关联 PR 的全局唯一 id否则为空字符串"
};
}

56
src/issue/npc/repo-env.ts Normal file
View File

@@ -0,0 +1,56 @@
import { useKey } from "@kevisual/context";
// CNB_REPO_SLUG kevision/dev-cnb 目标仓库路径,格式为 group_slug / repo_namegroup_slug / sub_gourp_slug /.../repo_name
// CNB_REPO_SLUG_LOWERCASE kevision/dev-cnb 目标仓库路径小写格式
// CNB_REPO_NAME dev-cnb 目标仓库名称
// CNB_REPO_NAME_LOWERCASE dev-cnb 目标仓库名称小写格式
// CNB_REPO_ID 2026263219584110592 目标仓库的 id
// CNB_REPO_URL_HTTPS 目标仓库 https 地址
export const useRepoInfoEnv = () => {
const repoSlug = useKey("CNB_REPO_SLUG");
const repoSlugLowercase = useKey("CNB_REPO_SLUG_LOWERCASE");
const repoName = useKey("CNB_REPO_NAME");
const repoNameLowercase = useKey("CNB_REPO_NAME_LOWERCASE");
const repoId = useKey("CNB_REPO_ID");
const repoUrlHttps = useKey("CNB_REPO_URL_HTTPS");
return {
/**
* @key CNB_REPO_SLUG
* @description:目标仓库路径,格式为 group_slug/repo_namegroup_slug/sub_group_slug/.../repo_name
*/
repoSlug,
repoSlugLabel: "目标仓库路径,格式为 group_slug/repo_namegroup_slug/sub_group_slug/.../repo_name",
/**
* @key CNB_REPO_SLUG_LOWERCASE
* @description:目标仓库路径小写格式
*/
repoSlugLowercase,
repoSlugLowercaseLabel: "目标仓库路径小写格式",
/**
* @key CNB_REPO_NAME
* @description:目标仓库名称
*/
repoName,
repoNameLabel: "目标仓库名称",
/**
* @key CNB_REPO_NAME_LOWERCASE
* @description:目标仓库名称小写格式
*/
repoNameLowercase,
repoNameLowercaseLabel: "目标仓库名称小写格式",
/**
* @key CNB_REPO_ID
* @description:目标仓库的 id
*/
repoId,
repoIdLabel: "目标仓库的 id",
/**
* @key CNB_REPO_URL_HTTPS
* @description:目标仓库 https 地址
*/
repoUrlHttps,
repoUrlHttpsLabel: "目标仓库 https 地址"
};
}

View File

@@ -1,5 +1,5 @@
import { CNBCore, CNBCoreOptions, RequestOptions, Result } from "../cnb-core.ts"; import { CNBCore, CNBCoreOptions, RequestOptions, Result } from "../cnb-core.ts";
import dayjs from "dayjs";
export class Repo extends CNBCore { export class Repo extends CNBCore {
constructor(options: CNBCoreOptions) { constructor(options: CNBCoreOptions) {
super(options); super(options);
@@ -30,6 +30,12 @@ export class Repo extends CNBCore {
const url = `/${repo}`; const url = `/${repo}`;
return this.delete({ url }); return this.delete({ url });
} }
/**
* 使用cookie创建提交如果没有指定parent_commit_sha则会自动获取最新的提交作为父提交
* @param repo
* @param data
* @returns
*/
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,
@@ -41,6 +47,12 @@ export class Repo extends CNBCore {
const preCommitSha = commitList.length > 0 ? commitList[0].sha : undefined; const preCommitSha = commitList.length > 0 ? commitList[0].sha : undefined;
if (!data.parent_commit_sha && preCommitSha) { if (!data.parent_commit_sha && preCommitSha) {
data.parent_commit_sha = preCommitSha; data.parent_commit_sha = preCommitSha;
} else if (data.parent_commit_sha) {
// 如果指定了parent_commi_sha;
if (!data.new_branch) {
const date = dayjs().format('MMDDHHmm');
data.new_branch = `refs/heads/${date}`;
}
} }
const url = `${this.hackURL}/${repo}/-/git/commits`; const url = `${this.hackURL}/${repo}/-/git/commits`;
const postData: CreateCommitData = { const postData: CreateCommitData = {
@@ -52,6 +64,7 @@ export class Repo extends CNBCore {
new_branch: data.new_branch || 'refs/heads/main', new_branch: data.new_branch || 'refs/heads/main',
}; };
if (!postData.parent_commit_sha) { if (!postData.parent_commit_sha) {
// 如果没有父提交sha则说明是第一次提交可以删除parent_commit_sha和base_branch字段
delete postData.parent_commit_sha; delete postData.parent_commit_sha;
delete postData.base_branch; delete postData.base_branch;
} }
@@ -108,6 +121,10 @@ export class Repo extends CNBCore {
const url = `/${repo}`; const url = `/${repo}`;
return this.patch({ url, data: params }); return this.patch({ url, data: params });
} }
getRepo(repo: string): Promise<Result<RepoItem>> {
const url = `/${repo}`;
return this.get({ url });
}
} }
type UpdateRepoInfo = { type UpdateRepoInfo = {
description?: string; description?: string;

View File

@@ -16,6 +16,18 @@ export class User extends CNBCore {
useCookie: true, useCookie: true,
}); });
} }
/**
* 判断当前 Cookie 是否有效
* @returns
*/
async checkCookieValid(): Promise<Result> {
const user = await this.getCurrentUser();
if (user.code === 200) {
return { code: 200, message: 'cookie valid' };
} else {
return { code: 401, message: 'cookie invalid' };
}
}
/** /**
* 使用 Token 获取用户信息 * 使用 Token 获取用户信息
* @returns * @returns

View File

@@ -62,7 +62,7 @@ export class Workspace extends CNBCore {
* 停止我的云原生开发环境 * 停止我的云原生开发环境
* @param params 停止参数pipelineId 和 sn 二选一,优先使用 pipelineId * @param params 停止参数pipelineId 和 sn 二选一,优先使用 pipelineId
*/ */
async stopWorkspace(params: { pipelineId?: string; sn?: string }): Promise<{ buildLogUrl: string; message: string; sn: string }> { async stopWorkspace(params: { pipelineId?: string; sn?: string }): Promise<Result<{ buildLogUrl: string; message: string; sn: string }>> {
const data: { pipelineId?: string; sn?: string } = {}; const data: { pipelineId?: string; sn?: string } = {};
if (params.pipelineId) { if (params.pipelineId) {
@@ -113,7 +113,62 @@ export class Workspace extends CNBCore {
return this.post({ url: `/${repo}/-/workspace/start`, data }); return this.post({ url: `/${repo}/-/workspace/start`, data });
} }
/**
* 添加使用cookie获取工作空间访问权限的功能适用于需要保持工作空间连接状态的场景
* 例如使用 WebSocket 连接工作空间时需要携带 cookie 进行身份验证。
* https://cnb.cool/kevisual/dev-env/-/workspace/vscode-web/cnb-708-1ji9sog7o-001
* @param repo
* @param pipelineId
* @returns
*/
async getWorkspaceCookie(repo: string, pipelineId: string): Promise<Result<{ value: string, cookie: string; cookieName: string }>> {
const url = `${this.hackURL}/${repo}/-/workspace/vscode-web/${pipelineId}`;
const response = await fetch(url, {
method: 'GET',
redirect: 'manual',
headers: {
'Cookie': this.cookie || '',
'Accept': 'application/json',
}
});
// 第一次 302 重定向
if (response.status === 302 || response.status === 301) {
// 包含token的重定向 URL 通常在 Location 头中返回
// 类似 https://cnb-708-1ji9sog7o-001.cnb.space/login?t=orange:workspace:login-token:963691a2-35ce-4fef-a7ba-72723cefd226
const loginURL = response.headers.get('Location');
// 从 URL 参数中获取 cookieName例如: orange:workspace:cookie-session:cnb-708-1ji9sog7o-001
const cookieName = `orange:workspace:cookie-session:${pipelineId}`;
// 第二次请求,也设置为 manual 防止自动重定向
const response2 = await fetch(loginURL || '', {
method: 'GET',
redirect: 'manual',
headers: {
'Cookie': this.cookie || '',
'Accept': 'application/json',
}
});
// 第二次 302 重定向,获取最终的 cookie 值
if (response2.status === 302 || response2.status === 301) {
// 从 Set-Cookie 头中获取 cookie 值
const setCookie = response2.headers.get('Set-Cookie');
// 解析 cookie 值
const cookieValue = setCookie?.split(';')[0]?.split('=')[1] || '';
return {
code: 200,
message: 'success',
data: { value: cookieValue, cookieName, cookie: `${cookieName}=${cookieValue}` }
};
}
// 如果不是重定向,尝试获取 JSON 数据
return { code: 500 };
}
return { code: 500, };
}
} }
export interface WorkspaceLinkDetail { export interface WorkspaceLinkDetail {
codebuddy: string; codebuddy: string;

View File

@@ -0,0 +1,137 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { execSync } from 'node:child_process';
export type KeepAliveData = {
wsUrl: string;
cookie: string;
repo: string;
pipelineId: string;
createdTime: number;
filePath: string;
pm2Name: string;
}
type KeepAliveCache = {
data: KeepAliveData[];
}
const baseDir = path.join(os.homedir(), '.cnb', 'live');
const keepAliveFilePath = path.join(baseDir, 'keepAliveCache.json');
export const runLive = (filePath: string, pm2Name: string) => {
// 使用 npx 运行命令
const cmdArgs = `cnb live -c ${filePath}`;
// 先停止已存在的同名 pm2 进程
const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
console.log('停止已存在的进程:', stopCmd);
try {
execSync(stopCmd, { stdio: 'inherit' });
} catch (error) {
console.log('停止进程失败或进程不存在:', error);
}
// 使用pm2启动
const pm2Cmd = `pm2 start ev --name ${pm2Name} --no-autorestart -- ${cmdArgs}`;
console.log('执行命令:', pm2Cmd);
try {
const result = execSync(pm2Cmd, { stdio: 'pipe', encoding: 'utf8' });
console.log(result);
} catch (error) {
console.error("状态码:", error.status);
console.error("错误详情:", error.stderr.toString()); // 这里会显示 ev 命令报的具体错误
}
}
export const stopLive = (pm2Name: string): boolean => {
const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
console.log('停止进程:', stopCmd);
try {
execSync(stopCmd, { stdio: 'inherit' });
console.log(`已停止 ${pm2Name} 的保持存活任务`);
return true;
} catch (error) {
console.error('停止进程失败:', error);
}
return false;
}
export function getKeepAliveCache(): KeepAliveCache {
try {
if (fs.existsSync(keepAliveFilePath)) {
const data = fs.readFileSync(keepAliveFilePath, 'utf-8');
const cache = JSON.parse(data) as KeepAliveCache;
return cache;
} else {
return { data: [] };
}
} catch (error) {
console.error('读取保持存活缓存文件失败:', error);
return { data: [] };
}
}
export function addKeepAliveData(data: KeepAliveData): KeepAliveCache {
const cache = getKeepAliveCache();
cache.data.push(data);
runLive(data.filePath, data.pm2Name);
try {
if (!fs.existsSync(path.dirname(keepAliveFilePath))) {
fs.mkdirSync(path.dirname(keepAliveFilePath), { recursive: true });
}
fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
return cache;
} catch (error) {
console.error('写入保持存活缓存文件失败:', error);
return { data: [] };
}
}
export function removeKeepAliveData(repo: string, pipelineId: string): KeepAliveCache {
const cache = getKeepAliveCache();
const item = cache.data.find(item => item.repo === repo && item.pipelineId === pipelineId);
if (item) {
stopLive(item.pm2Name);
}
cache.data = cache.data.filter(item => item.repo !== repo || item.pipelineId !== pipelineId);
try {
fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
return cache;
} catch (error) {
console.error('写入保持存活缓存文件失败:', error);
return { data: [] };
}
}
export const createLiveData = (data: { cookie: string, repo: string, pipelineId: string }): KeepAliveData => {
const { cookie, repo, pipelineId } = data;
const createdTime = Date.now();
const wsUrl = `wss://${pipelineId}.cnb.space:443?skipWebSocketFrames=false`;
const pm2Name = `keep_${repo}__${pipelineId}`.replace(/\//g, '__');
const filePath = path.join(baseDir, `${pm2Name}.json`);
const _newData = { wss: wsUrl, wsUrl, cookie, repo, pipelineId, createdTime, filePath, pm2Name };
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
fs.writeFileSync(filePath, JSON.stringify(_newData, null, 2), 'utf-8');
return _newData;
}
export class KeepAliveManager {
static getCache() {
return getKeepAliveCache();
}
static add(data: KeepAliveData) {
return addKeepAliveData(data);
}
static createLiveData(data: { cookie: string, repo: string, pipelineId: string }): KeepAliveData {
return createLiveData(data);
}
static remove(repo: string, pipelineId: string) {
return removeKeepAliveData(repo, pipelineId);
}
}

View File

@@ -1,5 +1,16 @@
// WebSocket Keep-Alive Client Library // WebSocket Keep-Alive Client Library
import WebSocket from "ws"; // 运行时检测Bun 使用原生 WebSocketNode.js 使用 ws 库
let WebSocketModule: any;
if (typeof Bun !== 'undefined') {
// Bun 环境:使用原生 WebSocket
WebSocketModule = { WebSocket: globalThis.WebSocket };
} else {
// Node.js 环境:使用 ws 库
WebSocketModule = await import('ws');
}
const WebSocket = WebSocketModule.WebSocket;
export interface KeepAliveConfig { export interface KeepAliveConfig {
wsUrl: string; wsUrl: string;
@@ -31,6 +42,7 @@ export class WSKeepAlive {
private pingTimer: NodeJS.Timeout | null = null; private pingTimer: NodeJS.Timeout | null = null;
private messageHandlers: Set<MessageHandler> = new Set(); private messageHandlers: Set<MessageHandler> = new Set();
private url: URL; private url: URL;
private readonly isBun: boolean;
constructor(config: KeepAliveConfig) { constructor(config: KeepAliveConfig) {
this.config = { this.config = {
@@ -48,6 +60,7 @@ export class WSKeepAlive {
debug: config.debug ?? false, debug: config.debug ?? false,
}; };
this.url = new URL(this.config.wsUrl); this.url = new URL(this.config.wsUrl);
this.isBun = typeof Bun !== 'undefined';
} }
private log(message: string) { private log(message: string) {
@@ -107,44 +120,91 @@ export class WSKeepAlive {
} }
}); });
this.ws.on("open", () => { if (this.isBun) {
debug && this.log("Connected!"); // Bun 环境:使用标准 Web API
this.reconnectAttempts = 0; const ws = this.ws as any;
this.config.onConnect(); ws.onopen = () => {
this.startPing(); debug && this.log("Connected!");
}); this.reconnectAttempts = 0;
this.config.onConnect();
this.startPing();
};
this.ws.on("message", (data: any) => { ws.onmessage = async (event: MessageEvent) => {
if (Buffer.isBuffer(data)) { let data: Buffer | string;
const parsed = this.parseMessage(data);
this.config.onMessage(parsed?.raw ?? data);
this.messageHandlers.forEach(handler => { if (event.data instanceof Blob) {
if (parsed) handler(parsed); data = Buffer.from(await event.data.arrayBuffer());
}); } else if (event.data instanceof ArrayBuffer) {
} else { data = Buffer.from(event.data);
this.config.onMessage(data); } else if (typeof event.data === 'string') {
} data = event.data;
}); } else {
data = Buffer.from(event.data);
}
this.ws.on("close", (code: number) => { this.handleMessage(data);
debug && this.log(`Disconnected (code: ${code})`); };
this.stopPing();
this.config.onDisconnect(code);
this.handleReconnect();
});
this.ws.on("error", (err: Error) => { ws.onclose = (event: CloseEvent) => {
debug && this.log(`Error: ${err.message}`); debug && this.log(`Disconnected (code: ${event.code})`);
this.config.onError(err); this.stopPing();
}); this.config.onDisconnect(event.code);
this.handleReconnect();
};
ws.onerror = (event: Event) => {
debug && this.log(`Error: ${event}`);
this.config.onError(new Error("WebSocket error"));
};
} else {
// Node.js (ws 库):使用 EventEmitter 模式
const ws = this.ws as any;
ws.on("open", () => {
debug && this.log("Connected!");
this.reconnectAttempts = 0;
this.config.onConnect();
this.startPing();
});
ws.on("message", (data: any) => {
this.handleMessage(data);
});
ws.on("close", (code: number) => {
debug && this.log(`Disconnected (code: ${code})`);
this.stopPing();
this.config.onDisconnect(code);
this.handleReconnect();
});
ws.on("error", (err: Error) => {
debug && this.log(`Error: ${err.message}`);
this.config.onError(err);
});
}
}
// 统一的消息处理方法
private handleMessage(data: Buffer | string) {
if (Buffer.isBuffer(data)) {
const parsed = this.parseMessage(data);
this.config.onMessage(parsed?.raw ?? data);
this.messageHandlers.forEach(handler => {
if (parsed) handler(parsed);
});
} else {
this.config.onMessage(data);
}
} }
private startPing() { private startPing() {
this.stopPing(); this.stopPing();
this.pingTimer = setInterval(() => { this.pingTimer = setInterval(() => {
if (this.ws && this.ws.readyState === WebSocket.OPEN) { if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.ping(); // 使用 JSON 格式的 ping 消息,兼容 Bun 和 Node.js
this.ws.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
this.log("Sent ping"); this.log("Sent ping");
} }
}, this.config.pingInterval); }, this.config.pingInterval);

12
test/a-config.ts Normal file
View File

@@ -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();

1
test/build-log.ts Normal file
View File

@@ -0,0 +1 @@
// 不用查看

View File

@@ -60,4 +60,34 @@ main:
api_trigger_sync_from_gitea: api_trigger_sync_from_gitea:
- <<: *common_sync_from_gitea - <<: *common_sync_from_gitea
` `
buildByConfig() // buildByConfig()
const buildByConfig2 = async () => {
const build = await repo.startBuild('kevisual/cnb', {
branch: 'main',
env: {
},
event: 'api_trigger_events',
config: config2,
});
console.log("build", showMore(build));
}
const config2 = `# .cnb.yml
include:
- https://cnb.cool/kevisual/cnb/-/blob/main/.cnb/template.yml
main:
api_trigger_events:
-
docker:
image: docker.cnb.cool/kevisual/dev-env:latest
services:
- vscode
- docker
stages:
- name: test
steps:
- run: echo "hello world"
`
buildByConfig2()

5
test/get-live-meta.ts Normal file
View File

@@ -0,0 +1,5 @@
import { cnb, showMore } from './common'
const meta = await cnb.issue.getAliveMetadata('kevisual/kevisual', 34);
console.log('meta', showMore(meta));

43
test/issue-comment.ts Normal file
View File

@@ -0,0 +1,43 @@
import { Issue } from "../src/issue/index.ts";
import { token, showMore, cookie } from "./common.ts";
const issue = new Issue({
token: token,
cookie: cookie
});
const repo = "kevisual/dev-env";
const issueNumber = 5;
// 1. 查询评论列表
console.log("=== 1. 查询评论列表 ===");
const commentListRes = await issue.getCommentList(repo, issueNumber, {
page: 1,
page_size: 30
});
console.log(showMore(commentListRes));
// 2. 创建评论
console.log("\n=== 2. 创建评论 ===");
const createRes = await issue.createComment(repo, issueNumber, "测试评论内容 " + new Date().toISOString());
console.log(showMore(createRes));
// 如果创建成功,获取评论 ID
let commentId = null;
if (createRes.code === 200 && createRes.data?.id) {
commentId = createRes.data.id;
console.log("创建的评论 ID:", commentId);
// 3. 获取单个评论
console.log("\n=== 3. 获取单个评论 ===");
const getRes = await issue.getComment(repo, issueNumber, commentId);
console.log(showMore(getRes));
// 4. 修改评论
console.log("\n=== 4. 修改评论 ===");
const updateRes = await issue.updateComment(repo, issueNumber, commentId, "这是修改后的评论内容 " + new Date().toISOString());
console.log(showMore(updateRes));
}
const env = issue.useCommentEnv();
process.exit(0);

11
test/keep-cookie-get.ts Normal file
View File

@@ -0,0 +1,11 @@
// https://cnb.cool/kevisual/dev-env/-/workspace/vscode-web/cnb-708-1ji9sog7o-001
import { Build } from "../src/index.ts";
import { cnb, showMore } from "./common.ts";
const repo = 'kevisual/dev-env';
const pipelineId = 'cnb-708-1ji9sog7o-001';
const res = await cnb.workspace.getWorkspaceCookie(repo, pipelineId);
console.log('token', showMore(res));

19
test/keep-file-live.ts Normal file
View File

@@ -0,0 +1,19 @@
import { addKeepAliveData, createLiveData, getKeepAliveCache } from '../agent/routes/workspace/keep-file-live';
const repo = 'kevisual/dev-env';
const pipelineId = 'cnb-708-1ji9sog7o-001';
const testData = createLiveData({
wsUrl: "wss://cnb-708-1ji9sog7o-001.cnb.space:443?skipWebSocketFrames=false",
cookie: "orange:workspace:cookie-session:cnb-708-1ji9sog7o-001=3dc03d84-5617-4e44-a6b9-38ce4398aea5",
repo: repo,
pipelineId: pipelineId
});
addKeepAliveData(testData);
// 运行后可以在 ~/.cnb/kevisual_dev-env_cnb-708-1ji9sog7o-001.json 中看到保持存活的数据
// 同时可以通过 pm2 list 命令看到对应的保持存活的进程
// 注意:如果要测试停止保持存活,可以调用 stopLive(testData.pm2Name) 来停止对应的进程
// 例如stopLive('kevisual_dev-env_cnb-708-1ji9sog7o-001');

View File

@@ -1,13 +1,16 @@
import { createKeepAlive } from "@kevisual/cnb/keep"; import { createKeepAlive } from "@kevisual/cnb/keep";
// stable-9184b645cc7aa41b750e2f2ef956f2896512dd84 这个可以修改
// reconnectionToken 不能修改
// 但是可以删除 reconnectionToken=38837a9e-dd5a-4d28-9ec0-5e5b537a8b0f&skipWebSocketFrames=false
const config = { const config = {
"wss": "wss://cnb-dk4-1jgcjjqvc-001.cnb.space:443/stable-3c0b449c6e6e37b44a8a7938c0d8a3049926a64c?reconnectionToken=d70ab69b-5e92-471a-b3d2-31f554b468d4&reconnection=false&skipWebSocketFrames=false", "wss": "wss://cnb-708-1ji9sog7o-001.cnb.space:443?skipWebSocketFrames=false",
"cookie": "orange:workspace:cookie-session:cnb-dk4-1jgcjjqvc-001=01fea6db-d73f-4ce8-8929-36903ee7a266", "cookie": "orange:workspace:cookie-session:cnb-708-1ji9sog7o-001=3dc03d84-5617-4e44-a6b9-38ce4398aea5",
"url": "https://cnb-dk4-1jgcjjqvc-001.cnb.space/?folder=/workspace" "url": "https://cnb-708-1ji9sog7o-001.cnb.space/?folder=/workspace"
} }
createKeepAlive({ createKeepAlive({
wsUrl: config.wss, wsUrl: config.wss,
cookie: config.cookie, cookie: config.cookie,
debug: true, debug: true,
}); });

27
test/npc-test.ts Normal file
View File

@@ -0,0 +1,27 @@
import { fork } from 'node:child_process';
import path from 'node:path';
const runCmd = () => {
const filePath = path.resolve(process.cwd(), 'agent/npc.ts');
const child = fork(filePath, {
env: {
...process.env,
// CNB_COMMENT_BODY: '@kevisual/cnb(router) 我的kevisual/cnb的issues列表',
CNB_COMMENT_BODY: '关闭仓库的issue。kevisual/cnb/-/issues/5',
CNB_ISSUE_ID: '6',
CNB_ISSUE_TITLE: '托尔斯泰',
CNB_REPO_SLUG: 'kevisual/cnb',
},
stdio: 'inherit',
});
child.on('error', (err) => {
console.error('Error in child process:', err);
});
child.on('exit', (code) => {
console.log(`Child process exited with code ${code}`);
});
}
runCmd();

View File

@@ -4,10 +4,14 @@ import { token, showMore, cookie } from "./common.ts";
const user = new User({ token: token, cookie: cookie }); const user = new User({ token: token, cookie: cookie });
const currentUser = await user.getCurrentUser(); // const currentUser = await user.getCurrentUser();
console.log("currentUser", showMore(currentUser)); // console.log("currentUser", showMore(currentUser));
// const accessToken = await user.createAccessToken({ description: "Test Token from API" }); // const accessToken = await user.createAccessToken({ description: "Test Token from API" });
// console.log("accessToken", showMore(accessToken)); // console.log("accessToken", showMore(accessToken));
const tokenUser = await user.getUser();
console.log("tokenUser", showMore(tokenUser));

11
test/version.test.ts Normal file
View File

@@ -0,0 +1,11 @@
import { getCNBVersion } from '../src/index.ts';
import { describe, it, expect } from 'bun:test';
describe('getCNBVersion', () => {
it('should return version info', async () => {
const versionInfo = await getCNBVersion();
expect(versionInfo).toHaveProperty('version');
expect(versionInfo).toHaveProperty('commitID');
expect(versionInfo).toHaveProperty('hash');
});
});