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.
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import { createSkill, tool } from '@kevisual/router'
|
||||
import { app } from '../../app.ts'
|
||||
|
||||
if (!app.hasRoute('call')) {
|
||||
// "调用 path: cnb key: list-repos"
|
||||
app.route({
|
||||
// "调用 path: cnb key: list-repos"
|
||||
app.route({
|
||||
path: 'call',
|
||||
key: '',
|
||||
description: '调用',
|
||||
@@ -21,7 +20,7 @@ if (!app.hasRoute('call')) {
|
||||
}
|
||||
})
|
||||
},
|
||||
}).define(async (ctx) => {
|
||||
}).define(async (ctx) => {
|
||||
const { path, key } = ctx.query;
|
||||
console.log('call app', ctx.query);
|
||||
if (!path) {
|
||||
@@ -29,5 +28,4 @@ if (!app.hasRoute('call')) {
|
||||
}
|
||||
const res = await ctx.run({ path, key, payload: ctx.query.payload || {} });
|
||||
ctx.forward(res);
|
||||
}).addTo(app)
|
||||
}
|
||||
}).addTo(app, { overwrite: false })
|
||||
424
agent/routes/cnb-board/cnb-dev-env.ts
Normal file
424
agent/routes/cnb-board/cnb-dev-env.ts
Normal file
@@ -0,0 +1,424 @@
|
||||
import { app } from '../../app.ts';
|
||||
import { useKey } from '@kevisual/context'
|
||||
import { getLiveMdContent } from './live/live-content.ts';
|
||||
import z from 'zod';
|
||||
const notCNBCheck = (ctx: any) => {
|
||||
const isCNB = useKey('CNB');
|
||||
if (!isCNB) {
|
||||
ctx.body = {
|
||||
title: '非 cnb-board 环境',
|
||||
list: []
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
app.route({
|
||||
path: 'cnb_board',
|
||||
key: 'live',
|
||||
description: '获取cnb-board live的mdContent内容',
|
||||
middleware: ['auth-admin'],
|
||||
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-admin']
|
||||
}).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_name,group_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-admin']
|
||||
}).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 image,如:alpine: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);
|
||||
0
agent/routes/cnb-board/common.ts
Normal file
0
agent/routes/cnb-board/common.ts
Normal file
38
agent/routes/cnb-board/index.ts
Normal file
38
agent/routes/cnb-board/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { app } 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-admin'],
|
||||
}).define(async (ctx) => {
|
||||
const cmd = 'kill 1';
|
||||
execCommand(cmd);
|
||||
}).addTo(app);
|
||||
341
agent/routes/cnb-board/live/live-content.ts
Normal file
341
agent/routes/cnb-board/live/live-content.ts
Normal 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
|
||||
}
|
||||
1
agent/routes/cnb-board/modules/index.ts
Normal file
1
agent/routes/cnb-board/modules/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './is-cnb.ts';
|
||||
6
agent/routes/cnb-board/modules/is-cnb.ts
Normal file
6
agent/routes/cnb-board/modules/is-cnb.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useKey } from "@kevisual/context";
|
||||
|
||||
export const isCnb = () => {
|
||||
const CNB = useKey('CNB');
|
||||
return !!CNB;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import './call/index.ts'
|
||||
import './cnb-env/index.ts'
|
||||
import './knowledge/index.ts'
|
||||
import './issues/index.ts'
|
||||
import './cnb-board/index.ts';
|
||||
|
||||
/**
|
||||
* 验证上下文中的 App ID 是否与指定的 App ID 匹配
|
||||
@@ -25,25 +26,23 @@ const checkAppId = (ctx: any, appId: string) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!app.hasRoute('auth')) {
|
||||
app.route({
|
||||
app.route({
|
||||
id: 'auth',
|
||||
path: 'auth',
|
||||
}).define(async (ctx) => {
|
||||
}).define(async (ctx) => {
|
||||
// ctx.body = 'Auth Route';
|
||||
if (checkAppId(ctx, app.appId)) {
|
||||
return;
|
||||
}
|
||||
}).addTo(app);
|
||||
}).addTo(app, { overwrite: false });
|
||||
|
||||
app.route({
|
||||
app.route({
|
||||
id: 'admin-auth',
|
||||
path: 'admin-auth',
|
||||
middleware: ['auth'],
|
||||
}).define(async (ctx) => {
|
||||
}).define(async (ctx) => {
|
||||
// ctx.body = 'Admin Auth Route';
|
||||
if (checkAppId(ctx, app.appId)) {
|
||||
return;
|
||||
}
|
||||
}).addTo(app);
|
||||
}
|
||||
}).addTo(app, { overwrite: false });
|
||||
@@ -1,114 +0,0 @@
|
||||
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 keepAliveFilePath = path.join(os.homedir(), '.cnb/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();
|
||||
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: { wsUrl: string, cookie: string, repo: string, pipelineId: string }): KeepAliveData => {
|
||||
const { wsUrl, cookie, repo, pipelineId } = data;
|
||||
const createdTime = Date.now();
|
||||
const pm2Name = `${repo}__${pipelineId}`.replace(/\//g, '__');
|
||||
const filePath = path.join(os.homedir(), '.cnb', `${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;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { tool } from '@kevisual/router';
|
||||
import { app, cnb } from '../../app.ts';
|
||||
import { addKeepAliveData, KeepAliveData, removeKeepAliveData, createLiveData } from '../../../src/workspace/keep-file-live.ts';
|
||||
import { useKey } from '@kevisual/context';
|
||||
|
||||
// 保持工作空间存活技能
|
||||
app.route({
|
||||
@@ -75,3 +76,23 @@ app.route({
|
||||
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'cnb',
|
||||
key: 'keep-alive-current-workspace',
|
||||
description: '保持当前工作空间存活技能',
|
||||
middleware: ['admin-auth'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
skill: 'keep-alive-current-workspace',
|
||||
title: '保持当前工作空间存活',
|
||||
summary: '保持当前工作空间存活,防止被关闭或释放资源',
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const pipelineId = useKey('CNB_PIPELINE_ID');
|
||||
const repo = useKey('CNB_REPO_SLUG_LOWERCASE');
|
||||
if (!pipelineId || !repo) {
|
||||
ctx.throw(400, '当前环境缺少 CNB_PIPELINE_ID 或 CNB_REPO_SLUG_LOWERCASE 环境变量,无法保持工作空间存活');
|
||||
}
|
||||
const res = await app.run({ path: 'cnb', key: 'keep-workspace-alive', payload: { repo, pipelineId } }, ctx);
|
||||
ctx.forward(res);
|
||||
}).addTo(app);
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/cnb",
|
||||
"version": "0.0.33",
|
||||
"version": "0.0.34",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -19,14 +19,14 @@
|
||||
"packageManager": "pnpm@10.30.3",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@kevisual/ai": "^0.0.24",
|
||||
"@kevisual/ai": "^0.0.26",
|
||||
"@kevisual/code-builder": "^0.0.6",
|
||||
"@kevisual/dts": "^0.0.4",
|
||||
"@kevisual/context": "^0.0.8",
|
||||
"@kevisual/types": "^0.0.12",
|
||||
"@opencode-ai/plugin": "^1.2.15",
|
||||
"@types/bun": "^1.3.9",
|
||||
"@types/node": "^25.3.2",
|
||||
"@opencode-ai/plugin": "^1.2.16",
|
||||
"@types/bun": "^1.3.10",
|
||||
"@types/node": "^25.3.3",
|
||||
"@types/ws": "^8.18.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"dotenv": "^17.3.1"
|
||||
@@ -39,9 +39,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@kevisual/query": "^0.0.52",
|
||||
"@kevisual/router": "^0.0.84",
|
||||
"@kevisual/router": "^0.0.85",
|
||||
"@kevisual/use-config": "^1.0.30",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"es-toolkit": "^1.45.1",
|
||||
"nanoid": "^5.1.6",
|
||||
"unstorage": "^1.17.4",
|
||||
"ws": "npm:@kevisual/ws",
|
||||
|
||||
99
pnpm-lock.yaml
generated
99
pnpm-lock.yaml
generated
@@ -15,14 +15,14 @@ importers:
|
||||
specifier: ^0.0.52
|
||||
version: 0.0.52
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.84
|
||||
version: 0.0.84
|
||||
specifier: ^0.0.85
|
||||
version: 0.0.85
|
||||
'@kevisual/use-config':
|
||||
specifier: ^1.0.30
|
||||
version: 1.0.30(dotenv@17.3.1)
|
||||
es-toolkit:
|
||||
specifier: ^1.44.0
|
||||
version: 1.44.0
|
||||
specifier: ^1.45.1
|
||||
version: 1.45.1
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
@@ -37,8 +37,8 @@ importers:
|
||||
version: 4.3.6
|
||||
devDependencies:
|
||||
'@kevisual/ai':
|
||||
specifier: ^0.0.24
|
||||
version: 0.0.24
|
||||
specifier: ^0.0.26
|
||||
version: 0.0.26
|
||||
'@kevisual/code-builder':
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
@@ -52,14 +52,14 @@ importers:
|
||||
specifier: ^0.0.12
|
||||
version: 0.0.12
|
||||
'@opencode-ai/plugin':
|
||||
specifier: ^1.2.15
|
||||
version: 1.2.15
|
||||
specifier: ^1.2.16
|
||||
version: 1.2.16
|
||||
'@types/bun':
|
||||
specifier: ^1.3.9
|
||||
version: 1.3.9
|
||||
specifier: ^1.3.10
|
||||
version: 1.3.10
|
||||
'@types/node':
|
||||
specifier: ^25.3.2
|
||||
version: 25.3.2
|
||||
specifier: ^25.3.3
|
||||
version: 25.3.3
|
||||
'@types/ws':
|
||||
specifier: ^8.18.1
|
||||
version: 8.18.1
|
||||
@@ -83,8 +83,8 @@ packages:
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@kevisual/ai@0.0.24':
|
||||
resolution: {integrity: sha512-7jvZk1/L//VIClK7usuNgN4ZA9Etgbooka1Sj5quE/0UywR+NNnwqXVZ89Y1fBhI1TkhauDsdJBAtcQ7r/vbVw==}
|
||||
'@kevisual/ai@0.0.26':
|
||||
resolution: {integrity: sha512-lhaMpxi+vgqPdyBKiuNbSil4hy13tNLbDiqCtG0qUXKtvoowK6xMx269pSSYkYBivczM8g8I0XEouuJceUpJPg==}
|
||||
|
||||
'@kevisual/code-builder@0.0.6':
|
||||
resolution: {integrity: sha512-0aqATB31/yw4k4s5/xKnfr4DKbUnx8e3Z3BmKbiXTrc+CqWiWTdlGe9bKI9dZ2Df+xNp6g11W4xM2NICNyyCCw==}
|
||||
@@ -103,17 +103,14 @@ packages:
|
||||
'@kevisual/logger@0.0.4':
|
||||
resolution: {integrity: sha512-+fpr92eokSxoGOW1SIRl/27lPuO+zyY+feR5o2Q4YCNlAdt2x64NwC/w8r/3NEC5QenLgd4K0azyKTI2mHbARw==}
|
||||
|
||||
'@kevisual/permission@0.0.3':
|
||||
resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==}
|
||||
|
||||
'@kevisual/query@0.0.38':
|
||||
resolution: {integrity: sha512-bfvbSodsZyMfwY+1T2SvDeOCKsT/AaIxlVe0+B1R/fNhlg2MDq2CP0L9HKiFkEm+OXrvXcYDMKPUituVUM5J6Q==}
|
||||
'@kevisual/permission@0.0.4':
|
||||
resolution: {integrity: sha512-zwBYPnT/z21W4q2wkklJrxvoYBYWG/+a3iXFDKqXQAnDOcxm/SU1f1N6FQb9KxGKl36/fclVlhxlxqszvKCenQ==}
|
||||
|
||||
'@kevisual/query@0.0.52':
|
||||
resolution: {integrity: sha512-m1UbyDTIxtfAQXM+EqhXA4ytE2V8rV8mXTZVBwzfW9O6+gtvAcRY7K1YYxfewTSXLVh9nwvfHe0KQ8MDL5ukyw==}
|
||||
|
||||
'@kevisual/router@0.0.84':
|
||||
resolution: {integrity: sha512-l/TUFuqTJegB/S3FZQRBMUoz0Spvg8EzV3C/kBi/VO9KKCzjqZDVvhZJJbTQh9879CBY6vUy1ajo9WcLYnwbNA==}
|
||||
'@kevisual/router@0.0.85':
|
||||
resolution: {integrity: sha512-ihSzPXHOMSOnZD/+Eso4yZMt4MoUXyLdfRHhXJGg90+sJBr/BjsmgAokit4pI9gWU+Rs/3JqQ2/aqA43FHtGoA==}
|
||||
|
||||
'@kevisual/types@0.0.12':
|
||||
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
|
||||
@@ -127,11 +124,11 @@ packages:
|
||||
resolution: {integrity: sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@opencode-ai/plugin@1.2.15':
|
||||
resolution: {integrity: sha512-mh9S05W+CZZmo6q3uIEBubS66QVgiev7fRafX7vemrCfz+3pEIkSwipLjU/sxIewC9yLiDWLqS73DH/iEQzVDw==}
|
||||
'@opencode-ai/plugin@1.2.16':
|
||||
resolution: {integrity: sha512-9Kb7BQIC2P3oKCvI8K3thP5YP0vE7yLvcmBmgyACUIqc3e5UL6U+4umLpTvgQa2eQdjxtOXznuGTNwgcGMHUHg==}
|
||||
|
||||
'@opencode-ai/sdk@1.2.15':
|
||||
resolution: {integrity: sha512-NUJNlyBCdZ4R0EBLjJziEQOp2XbRPJosaMcTcWSWO5XJPKGUpz0u8ql+5cR8K+v2RJ+hp2NobtNwpjEYfe6BRQ==}
|
||||
'@opencode-ai/sdk@1.2.16':
|
||||
resolution: {integrity: sha512-y9ae9VnCcuog0GaI4DveX1HB6DBoZgGN3EuJVlRFbBCPwhzkls6fCfHSb5+VnTS6Fy0OWFUL28VBCmixL/D+/Q==}
|
||||
|
||||
'@rollup/plugin-commonjs@29.0.0':
|
||||
resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==}
|
||||
@@ -311,14 +308,14 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/bun@1.3.9':
|
||||
resolution: {integrity: sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw==}
|
||||
'@types/bun@1.3.10':
|
||||
resolution: {integrity: sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/node@25.3.2':
|
||||
resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==}
|
||||
'@types/node@25.3.3':
|
||||
resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==}
|
||||
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
@@ -330,8 +327,8 @@ packages:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
bun-types@1.3.9:
|
||||
resolution: {integrity: sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg==}
|
||||
bun-types@1.3.10:
|
||||
resolution: {integrity: sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg==}
|
||||
|
||||
chokidar@5.0.0:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
@@ -363,8 +360,8 @@ packages:
|
||||
resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
es-toolkit@1.44.0:
|
||||
resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
|
||||
es-toolkit@1.45.1:
|
||||
resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==}
|
||||
|
||||
estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
@@ -575,11 +572,11 @@ snapshots:
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@kevisual/ai@0.0.24':
|
||||
'@kevisual/ai@0.0.26':
|
||||
dependencies:
|
||||
'@kevisual/logger': 0.0.4
|
||||
'@kevisual/permission': 0.0.3
|
||||
'@kevisual/query': 0.0.38
|
||||
'@kevisual/permission': 0.0.4
|
||||
'@kevisual/query': 0.0.52
|
||||
|
||||
'@kevisual/code-builder@0.0.6': {}
|
||||
|
||||
@@ -602,17 +599,13 @@ snapshots:
|
||||
|
||||
'@kevisual/logger@0.0.4': {}
|
||||
|
||||
'@kevisual/permission@0.0.3': {}
|
||||
|
||||
'@kevisual/query@0.0.38':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
'@kevisual/permission@0.0.4': {}
|
||||
|
||||
'@kevisual/query@0.0.52': {}
|
||||
|
||||
'@kevisual/router@0.0.84':
|
||||
'@kevisual/router@0.0.85':
|
||||
dependencies:
|
||||
es-toolkit: 1.44.0
|
||||
es-toolkit: 1.45.1
|
||||
|
||||
'@kevisual/types@0.0.12': {}
|
||||
|
||||
@@ -623,12 +616,12 @@ snapshots:
|
||||
|
||||
'@kevisual/ws@8.19.0': {}
|
||||
|
||||
'@opencode-ai/plugin@1.2.15':
|
||||
'@opencode-ai/plugin@1.2.16':
|
||||
dependencies:
|
||||
'@opencode-ai/sdk': 1.2.15
|
||||
'@opencode-ai/sdk': 1.2.16
|
||||
zod: 4.3.6
|
||||
|
||||
'@opencode-ai/sdk@1.2.15': {}
|
||||
'@opencode-ai/sdk@1.2.16': {}
|
||||
|
||||
'@rollup/plugin-commonjs@29.0.0(rollup@4.57.1)':
|
||||
dependencies:
|
||||
@@ -744,13 +737,13 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.57.1':
|
||||
optional: true
|
||||
|
||||
'@types/bun@1.3.9':
|
||||
'@types/bun@1.3.10':
|
||||
dependencies:
|
||||
bun-types: 1.3.9
|
||||
bun-types: 1.3.10
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/node@25.3.2':
|
||||
'@types/node@25.3.3':
|
||||
dependencies:
|
||||
undici-types: 7.18.2
|
||||
|
||||
@@ -758,16 +751,16 @@ snapshots:
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 25.3.2
|
||||
'@types/node': 25.3.3
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
bun-types@1.3.9:
|
||||
bun-types@1.3.10:
|
||||
dependencies:
|
||||
'@types/node': 25.3.2
|
||||
'@types/node': 25.3.3
|
||||
|
||||
chokidar@5.0.0:
|
||||
dependencies:
|
||||
@@ -791,7 +784,7 @@ snapshots:
|
||||
|
||||
dotenv@17.3.1: {}
|
||||
|
||||
es-toolkit@1.44.0: {}
|
||||
es-toolkit@1.45.1: {}
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user