feat: 添加 cnb-board 路由及相关功能,获取 live 的 repo、构建、PR、NPC 和评论信息,并更新文档

fix: 更新 SKILL.md 文件格式,调整 metadata 标签位置
This commit is contained in:
2026-02-23 18:36:11 +08:00
parent 88313d5b8e
commit d6e3f67ac3
12 changed files with 982 additions and 25 deletions

View File

@@ -0,0 +1,262 @@
import { useKey } from "@kevisual/context"
import os from 'node:os';
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 vscodeWebUrl = useKey('CNB_VSCODE_WEB_URL') || ''
const TEMPLATE = `# 开发环境模式配置
### 服务访问地址
#### nginx 反向代理访问(推荐)
- OpenClaw: ${openclawUrl}
- OpenCode: ${opencodeUrl}
### 直接访问
- Kevisual: ${kevisualUrl}
- OpenCode: ${url?.replace('{{port}}', '4096')}
- VSCode Web: ${vscodeWebUrl}
### 密码访问
- OpenClaw: ${openclawUrlSecret}
- OpenCode: ${opencodeUrlSecret}
### 环境变量
- CNB_TOKEN: ${token}
### 其他说明
使用插件访问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
`
const labels = [
{
title: 'vscodeWebUrl',
value: vscodeWebUrl,
description: 'VSCode Web 的访问地址'
},
{
title: 'kevisualUrl',
value: kevisualUrl,
description: 'Kevisual 的访问地址,可以通过该地址访问 Kevisual 服务'
},
{
title: 'cnbTempToken',
value: token,
description: 'CNB 临时 Token保持和环境变量 CNB_TOKEN 一致'
},
{
title: 'openclawUrl',
value: openclawUrl,
description: 'OpenClaw 的访问地址,可以通过该地址访问 OpenClaw 服务'
},
{
title: 'openclawUrlSecret',
value: openclawUrlSecret,
description: 'OpenClaw 的访问地址,包含 token 参数,可以直接访问 OpenClaw 服务'
},
{
title: 'opencodeUrl',
value: opencodeUrl,
description: 'OpenCode 的访问地址,可以通过该地址访问 OpenCode 服务'
},
{
title: 'opencodeUrlSecret',
value: opencodeUrlSecret,
description: 'OpenCode 的访问地址,包含 token 参数,可以直接访问 OpenCode 服务'
},
{
title: 'docs',
value: TEMPLATE,
description: '开发环境模式配置说明文档'
}
]
const osInfoList = createOSInfo(more)
labels.push(...osInfoList)
return labels
}
const createOSInfo = (more = false) => {
const labels: Array<{ title: string; value: string; description: string }> = []
const startTimer = useKey('CNB_BUILD_START_TIME') || 0
// 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)
// 内存使用情况
const totalMem = os.totalmem()
const freeMem = os.freemem()
const usedMem = totalMem - freeMem
const memUsage = ((usedMem / totalMem) * 100).toFixed(2)
// 格式化字节为人类可读格式
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 bootTime = os.uptime()
const bootTimeDate = new Date(Date.now() - bootTime * 1000)
const bootTimeStr = dayjs(bootTimeDate).format('YYYY-MM-DD HH:mm:ss')
// 运行时间格式化
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)
return `${days}${hours}小时 ${minutes}分钟 ${secs}`
}
// 磁盘大小 (假设获取 / 目录)
// 注意: Node.js 原生不提供磁盘大小,需要通过 child_process 或假设值
// 这里使用内存作为参考,实际磁盘需要额外处理
const diskInfo = '可通过 df -h 命令获取'
labels.push(
{
title: 'cpuUsage',
value: `${cpuUsage}%`,
description: 'CPU 使用率'
},
{
title: 'cpuCores',
value: `${cpus.length}`,
description: 'CPU 核心数'
},
{
title: 'memoryUsed',
value: formatBytes(usedMem),
description: '已使用内存'
},
{
title: 'memoryTotal',
value: formatBytes(totalMem),
description: '总内存'
},
{
title: 'memoryUsage',
value: `${memUsage}%`,
description: '内存使用率'
},
{
title: 'diskInfo',
value: diskInfo,
description: '磁盘信息 (请使用 df -h 命令查看)'
},
{
title: 'bootTime',
value: bootTimeStr,
description: '系统启动时间'
},
{
title: 'uptime',
value: formatUptime(bootTime),
description: '系统运行时间'
}
)
// 如果有 CNB_BUILD_START_TIME添加构建启动时间
if (startTimer) {
const buildStartTime = dayjs(parseInt(startTimer as string)).format('YYYY-MM-DD HH:mm:ss')
const buildUptime = Date.now() - parseInt(startTimer as string)
const buildUptimeStr = formatUptime(Math.floor(buildUptime / 1000))
labels.push(
{
title: 'buildStartTime',
value: buildStartTime,
description: '构建启动时间'
},
{
title: 'buildUptime',
value: buildUptimeStr,
description: '构建已运行时间'
}
)
}
// more 为 true 时添加更多系统信息
if (more) {
const loadavg = os.loadavg()
labels.push(
{
title: 'hostname',
value: os.hostname(),
description: '主机名'
},
{
title: 'platform',
value: os.platform(),
description: '运行平台'
},
{
title: 'arch',
value: os.arch(),
description: '系统架构'
},
{
title: 'osType',
value: os.type(),
description: '操作系统类型'
},
{
title: 'loadavg1m',
value: loadavg[0].toFixed(2),
description: '系统负载 (1分钟)'
},
{
title: 'loadavg5m',
value: loadavg[1].toFixed(2),
description: '系统负载 (5分钟)'
},
{
title: 'loadavg15m',
value: loadavg[2].toFixed(2),
description: '系统负载 (15分钟)'
}
)
}
return labels
}