Compare commits

...

13 Commits

Author SHA1 Message Date
22ef776b27 feat: 限制远程应用最大重连次数为 50,增强错误日志信息;调整 cnb-board 路由逻辑 2026-02-24 23:53:21 +08:00
37e00a9ff3 feat: 添加非 cnb-board 环境检查,更新相关路由返回值;更新版本号至 0.1.12,新增 common.ts 文件 2026-02-24 23:44:05 +08:00
d3104bd5a5 feat: 根据 CNB 环境变量调整助手配置文件的 home 路径 2026-02-24 18:51:20 +08:00
xiongxiao
6795cef3d5 v0.1.11 2026-02-24 05:13:42 +08:00
dff858d820 feat: 添加获取 cnb-board live mdContent 内容的路由,修正时间戳处理 2026-02-24 02:12:05 +08:00
f67ca752f3 update 2026-02-24 01:39:59 +08:00
bda7fc3b92 feat: 更新依赖版本,添加 cnb-board 环境检查功能 2026-02-24 01:23:52 +08:00
58a0e9e61f feat: 添加 cnb-board 路由以支持退出程序功能 2026-02-23 18:56:20 +08:00
4b935036ad fix: 修正 OpenClaw 服务访问地址,添加 '/openclaw' 路径 2026-02-23 18:43:59 +08:00
8875c0ea3d fix: 更新版本号至 0.1.9,并升级 @inquirer/prompts 依赖至 8.3.0 2026-02-23 18:36:42 +08:00
d6e3f67ac3 feat: 添加 cnb-board 路由及相关功能,获取 live 的 repo、构建、PR、NPC 和评论信息,并更新文档
fix: 更新 SKILL.md 文件格式,调整 metadata 标签位置
2026-02-23 18:36:11 +08:00
xiongxiao
88313d5b8e fix: 更新 AssistantConfig 以启用共享功能,并优化本地用户检查逻辑 2026-02-22 00:20:59 +08:00
xiongxiao
e377557587 fix: 更新 @kevisual/api 依赖至 0.0.59,并优化本地用户检查逻辑 2026-02-22 00:07:02 +08:00
32 changed files with 1396 additions and 294 deletions

View File

@@ -4,8 +4,7 @@ include:
.common_env: &common_env .common_env: &common_env
env: env:
TO_REPO: kevisual/cli 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
@@ -16,29 +15,6 @@ $:
services: services:
- vscode - vscode
- docker - docker
env: !reference [.common_env, env]
imports: !reference [.common_env, imports] imports: !reference [.common_env, imports]
# 开发环境启动后会执行的任务 stages: !reference [.dev_template, stages]
# stages:
# - name: pnpm install
# script: pnpm install
stages: !reference [.dev_tempalte, stages]
.common_sync_to_gitea: &common_sync_to_gitea
- <<: *common_env
services: !reference [.common_sync_to_gitea_template, services]
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

View File

@@ -1,11 +0,0 @@
# .cnb/web_trigger.yml
branch:
# 如下按钮在分支名以 release 开头的分支详情页面显示
- reg: "^main"
buttons:
- name: 同步代码到gitea
description: 同步代码到gitea
event: web_trigger_sync_to_gitea
- name: 同步gitea代码到当前仓库
description: 同步gitea代码到当前仓库
event: web_trigger_sync_from_gitea

View File

@@ -1,6 +1,7 @@
--- ---
name: kill-opencode name: kill-opencode
description: 自动查找并杀死所有opencode相关的进程确保系统资源释放。 description: 自动查找并杀死所有opencode相关的进程确保系统资源释放。
metadata:
tags: tags:
- opencode - opencode
- process-management - process-management

View File

@@ -0,0 +1,77 @@
---
name: opencode-skill-creator
description: 教你如何在opencode中创建自定义skill
---
## Skill 创建指南
### 目录结构
每个skill都是一个文件夹包含以下文件
```
skills/
└── your-skill-name/
└── SKILL.md
```
### SKILL.md 文件格式
使用 YAML frontmatter 定义元数据:
```yaml
---
name: 技能名称
description: 技能描述
---
```
### Frontmatter 字段说明
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | 是 | 技能名称,用于显示在技能列表中 |
| `description` | 是 | 技能简短描述 |
### 技能内容规范
1. **描述要详细**清楚地说明这个skill能做什么
2. **提供示例**:包含实际使用的代码示例
3. **说明参数**:列出所有可用参数及其作用
4. **保持简洁**:避免冗长的说明
### 创建步骤
1.`/workspace/.opencode/skills/` 目录下创建新文件夹
2. 在文件夹中创建 `SKILL.md` 文件
3. 编写frontmatter和内容
4. 技能将自动被系统识别
### 示例
```yaml
---
name: 示例技能
description: 这是一个示例技能的描述
---
这是一个详细的技能说明文档。
## 使用方法
```bash
# 示例命令
echo "Hello World"
```
## 参数说明
- `param1`: 参数1说明
- `param2`: 参数2说明
```
### 注意事项
- 文件名必须是 `SKILL.md`(大写)
- frontmatter 必须使用 `---` 包裹
- 内容支持 Markdown 语法
- 可以包含代码块、表格、列表等

View File

@@ -0,0 +1,213 @@
---
name: router-creator
description: 教你如何使用 @kevisual/router 创建和管理路由
metadata:
tags:
- router
- api
- routes
---
# router
一个轻量级的路由框架,支持链式调用、中间件、嵌套路由等功能。
## 快速开始
```ts
import { App } from '@kevisual/router';
const app = new App();
app.listen(4002);
app
.route({ path: 'demo', key: '02' })
.define(async (ctx) => {
ctx.body = '02';
})
.addTo(app);
app
.route({ path: 'demo', key: '03' })
.define(async (ctx) => {
ctx.body = '03';
})
.addTo(app);
```
## 核心概念
### RouteContext 属性说明
在 route handler 中,你可以通过 `ctx` 访问以下属性:
| 属性 | 类型 | 说明 |
| --------------- | ---------------------------- | ---------------------------- |
| `query` | `object` | 请求参数,会自动合并 payload |
| `body` | `number \| string \| Object` | 响应内容 |
| `code` | `number` | 响应状态码,默认为 200 |
| `message` | `string` | 响应消息 |
| `state` | `any` | 状态数据,可在路由间传递 |
| `appId` | `string` | 应用标识 |
| `currentId` | `string` | 当前路由ID |
| `currentPath` | `string` | 当前路由路径 |
| `currentKey` | `string` | 当前路由 key |
| `currentRoute` | `Route` | 当前 Route 实例 |
| `progress` | `[string, string][]` | 路由执行路径记录 |
| `nextQuery` | `object` | 传递给下一个路由的参数 |
| `end` | `boolean` | 是否提前结束路由执行 |
| `app` | `QueryRouter` | 路由实例引用 |
| `error` | `any` | 错误信息 |
| `index` | `number` | 当前路由执行深度 |
| `needSerialize` | `boolean` | 是否需要序列化响应数据 |
### 上下文方法
| 方法 | 参数 | 说明 |
| ----------------------------------- | ----------------------------------------- | -------------------------------------------- |
| `ctx.call(msg, ctx?)` | `{ path, key?, payload?, ... } \| { id }` | 调用其他路由,返回完整 context |
| `ctx.run(msg, ctx?)` | `{ path, key?, payload? }` | 调用其他路由,返回 `{ code, data, message }` |
| `ctx.forward(res)` | `{ code, data?, message? }` | 设置响应结果 |
| `ctx.throw(code?, message?, tips?)` | - | 抛出自定义错误 |
## 完整示例
```ts
import { App } from '@kevisual/router';
import z from 'zod';
const app = new App();
app.listen(4002);
// 基本路由
app
.route({ path: 'user', key: 'info', id: 'user-info' })
.define(async (ctx) => {
// ctx.query 包含请求参数
const { id } = ctx.query;
// 使用 state 在路由间传递数据
ctx.state.orderId = '12345';
ctx.body = { id, name: '张三' };
ctx.code = 200;
})
.addTo(app);
app
.route({ path: 'order', key: 'pay', middleware: ['user-info'] })
.define(async (ctx) => {
// 可以获取前一个路由设置的 state
const { orderId } = ctx.state;
ctx.body = { orderId, status: 'paid' };
})
.addTo(app);
// 调用其他路由
app
.route({ path: 'dashboard', key: 'stats' })
.define(async (ctx) => {
// 调用 user/info 路由
const userRes = await ctx.run({ path: 'user', key: 'info', payload: { id: 1 } });
// 调用 product/list 路由
const productRes = await ctx.run({ path: 'product', key: 'list' });
ctx.body = {
user: userRes.data,
products: productRes.data,
};
})
.addTo(app);
// 使用 throw 抛出错误
app
.route({ path: 'admin', key: 'delete' })
.define(async (ctx) => {
const { id } = ctx.query;
if (!id) {
ctx.throw(400, '缺少参数', 'id is required');
}
ctx.body = { success: true };
})
.addTo(app);
```
## 中间件
```ts
import { App, Route } from '@kevisual/router';
const app = new App();
// 定义中间件
app
.route({
id: 'auth-example',
description: '权限校验中间件',
})
.define(async (ctx) => {
const token = ctx.query.token;
if (!token) {
ctx.throw(401, '未登录', '需要 token');
}
// 验证通过,设置用户信息到 state
ctx.state.tokenUser = { id: 1, name: '用户A' };
})
.addTo(app);
// 使用中间件(通过 id 引用)
app
.route({ path: 'admin', key: 'panel', middleware: ['auth-example'] })
.define(async (ctx) => {
// 可以访问中间件设置的 state
const { tokenUser } = ctx.state;
ctx.body = { tokenUser };
})
.addTo(app);
```
## 一个丰富的router示例
```ts
import { App } from '@kevisual/router';
const app = new App();
app
.router({
path: 'dog',
key: 'info',
description: '获取狗的信息',
metedata: {
args: {
owner: z.string().describe('狗主人姓名'),
age: z.number().describe('狗的年龄'),
},
},
})
.define(async (ctx) => {
const { owner, age } = ctx.query;
ctx.body = {
content: `这是一只${age}岁的狗,主人是${owner}`,
};
})
.addTo(app);
```
## 注意事项
1. **path 和 key 的组合是路由的唯一标识**,同一个 path+key 只能添加一个路由,后添加的会覆盖之前的。
2. **ctx.call vs ctx.run**
- `call` 返回完整 context包含所有属性
- `run` 返回 `{ code, data, message }` 格式data 即 body
3. **ctx.throw 会自动结束执行**,抛出自定义错误。
4. **payload 会自动合并到 query**,调用 `ctx.run({ path, key, payload })`payload 会合并到 query。
5. **nextQuery 用于传递给 nextRoute**,在当前路由中设置 `ctx.nextQuery`,会在执行 nextRoute 时合并到 query。
6. **避免 nextRoute 循环调用**,默认最大深度为 40 次,超过会返回 500 错误。
7. **needSerialize 默认为 true**,会自动对 body 进行 JSON 序列化和反序列化。
8. **progress 记录执行路径**,可用于调试和追踪路由调用链。
9. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。

View File

@@ -42,14 +42,14 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@inquirer/prompts": "^8.2.1", "@inquirer/prompts": "^8.3.0",
"@kevisual/ai": "^0.0.24", "@kevisual/ai": "^0.0.24",
"@kevisual/api": "^0.0.58", "@kevisual/api": "^0.0.59",
"@kevisual/load": "^0.0.6", "@kevisual/load": "^0.0.6",
"@kevisual/local-app-manager": "^0.1.32", "@kevisual/local-app-manager": "^0.1.32",
"@kevisual/logger": "^0.0.4", "@kevisual/logger": "^0.0.4",
"@kevisual/query": "0.0.49", "@kevisual/query": "0.0.49",
"@kevisual/router": "^0.0.83", "@kevisual/router": "^0.0.84",
"@kevisual/types": "^0.0.12", "@kevisual/types": "^0.0.12",
"@kevisual/use-config": "^1.0.30", "@kevisual/use-config": "^1.0.30",
"@opencode-ai/plugin": "^1.2.10", "@opencode-ai/plugin": "^1.2.10",

View File

@@ -380,6 +380,7 @@ export class AssistantConfig {
} }
protected getDefaultInitAssistantConfig() { protected getDefaultInitAssistantConfig() {
const id = randomId(); const id = randomId();
const isCNB = !!useKey('CNB');
return { return {
app: { app: {
url: 'https://kevisual.cn', url: 'https://kevisual.cn',
@@ -387,10 +388,10 @@ export class AssistantConfig {
}, },
description: '助手配置文件', description: '助手配置文件',
docs: "https://kevisual.cn/root/cli/docs/", docs: "https://kevisual.cn/root/cli/docs/",
home: '/root/home', home: isCNB ? '/root/cli-center' : '/root/home',
proxy: [], proxy: [],
share: { share: {
enabled: false, enabled: true,
url: 'https://kevisual.cn/ws/proxy', url: 'https://kevisual.cn/ws/proxy',
}, },
} as AssistantConfigData; } as AssistantConfigData;

View File

@@ -37,6 +37,23 @@ export class AssistantApp extends Manager {
this.config = config; this.config = config;
this.resolver = new ModuleResolver(config.configPath.configDir); this.resolver = new ModuleResolver(config.configPath.configDir);
} }
async init(opts?: { isServer?: boolean }) {
const manager = this;
await manager.load({ runtime: 'client' });
console.log('Assistant App Loaded');
await manager.checkLocalUser();
const token = await assistantQuery.getToken();
if (token) {
console.log('用户已登录,正在初始化远程应用连接...');
await manager.initRemoteApp({ token });
await manager.initRemoteApp();
await manager.initRouterProxyLightApp();
}
await manager.initRouterProxyApp();
if (opts?.isServer) {
await manager.initRoutes();
}
}
async pageList() { async pageList() {
const pages = await glob(['*/*/package.json'], { const pages = await glob(['*/*/package.json'], {
cwd: this.pagesPath, cwd: this.pagesPath,
@@ -99,7 +116,11 @@ export class AssistantApp extends Manager {
} }
let token = opts?.token; let token = opts?.token;
if (!token) { if (!token) {
token = await assistantQuery.queryLogin.getToken(); token = await assistantQuery.getToken();
}
if (!token) {
logger.error('[remote-app] cli当前未登录无法连接远程app');
return;
} }
let shareUrl = share?.url; let shareUrl = share?.url;
if (!shareUrl) { if (!shareUrl) {
@@ -118,7 +139,7 @@ export class AssistantApp extends Manager {
// 使用 RemoteApp 内置的自动重连机制 // 使用 RemoteApp 内置的自动重连机制
autoReconnect: true, autoReconnect: true,
reconnectDelay: 5000, // 首次重连延迟 5 秒 reconnectDelay: 5000, // 首次重连延迟 5 秒
maxReconnectAttempts: Infinity, // 无限重连 maxReconnectAttempts: 50, // 最大重连次数
enableBackoff: true, // 启用指数退避 enableBackoff: true, // 启用指数退避
}); });
remoteApp.isConnect(); remoteApp.isConnect();
@@ -140,7 +161,7 @@ export class AssistantApp extends Manager {
logger.info('[remote-app] 远程连接已关闭,自动重连机制正在处理...'); logger.info('[remote-app] 远程连接已关闭,自动重连机制正在处理...');
}); });
remoteApp.on('maxReconnectAttemptsReached', () => { remoteApp.on('maxReconnectAttemptsReached', () => {
logger.error('远程应用重连达到最大次数,停止重连'); logger.error('[remote-app] 远程应用重连达到最大次数,停止重连');
}); });
this.remoteApp = remoteApp; this.remoteApp = remoteApp;
} else { } else {
@@ -161,11 +182,10 @@ export class AssistantApp extends Manager {
* - lightcode: 轻代码模块配置 * - lightcode: 轻代码模块配置
* @returns * @returns
*/ */
async initRouterApp() { async initRouterProxyApp() {
const config = this.config.getConfig(); const config = this.config.getConfig();
const routerProxy = config?.router?.proxy || []; let routerProxy = config?.router?.proxy || [];
const base = config.router?.base ?? false; const base = config.router?.base ?? false;
const lightcode = config.router?.lightcode ?? true;
if (base) { if (base) {
routerProxy.push({ routerProxy.push({
type: 'router', type: 'router',
@@ -174,42 +194,13 @@ export class AssistantApp extends Manager {
} }
}) })
} }
if (lightcode) { routerProxy = routerProxy.filter(item => item.type === 'router');
routerProxy.push({
type: 'lightcode',
lightcode: {
id: 'main',
sync: 'remote',
rootPath: path.join(this.config.configPath.appsDir, 'light-code', 'code'),
}
})
}
if (routerProxy.length === 0) { if (routerProxy.length === 0) {
return return
} }
for (const proxyInfo of routerProxy) { for (const proxyInfo of routerProxy) {
if (proxyInfo.type !== 'router' && proxyInfo.type !== 'lightcode') { if (proxyInfo.type !== 'router') {
console.warn('路由的type必须是"router", 或者lightcode'); console.warn('路由的type必须是"router"');
continue;
}
if (proxyInfo.type === 'lightcode') {
const schema = z.object({
rootPath: z.string().describe('light-code 代码存放路径'),
sync: z.enum(['remote', 'local', 'both']).describe('同步方式remote: 仅从远程拉取local: 仅上传本地代码both: 双向同步').default('remote'),
});
const parseRes = schema.safeParse(proxyInfo.lightcode);
if (!parseRes.success) {
console.warn('lightcode 配置错误', parseRes.error);
continue;
}
const lightcodeConfig = parseRes.data;
initLightCode({
router: this.mainApp,
config: this.config,
sync: lightcodeConfig.sync,
rootPath: lightcodeConfig.rootPath,
});
continue; continue;
} }
const url = proxyInfo.router!.url; const url = proxyInfo.router!.url;
@@ -236,6 +227,49 @@ export class AssistantApp extends Manager {
} }
} }
} }
async initRouterProxyLightApp() {
const config = this.config.getConfig();
let routerProxy = config?.router?.proxy || [];
const lightcode = config.router?.lightcode ?? true;
if (lightcode) {
routerProxy.push({
type: 'lightcode',
lightcode: {
id: 'main',
sync: 'remote',
rootPath: path.join(this.config.configPath.appsDir, 'light-code', 'code'),
}
})
}
routerProxy = routerProxy.filter(item => item.type === 'lightcode');
if (routerProxy.length === 0) {
return
}
for (const proxyInfo of routerProxy) {
if (proxyInfo.type !== 'lightcode') {
console.warn('路由的type必须是"lightcode"');
continue;
}
const schema = z.object({
rootPath: z.string().describe('light-code 代码存放路径'),
sync: z.enum(['remote', 'local', 'both']).describe('同步方式remote: 仅从远程拉取local: 仅上传本地代码both: 双向同步').default('remote'),
});
const parseRes = schema.safeParse(proxyInfo.lightcode);
if (!parseRes.success) {
console.warn('lightcode 配置错误', parseRes.error);
continue;
}
const lightcodeConfig = parseRes.data;
await initLightCode({
router: this.mainApp,
config: this.config,
sync: lightcodeConfig.sync,
rootPath: lightcodeConfig.rootPath,
});
}
}
/** /**
* 动态加载文件插件模块的应用模块 * 动态加载文件插件模块的应用模块
* @info 不需要登录 * @info 不需要登录
@@ -265,34 +299,57 @@ export class AssistantApp extends Manager {
const checkLocalUser = async (opts: { assistantApp: AssistantApp }) => { const checkLocalUser = async (opts: { assistantApp: AssistantApp }) => {
const { assistantApp } = opts; const { assistantApp } = opts;
const config = assistantApp.config.getConfig(); const config = assistantApp.config.getConfig();
const auth = config?.auth; const auth = config?.auth || {};
let checkCNB = false; const isLogin = await assistantQuery.queryLogin.getToken();
if (!auth?.username) { logger.log('[assistant] 正在检查本地用户登录状态...', 'user', auth.username, '是否已登录', !!isLogin);
checkCNB = true;
// 没有登录过自动检测ci进行登录 const saveAuth = (auth: typeof config.auth, opts: { appId?: string, username: string; share: 'protected' | 'private' }) => {
// 检测条件1环境变量中存在 CNB_TOKEN auth.username = auth.username ?? opts.username;
} else { auth.share = auth.share ?? opts.share;
let temp = await assistantQuery.getToken()
logger.info('[assistant] 当前登录用户', auth.username, 'token有效性检查结果', !!temp);
}
const cnbToken = useKey('CNB_TOKEN');
if (!checkCNB && cnbToken) {
const res = await assistantQuery.queryLogin.loginByCnb({ cnbToken })
if (res.code === 200) {
logger.info('CNB登录成功用户信息已更新');
const userInfo = await assistantQuery.queryLogin.checkLocalUser()
auth.username = userInfo.username;
auth.share = 'protected'
const app = config?.app || {}; const app = config?.app || {};
if (!app?.id) { if (!app?.id) {
app.id = 'dev-cnb' app.id = opts.appId || 'dev-cnb'
} }
assistantApp.config.setConfig({ assistantApp.config.setConfig({
auth, auth,
app app
}); });
}
if (!auth?.username && isLogin) {
// 没有登录过自动检测ci进行登录
// 检测条件1环境变量中存在 CNB_API_KEY
// 检测条件2本地没有登录过
const userInfo = await assistantQuery.queryLogin.checkLocalUser()
saveAuth(auth, { username: userInfo.username, share: 'protected' })
return;
}
if (isLogin) return;
// 没有登录,尝试根据环境变量进行登录
// 尝试使用 CNB_API_KEY 登录, 并设置管理员用户
const cnbToken = useKey('CNB_API_KEY');
if (cnbToken) {
logger.info('[cnb]检测到 CNB_API_KEY正在尝试使用 CNB_API_KEY 登录...');
const res = await assistantQuery.queryLogin.loginByCnb({ cnbToken })
if (res.code === 200) {
const userInfo = await assistantQuery.queryLogin.checkLocalUser()
if (!userInfo.username) {
saveAuth(auth, { username: userInfo.username, share: 'protected' })
}
return
} else { } else {
console.error('CNB登录失败无法获取用户信息', res); logger.error('CNB登录失败无法获取用户信息', res);
}
}
const accessToken = useKey('KEVISUAL_TOKEN');
if (accessToken) {
logger.info('[cnb]检测到 KEVISUAL_TOKEN正在尝试使用 KEVISUAL_TOKEN 登录...');
const res = await assistantQuery.queryLogin.refreshLoginUser({ accessToken, refreshToken: '' })
if (res.code === 200) {
if (!auth.username) {
const userInfo = await assistantQuery.queryLogin.checkLocalUser()
saveAuth(auth, { username: userInfo.username, share: 'protected' })
}
} }
} }
} }

View File

@@ -37,10 +37,6 @@ export class AssistantQuery {
} }
async getToken() { async getToken() {
const token = await this.queryLogin.getToken(); const token = await this.queryLogin.getToken();
if (!token) return '';
const isExpired = await this.queryLogin.checkTokenValid()
console.log('Token 是否过期', isExpired, token);
console.log('info', this.queryLogin.cacheStore.cacheData)
return token; return token;
} }
} }

View File

@@ -5,10 +5,10 @@ import fs from 'node:fs';
import glob from 'fast-glob'; import glob from 'fast-glob';
import { runCode } from './run.ts'; import { runCode } from './run.ts';
const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2' const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2'
import { filter } from "@kevisual/js-filter";
import { getHash, getStringHash } from '../file-hash.ts'; import { getHash, getStringHash } from '../file-hash.ts';
import { AssistantConfig } from '@/lib.ts'; import { AssistantConfig } from '@/lib.ts';
import { assistantQuery } from '@/app.ts'; import { assistantQuery } from '@/app.ts';
import { logger } from '../logger.ts';
const codeDemo = `// 这是一个示例代码文件 const codeDemo = `// 这是一个示例代码文件
import {App} from '@kevisual/router'; import {App} from '@kevisual/router';
@@ -45,15 +45,19 @@ type LightCodeFile = {
id?: string, code?: string, hash?: string, filepath: string id?: string, code?: string, hash?: string, filepath: string
} }
export const initLightCode = async (opts: Opts) => { export const initLightCode = async (opts: Opts) => {
const token = await assistantQuery.getToken();
if (!token) {
logger.error('[light-code] 当前未登录,无法初始化 light-code');
return;
}
// 注册 light-code 路由 // 注册 light-code 路由
console.log('初始化 light-code 路由');
const config = opts.config as AssistantInit; const config = opts.config as AssistantInit;
const app = opts.router; const app = opts.router;
const token = await assistantQuery.getToken(); logger.log('[light-code] 初始化 light-code 路由');
const query = config.query; const query = config.query;
const sync = opts.sync ?? 'remote'; const sync = opts.sync ?? 'remote';
if (!config || !app) { if (!config || !app) {
console.error('[light-code] initLightCode 缺少必要参数, config 或 app'); logger.error('[light-code] initLightCode 缺少必要参数, config 或 app');
return; return;
} }
const lightcodeDir = opts.rootPath; const lightcodeDir = opts.rootPath;

View File

@@ -207,7 +207,7 @@ export class RemoteApp {
this.emitter.emit('message', data); this.emitter.emit('message', data);
} }
onError(error: any) { onError(error: any) {
console.error('远程应用错误:', this.id, error); console.error(`[remote-app] 远程应用错误: ${this.id}`, error);
this.isError = true; this.isError = true;
this.emitter.emit('error', error); this.emitter.emit('error', error);
} }

View File

@@ -6,7 +6,7 @@ app.route({
path: 'call', path: 'call',
key: '', key: '',
description: '调用', description: '调用',
middleware: ['auth'], middleware: ['auth-admin'],
metadata: { metadata: {
tags: ['opencode'], tags: ['opencode'],
...createSkill({ ...createSkill({
@@ -21,11 +21,11 @@ app.route({
}) })
}, },
}).define(async (ctx) => { }).define(async (ctx) => {
const { path, key = '' } = ctx.query; const { path, key = '' } = ctx.args;
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.args.payload || {} }, {
...ctx ...ctx
}); });
ctx.forward(res); ctx.forward(res);

View File

@@ -33,7 +33,8 @@ app.route({
title: '查看客户端 IP 地址', title: '查看客户端 IP 地址',
summary: '获取当前客户端的 IP 地址信息', summary: '获取当前客户端的 IP 地址信息',
}) })
} },
middleware: ['auth-admin']
}) })
.define(async (ctx) => { .define(async (ctx) => {
const networkInterfaces = os.networkInterfaces(); const networkInterfaces = os.networkInterfaces();

View File

@@ -76,10 +76,6 @@ app.route({
message: '客户端重启命令已执行', message: '客户端重启命令已执行',
}; };
} catch (error) { } catch (error) {
ctx.status = 500; ctx.throw(500, '重启客户端失败');
ctx.body = {
message: '重启客户端失败',
error: error.message,
};
} }
}).addTo(app); }).addTo(app);

View 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_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-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 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,29 @@
import { app } from '../../app.ts';
import './cnb-dev-env.ts';
import { execCommand } from '@/module/npm-install.ts';
import { useKey } from '@kevisual/context';
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);

View File

@@ -0,0 +1,340 @@
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}
### 直接访问
- 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.(直接对话opencode或者openclaw调用cnb-live技能即可)
`
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,
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; 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: buildUptimeStr,
description: '构建已运行时间'
}
)
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: formatUptime(Math.floor((maxRunTime - buildUptime) / 1000)) + ' ' + timeTo4Str,
description: '构建剩余时间'
})
}
}
// 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

@@ -28,9 +28,14 @@ app
app.route({ app.route({
path: 'config', path: 'config',
key: 'getId' key: 'getId',
description: '获取appId',
}).define(async (ctx) => { }).define(async (ctx) => {
const config = assistantConfig.getCacheAssistantConfig(); const config = assistantConfig.getCacheAssistantConfig();
ctx.body = config?.app?.id || null; const appId = config?.app?.id || null;
ctx.body = {
id: appId,
}
}).addTo(app); }).addTo(app);

View File

@@ -2,13 +2,14 @@ import { app, assistantConfig } from '../app.ts';
import './config/index.ts'; import './config/index.ts';
import './client/index.ts'; import './client/index.ts';
import './shop-install/index.ts'; import './shop-install/index.ts';
import './ai/index.ts'; // import './ai/index.ts';
import './user/index.ts'; import './user/index.ts';
import './call/index.ts' import './call/index.ts'
import './opencode/index.ts'; import './opencode/index.ts';
import './remote/index.ts'; import './remote/index.ts';
// import './kevisual/index.ts' // import './kevisual/index.ts'
import './cnb-board/index.ts';
import { authCache } from '@/module/cache/auth.ts'; import { authCache } from '@/module/cache/auth.ts';
@@ -70,7 +71,7 @@ export const checkAuth = async (ctx: any, isAdmin = false) => {
if (!auth.username) { if (!auth.username) {
// 初始管理员账号 // 初始管理员账号
auth.username = username; auth.username = username;
assistantConfig.setConfig({ auth, token: token }); assistantConfig.setConfig({ auth });
} }
if (isAdmin && auth.username) { if (isAdmin && auth.username) {
const admins = config.auth?.admin || []; const admins = config.auth?.admin || [];
@@ -78,12 +79,6 @@ export const checkAuth = async (ctx: any, isAdmin = false) => {
const admin = auth.username; const admin = auth.username;
if (admin === username) { if (admin === username) {
isCheckAdmin = true; isCheckAdmin = true;
const _token = config.token;
if (!_token) {
assistantConfig.setConfig({ token: token });
} else if (_token && _token.startsWith('st-') && _token !== token) {
assistantConfig.setConfig({ token: token });
}
} }
if (!isCheckAdmin && admins.length > 0 && admins.includes(username)) { if (!isCheckAdmin && admins.length > 0 && admins.includes(username)) {
isCheckAdmin = true; isCheckAdmin = true;
@@ -127,6 +122,7 @@ app
if (!ctx.query?.token && ctx.appId === app.appId) { if (!ctx.query?.token && ctx.appId === app.appId) {
return; return;
} }
ctx.state.isAdmin = true;
const authResult = await checkAuth(ctx, true); const authResult = await checkAuth(ctx, true);
if (authResult.code !== 200) { if (authResult.code !== 200) {
ctx.throw(authResult.code, authResult.message); ctx.throw(authResult.code, authResult.message);

View File

@@ -48,14 +48,7 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
]); ]);
const manager = useContextKey('manager', new AssistantApp(assistantConfig, app)); const manager = useContextKey('manager', new AssistantApp(assistantConfig, app));
setTimeout(async () => { setTimeout(async () => {
await manager.load({ runtime: 'client' }); manager.init({ isServer: runtime.isServer });
console.log('Assistant App Loaded');
await manager.checkLocalUser()
await manager.initRemoteApp();
await manager.initRouterApp();
if (runtime.isServer) {
await manager.initRoutes();
}
}, 1000); }, 1000);
return { return {

View File

@@ -9,11 +9,13 @@ import type { WebSocketListenerFun } from "@kevisual/router";
import WebSocket from 'ws'; import WebSocket from 'ws';
import { renderNoAuthAndLogin } from '@/module/assistant/html/login.ts'; import { renderNoAuthAndLogin } from '@/module/assistant/html/login.ts';
import { LiveCode } from '@/module/livecode/index.ts'; import { LiveCode } from '@/module/livecode/index.ts';
import { isCnb } from '@/routes/cnb-board/modules/is-cnb.ts';
import { is } from 'zod/v4/locales';
const localProxy = new LocalProxy({}); const localProxy = new LocalProxy({});
localProxy.initFromAssistantConfig(assistantConfig); localProxy.initFromAssistantConfig(assistantConfig);
const isOpenPath = (pathname: string): boolean => { const isOpenPath = (pathname: string): boolean => {
const openPaths = ['/root/home', '/root/cli', '/root/login']; const openPaths = ['/root/home', '/root/cli', '/root/login', '/root/cli-center'];
for (const openPath of openPaths) { for (const openPath of openPaths) {
if (pathname.startsWith(openPath)) { if (pathname.startsWith(openPath)) {
return true; return true;
@@ -81,12 +83,24 @@ const authFilter = async (req: http.IncomingMessage, res: http.ServerResponse) =
} }
export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => { export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => {
const _assistantConfig = assistantConfig.getCacheAssistantConfig(); const _assistantConfig = assistantConfig.getCacheAssistantConfig();
const home = _assistantConfig?.home || '/root/home'; let home = _assistantConfig?.home
const auth = _assistantConfig?.auth || {}; const auth = _assistantConfig?.auth || {};
// 没有管理员,需要去登陆
let noAdmin = !auth.username; let noAdmin = !auth.username;
if (!home) {
if (isCnb()) {
home = '/root/cli-center/cnb-board'
} else {
home = '/root/cli-center/';
}
} else {
if (!home.startsWith('/')) {
home = '/' + home;
}
}
const toSetting = () => { const toLogin = (redirect?: string) => {
res.writeHead(302, { Location: `/root/cli-center/` }); res.writeHead(302, { Location: `/root/login/` + (redirect ? `?redirect=${encodeURIComponent(redirect)}` : '') });
res.end(); res.end();
return true; return true;
} }
@@ -94,10 +108,11 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
const pathname = decodeURIComponent(url.pathname); const pathname = decodeURIComponent(url.pathname);
if (pathname === '/') { if (pathname === '/') {
if (noAdmin) { if (noAdmin) {
return toSetting(); return toLogin(home + '/');
} }
res.writeHead(302, { Location: `${home}/` }); res.writeHead(302, { Location: home });
return res.end(); res.end();
return
} }
if (pathname.startsWith('/favicon.ico')) { if (pathname.startsWith('/favicon.ico')) {
res.statusCode = 404; res.statusCode = 404;
@@ -162,8 +177,9 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
} }
const isOpen = isOpenPath(pathname) const isOpen = isOpenPath(pathname)
logger.debug('proxyRoute', { _user, _app, pathname, noAdmin, isOpen }); logger.debug('proxyRoute', { _user, _app, pathname, noAdmin, isOpen });
// 没有管理员,且不是开放路径,去登录
if (noAdmin && !isOpen) { if (noAdmin && !isOpen) {
return toSetting(); return toLogin();
} }
if (_app && urls.length === 3) { if (_app && urls.length === 3) {
// 重定向到 // 重定向到

40
assistant/src/test/cnb.ts Normal file
View File

@@ -0,0 +1,40 @@
import { CNB } from '@kevisual/cnb'
import { useKey } from '@kevisual/context'
import { QueryLoginNode } from '@kevisual/api/query-login-node'
import { Query } from '@kevisual/query'
const queryLogin = new QueryLoginNode({
query: new Query({ url: 'https://kevisual.cn/api/router' })
})
await queryLogin.cacheStore.init()
const cnb = new CNB({
token: useKey('CNB_TOKEN'),
})
// export const cnbLogin = async () => {
// const userInfo = await cnb.user.getUser()
// console.log('CNB用户信息', userInfo);
// }
// cnbLogin()
const testCnbLogin = async () => {
const res = await queryLogin.loginByCnb({
cnbToken: useKey('CNB_TOKEN') || '',
})
console.log('CNB登录结果', res);
if (res.code === 200) {
const userInfo = await queryLogin.checkLocalUser()
console.log('CNB登录成功用户信息', userInfo);
} else {
console.log('CNB登录失败', res);
}
}
// testCnbLogin()
const getCNBLocalUser = async () => {
const res = await queryLogin.checkLocalUser()
console.log('查询本地用户信息', res);
const token = await queryLogin.getToken()
console.log('检查token是否过期', token);
}
getCNBLocalUser()

View File

@@ -1,3 +0,0 @@
# envision-cli
## 上传文件

View File

@@ -1,5 +0,0 @@
{
"wss": "wss://cnb-pf8-1jhgvbrkm-001.cnb.space:443/stable-3c0b449c6e6e37b44a8a7938c0d8a3049926a64c?reconnectionToken=3d90027f-b2b1-4c60-a3b4-f061b75ec073&reconnection=false&skipWebSocketFrames=false",
"cookie": "orange:workspace:cookie-session:cnb-pf8-1jhgvbrkm-001=9cc870da-d3d5-44ee-afdc-7498e1111186",
"url": "https://cnb-pf8-1jhgvbrkm-001.cnb.space/?folder=/workspace"
}

View File

@@ -1,34 +0,0 @@
{
"metadata": {
"name": "kevisual",
"share": "public"
},
"checkDir": {
"./build/tools/kevisual-sync": {
"url": "https://kevisual.xiongxiao.me/root/ai/kevisual/tools/kevisual-sync/",
"enabled": true
}
},
"syncDirectory": [
{
"files": [
"build/**/*"
],
"ignore": [
"build/ignore.md"
],
"registry": "https://kevisual.xiongxiao.me/root/ai/kevisual",
"replace": {
"build/": ""
}
}
],
"sync": {
"./build/01-summary.md": {
"url": "https://kevisual.xiongxiao.me/root/ai/kevisual/01-summary.md",
"meta": {
"share": "public"
}
}
}
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@kevisual/cli", "name": "@kevisual/cli",
"version": "0.1.8", "version": "0.1.12",
"description": "envision 命令行工具", "description": "envision 命令行工具",
"type": "module", "type": "module",
"basename": "/root/cli", "basename": "/root/cli",
@@ -41,7 +41,7 @@
], ],
"author": "abearxiong", "author": "abearxiong",
"dependencies": { "dependencies": {
"@inquirer/prompts": "^8.2.1", "@inquirer/prompts": "^8.3.0",
"@kevisual/app": "^0.0.2", "@kevisual/app": "^0.0.2",
"@kevisual/auth": "^2.0.3", "@kevisual/auth": "^2.0.3",
"@kevisual/context": "^0.0.8", "@kevisual/context": "^0.0.8",
@@ -59,7 +59,7 @@
"unstorage": "^1.17.4" "unstorage": "^1.17.4"
}, },
"devDependencies": { "devDependencies": {
"@kevisual/api": "^0.0.58", "@kevisual/api": "^0.0.59",
"@kevisual/cnb": "^0.0.28", "@kevisual/cnb": "^0.0.28",
"@kevisual/dts": "^0.0.4", "@kevisual/dts": "^0.0.4",
"@kevisual/load": "^0.0.6", "@kevisual/load": "^0.0.6",

169
pnpm-lock.yaml generated
View File

@@ -9,8 +9,8 @@ importers:
.: .:
dependencies: dependencies:
'@inquirer/prompts': '@inquirer/prompts':
specifier: ^8.2.1 specifier: ^8.3.0
version: 8.2.1(@types/node@25.3.0) version: 8.3.0(@types/node@25.3.0)
'@kevisual/app': '@kevisual/app':
specifier: ^0.0.2 specifier: ^0.0.2
version: 0.0.2(dotenv@17.3.1) version: 0.0.2(dotenv@17.3.1)
@@ -58,8 +58,8 @@ importers:
version: 1.17.4(idb-keyval@6.2.2)(ioredis@5.9.3(supports-color@10.2.2)) version: 1.17.4(idb-keyval@6.2.2)(ioredis@5.9.3(supports-color@10.2.2))
devDependencies: devDependencies:
'@kevisual/api': '@kevisual/api':
specifier: ^0.0.58 specifier: ^0.0.59
version: 0.0.58(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 0.0.59(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@kevisual/cnb': '@kevisual/cnb':
specifier: ^0.0.28 specifier: ^0.0.28
version: 0.0.28(dotenv@17.3.1)(idb-keyval@6.2.2)(ioredis@5.9.3) version: 0.0.28(dotenv@17.3.1)(idb-keyval@6.2.2)(ioredis@5.9.3)
@@ -164,14 +164,14 @@ importers:
version: 4.3.6 version: 4.3.6
devDependencies: devDependencies:
'@inquirer/prompts': '@inquirer/prompts':
specifier: ^8.2.1 specifier: ^8.3.0
version: 8.2.1(@types/node@25.3.0) version: 8.3.0(@types/node@25.3.0)
'@kevisual/ai': '@kevisual/ai':
specifier: ^0.0.24 specifier: ^0.0.24
version: 0.0.24 version: 0.0.24
'@kevisual/api': '@kevisual/api':
specifier: ^0.0.58 specifier: ^0.0.59
version: 0.0.58(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) version: 0.0.59(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@kevisual/load': '@kevisual/load':
specifier: ^0.0.6 specifier: ^0.0.6
version: 0.0.6 version: 0.0.6
@@ -185,8 +185,8 @@ importers:
specifier: 0.0.49 specifier: 0.0.49
version: 0.0.49 version: 0.0.49
'@kevisual/router': '@kevisual/router':
specifier: ^0.0.83 specifier: ^0.0.84
version: 0.0.83 version: 0.0.84
'@kevisual/types': '@kevisual/types':
specifier: ^0.0.12 specifier: ^0.0.12
version: 0.0.12 version: 0.0.12
@@ -1128,8 +1128,8 @@ packages:
resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==} resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
'@inquirer/checkbox@5.0.6': '@inquirer/checkbox@5.1.0':
resolution: {integrity: sha512-qLZ1gOpsqsieB5k98GQ9bWYggvMsCXTc7HUwhEQpTsxFQYGthqR9UysCwqB7L9h47THYdXhJegnYb1IqURMjng==} resolution: {integrity: sha512-/HjF1LN0a1h4/OFsbGKHNDtWICFU/dqXCdym719HFTyJo9IG7Otr+ziGWc9S0iQuohRZllh+WprSgd5UW5Fw0g==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1137,8 +1137,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/confirm@6.0.6': '@inquirer/confirm@6.0.8':
resolution: {integrity: sha512-9ZkrGYiWnOKQPc3xfLIORE3lZW1qvtgRoJcoqopr5zssBn7yk4yONmzGynEOjc16FnUXzkAejj/I29BbfcoUfQ==} resolution: {integrity: sha512-Di6dgmiZ9xCSUxWUReWTqDtbhXCuG2MQm2xmgSAIruzQzBqNf49b8E07/vbCYY506kDe8BiwJbegXweG8M1klw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1146,8 +1146,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/core@11.1.3': '@inquirer/core@11.1.5':
resolution: {integrity: sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw==} resolution: {integrity: sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1155,8 +1155,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/editor@5.0.6': '@inquirer/editor@5.0.8':
resolution: {integrity: sha512-dxTi/TB29NaW18u0pQl3B140695izGUMzr340a4Yhxll3oa0/iwxl6C88sX9LDUPFaaM4FDASEMnLm8XVk2VVg==} resolution: {integrity: sha512-sLcpbb9B3XqUEGrj1N66KwhDhEckzZ4nI/W6SvLXyBX8Wic3LDLENlWRvkOGpCPoserabe+MxQkpiMoI8irvyA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1164,8 +1164,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/expand@5.0.6': '@inquirer/expand@5.0.8':
resolution: {integrity: sha512-HmgMzFdMk/gmPXfuFy4xgWkyIVbdH81otQkrFbhklFZcGauwDFD1EbgmZdgmYCN5pWhSEnYIadg1kysLgPIYag==} resolution: {integrity: sha512-QieW3F1prNw3j+hxO7/NKkG1pk3oz7pOB6+5Upwu3OIwADfPX0oZVppsqlL+Vl/uBHHDSOBY0BirLctLnXwGGg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1186,8 +1186,8 @@ packages:
resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==} resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
'@inquirer/input@5.0.6': '@inquirer/input@5.0.8':
resolution: {integrity: sha512-RZsJcjMJA3QNI9q9OiAi1fAom+Pb8on6alJB1Teh5jjKaiG5C79P69cG955ZRfgPdxTmI4uyhf33+94Xj7xWig==} resolution: {integrity: sha512-p0IJslw0AmedLEkOU+yrEX3Aj2RTpQq7ZOf8nc1DIhjzaxRWrrgeuE5Kyh39fVRgtcACaMXx/9WNo8+GjgBOfw==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1195,8 +1195,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/number@4.0.6': '@inquirer/number@4.0.8':
resolution: {integrity: sha512-owMkAY+gR0BggomDTL+Z22x/yfE4ocFrmNyJacOiaDVA/d+iL4IWyk7Ds7JEuDMxuhHFB46Dubdxg1uiD7GlCA==} resolution: {integrity: sha512-uGLiQah9A0F9UIvJBX52m0CnqtLaym0WpT9V4YZrjZ+YRDKZdwwoEPz06N6w8ChE2lrnsdyhY9sL+Y690Kh9gQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1204,8 +1204,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/password@5.0.6': '@inquirer/password@5.0.8':
resolution: {integrity: sha512-c4BT4SB79iYwPhtGVBSvrlTnn4oFSYnwocafmktpay8RK75T2c2+fLlR0i1Cxw0QOhdy/YULdmpHoy1sOrPzvA==} resolution: {integrity: sha512-zt1sF4lYLdvPqvmvHdmjOzuUUjuCQ897pdUCO8RbXMUDKXJTTyOQgtn23le+jwcb+MpHl3VAFvzIdxRAf6aPlA==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1213,8 +1213,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/prompts@8.2.1': '@inquirer/prompts@8.3.0':
resolution: {integrity: sha512-76knJFW2oXdI6If5YRmEoT5u7l+QroXYrMiINFcb97LsyECgsbO9m6iWlPuhBtaFgNITPHQCk3wbex38q8gsjg==} resolution: {integrity: sha512-JAj66kjdH/F1+B7LCigjARbwstt3SNUOSzMdjpsvwJmzunK88gJeXmcm95L9nw1KynvFVuY4SzXh/3Y0lvtgSg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1222,8 +1222,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/rawlist@5.2.2': '@inquirer/rawlist@5.2.4':
resolution: {integrity: sha512-ld2EhLlf3fsBv7QfxR31NdBecGdS6eeFFZ+Nx88ApjtifeCEc9TNrw8x5tGe+gd6HG1ERczOb4B/bMojiGIp1g==} resolution: {integrity: sha512-fTuJ5Cq9W286isLxwj6GGyfTjx1Zdk4qppVEPexFuA6yioCCXS4V1zfKroQqw7QdbDPN73xs2DiIAlo55+kBqg==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1231,8 +1231,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/search@4.1.2': '@inquirer/search@4.1.4':
resolution: {integrity: sha512-kdGbbbWYKldWxpxodKYPmFl/ctBi3DjWlA4LX48jXtqJ7NEeoEKlyFTbE4xNEFcGDi15tvaxRLzCV4A53zqYIw==} resolution: {integrity: sha512-9yPTxq7LPmYjrGn3DRuaPuPbmC6u3fiWcsE9ggfLcdgO/ICHYgxq7mEy1yJ39brVvgXhtOtvDVjDh9slJxE4LQ==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1240,8 +1240,8 @@ packages:
'@types/node': '@types/node':
optional: true optional: true
'@inquirer/select@5.0.6': '@inquirer/select@5.1.0':
resolution: {integrity: sha512-9DyVbNCo4q0C3CkGd6zW0SW3NQuuk4Hy0NSbP6zErz2YNWF4EHHJCRzcV34/CDQLraeAQXbHYlMofuUrs6BBZQ==} resolution: {integrity: sha512-OyYbKnchS1u+zRe14LpYrN8S0wH1vD0p2yKISvSsJdH2TpI87fh4eZdWnpdbrGauCRWDph3NwxRmM4Pcm/hx1Q==}
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
peerDependencies: peerDependencies:
'@types/node': '>=18' '@types/node': '>=18'
@@ -1290,8 +1290,8 @@ packages:
'@kevisual/api@0.0.28': '@kevisual/api@0.0.28':
resolution: {integrity: sha512-WQluRlu2qGM1qktIhPLODie8x382a6jEMfFOcay/rnkCgXK0BRpnqOKwlX7IMLdMqka7GY/BD69kSMnK1Exf5g==} resolution: {integrity: sha512-WQluRlu2qGM1qktIhPLODie8x382a6jEMfFOcay/rnkCgXK0BRpnqOKwlX7IMLdMqka7GY/BD69kSMnK1Exf5g==}
'@kevisual/api@0.0.58': '@kevisual/api@0.0.59':
resolution: {integrity: sha512-ontJswmD5LS4EjYZDGF/SI1oYI1/zCM6ovC8rviNzxI22294yQ5y8Lb/rYV2qsKaxJbC3uuOjjsjPWap0nSvAQ==} resolution: {integrity: sha512-2w6GBG2mS92dz8afB0hLfjTw8lBGD5oXU5bw/QglFugHHp24fISZkEW1Hc+jP/jOiYlMzphws2/31DIXdDoGkg==}
'@kevisual/app@0.0.1': '@kevisual/app@0.0.1':
resolution: {integrity: sha512-PEx8P3l0iNSqrz9Ib9kVCYfqNMX6/LfNu+cEafmY6ECP1cV5Vmv+TH2fuasMosKjtbH2fAdDi97sbd29tdEK+g==} resolution: {integrity: sha512-PEx8P3l0iNSqrz9Ib9kVCYfqNMX6/LfNu+cEafmY6ECP1cV5Vmv+TH2fuasMosKjtbH2fAdDi97sbd29tdEK+g==}
@@ -1363,8 +1363,8 @@ packages:
'@kevisual/router@0.0.80': '@kevisual/router@0.0.80':
resolution: {integrity: sha512-rVwi6Yf411bnNm2x94lMm+s4Csw0Yb7u/aj+VJJ59iouAYhjLuL7Rs1EcARhnQf47cegBJi6zozfGHgLsLHN2w==} resolution: {integrity: sha512-rVwi6Yf411bnNm2x94lMm+s4Csw0Yb7u/aj+VJJ59iouAYhjLuL7Rs1EcARhnQf47cegBJi6zozfGHgLsLHN2w==}
'@kevisual/router@0.0.83': '@kevisual/router@0.0.84':
resolution: {integrity: sha512-CVazzM1rXVyvU7QcMQr0/EuqacRNEGalThDDLGQcvKEVHyduJ9yWddn6kezgWFCpNlPKhzSCKkIFuZVixNVxDQ==} resolution: {integrity: sha512-l/TUFuqTJegB/S3FZQRBMUoz0Spvg8EzV3C/kBi/VO9KKCzjqZDVvhZJJbTQh9879CBY6vUy1ajo9WcLYnwbNA==}
'@kevisual/types@0.0.12': '@kevisual/types@0.0.12':
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==} resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
@@ -3036,15 +3036,6 @@ packages:
supports-color: supports-color:
optional: true optional: true
debug@4.4.0:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.3: debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
@@ -6445,23 +6436,23 @@ snapshots:
'@inquirer/ansi@2.0.3': {} '@inquirer/ansi@2.0.3': {}
'@inquirer/checkbox@5.0.6(@types/node@25.3.0)': '@inquirer/checkbox@5.1.0(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/ansi': 2.0.3 '@inquirer/ansi': 2.0.3
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/figures': 2.0.3 '@inquirer/figures': 2.0.3
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/confirm@6.0.6(@types/node@25.3.0)': '@inquirer/confirm@6.0.8(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/core@11.1.3(@types/node@25.3.0)': '@inquirer/core@11.1.5(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/ansi': 2.0.3 '@inquirer/ansi': 2.0.3
'@inquirer/figures': 2.0.3 '@inquirer/figures': 2.0.3
@@ -6473,17 +6464,17 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/editor@5.0.6(@types/node@25.3.0)': '@inquirer/editor@5.0.8(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/external-editor': 2.0.3(@types/node@25.3.0) '@inquirer/external-editor': 2.0.3(@types/node@25.3.0)
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/expand@5.0.6(@types/node@25.3.0)': '@inquirer/expand@5.0.8(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
@@ -6497,62 +6488,62 @@ snapshots:
'@inquirer/figures@2.0.3': {} '@inquirer/figures@2.0.3': {}
'@inquirer/input@5.0.6(@types/node@25.3.0)': '@inquirer/input@5.0.8(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/number@4.0.6(@types/node@25.3.0)': '@inquirer/number@4.0.8(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/password@5.0.6(@types/node@25.3.0)': '@inquirer/password@5.0.8(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/ansi': 2.0.3 '@inquirer/ansi': 2.0.3
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/prompts@8.2.1(@types/node@25.3.0)': '@inquirer/prompts@8.3.0(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/checkbox': 5.0.6(@types/node@25.3.0) '@inquirer/checkbox': 5.1.0(@types/node@25.3.0)
'@inquirer/confirm': 6.0.6(@types/node@25.3.0) '@inquirer/confirm': 6.0.8(@types/node@25.3.0)
'@inquirer/editor': 5.0.6(@types/node@25.3.0) '@inquirer/editor': 5.0.8(@types/node@25.3.0)
'@inquirer/expand': 5.0.6(@types/node@25.3.0) '@inquirer/expand': 5.0.8(@types/node@25.3.0)
'@inquirer/input': 5.0.6(@types/node@25.3.0) '@inquirer/input': 5.0.8(@types/node@25.3.0)
'@inquirer/number': 4.0.6(@types/node@25.3.0) '@inquirer/number': 4.0.8(@types/node@25.3.0)
'@inquirer/password': 5.0.6(@types/node@25.3.0) '@inquirer/password': 5.0.8(@types/node@25.3.0)
'@inquirer/rawlist': 5.2.2(@types/node@25.3.0) '@inquirer/rawlist': 5.2.4(@types/node@25.3.0)
'@inquirer/search': 4.1.2(@types/node@25.3.0) '@inquirer/search': 4.1.4(@types/node@25.3.0)
'@inquirer/select': 5.0.6(@types/node@25.3.0) '@inquirer/select': 5.1.0(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/rawlist@5.2.2(@types/node@25.3.0)': '@inquirer/rawlist@5.2.4(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/search@4.1.2(@types/node@25.3.0)': '@inquirer/search@4.1.4(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/figures': 2.0.3 '@inquirer/figures': 2.0.3
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
'@types/node': 25.3.0 '@types/node': 25.3.0
'@inquirer/select@5.0.6(@types/node@25.3.0)': '@inquirer/select@5.1.0(@types/node@25.3.0)':
dependencies: dependencies:
'@inquirer/ansi': 2.0.3 '@inquirer/ansi': 2.0.3
'@inquirer/core': 11.1.3(@types/node@25.3.0) '@inquirer/core': 11.1.5(@types/node@25.3.0)
'@inquirer/figures': 2.0.3 '@inquirer/figures': 2.0.3
'@inquirer/type': 4.0.3(@types/node@25.3.0) '@inquirer/type': 4.0.3(@types/node@25.3.0)
optionalDependencies: optionalDependencies:
@@ -6609,7 +6600,7 @@ snapshots:
fuse.js: 7.1.0 fuse.js: 7.1.0
nanoid: 5.1.6 nanoid: 5.1.6
'@kevisual/api@0.0.58(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': '@kevisual/api@0.0.59(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@kevisual/context': 0.0.8 '@kevisual/context': 0.0.8
'@kevisual/js-filter': 0.0.5 '@kevisual/js-filter': 0.0.5
@@ -6809,7 +6800,7 @@ snapshots:
dependencies: dependencies:
es-toolkit: 1.44.0 es-toolkit: 1.44.0
'@kevisual/router@0.0.83': '@kevisual/router@0.0.84':
dependencies: dependencies:
es-toolkit: 1.44.0 es-toolkit: 1.44.0
@@ -8850,12 +8841,6 @@ snapshots:
optionalDependencies: optionalDependencies:
supports-color: 10.2.2 supports-color: 10.2.2
debug@4.4.0(supports-color@10.2.2):
dependencies:
ms: 2.1.3
optionalDependencies:
supports-color: 10.2.2
debug@4.4.3(supports-color@10.2.2): debug@4.4.3(supports-color@10.2.2):
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
@@ -10456,7 +10441,7 @@ snapshots:
pm2-axon-rpc@0.7.1(supports-color@10.2.2): pm2-axon-rpc@0.7.1(supports-color@10.2.2):
dependencies: dependencies:
debug: 4.4.0(supports-color@10.2.2) debug: 4.4.3(supports-color@10.2.2)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -10464,7 +10449,7 @@ snapshots:
dependencies: dependencies:
amp: 0.3.1 amp: 0.3.1
amp-message: 0.1.2 amp-message: 0.1.2
debug: 4.4.0(supports-color@10.2.2) debug: 4.4.3(supports-color@10.2.2)
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -10481,7 +10466,7 @@ snapshots:
pm2-sysmonit@1.2.8(supports-color@10.2.2): pm2-sysmonit@1.2.8(supports-color@10.2.2):
dependencies: dependencies:
async: 3.2.6 async: 3.2.6
debug: 4.4.0(supports-color@10.2.2) debug: 4.4.3(supports-color@10.2.2)
pidusage: 2.0.21 pidusage: 2.0.21
systeminformation: 5.25.11 systeminformation: 5.25.11
tx2: 1.0.5 tx2: 1.0.5
@@ -10557,7 +10542,7 @@ snapshots:
proxy-agent@6.4.0(supports-color@10.2.2): proxy-agent@6.4.0(supports-color@10.2.2):
dependencies: dependencies:
agent-base: 7.1.3 agent-base: 7.1.3
debug: 4.4.0(supports-color@10.2.2) debug: 4.4.3(supports-color@10.2.2)
http-proxy-agent: 7.0.2(supports-color@10.2.2) http-proxy-agent: 7.0.2(supports-color@10.2.2)
https-proxy-agent: 7.0.6(supports-color@10.2.2) https-proxy-agent: 7.0.6(supports-color@10.2.2)
lru-cache: 7.18.3 lru-cache: 7.18.3
@@ -10832,7 +10817,7 @@ snapshots:
require-in-the-middle@5.2.0(supports-color@10.2.2): require-in-the-middle@5.2.0(supports-color@10.2.2):
dependencies: dependencies:
debug: 4.4.0(supports-color@10.2.2) debug: 4.4.3(supports-color@10.2.2)
module-details-from-path: 1.0.3 module-details-from-path: 1.0.3
resolve: 1.22.8 resolve: 1.22.8
transitivePeerDependencies: transitivePeerDependencies:

View File

@@ -19,7 +19,9 @@ const checkAuth = (value: string = '', baseURL: string = '') => {
return false; return false;
}; };
const DEFAULT_IGNORE = ['node_modules/**', '.git/**', '.next/**', '.astro/**', '.pack-dist/**']; const DEFAULT_IGNORE = [
'node_modules/**', '.git/**', '.next/**', '.astro/**', '.pack-dist/**',
];
export class SyncBase { export class SyncBase {
config: Config; config: Config;
#filename: string; #filename: string;

View File

@@ -1,4 +0,0 @@
"@nut-tree-fork/nut-js": "^4.2.6",
"@kevisual/hot-api": "^0.0.3",
KEVISUAL_TOKEN="" LOG_LEVEL=DEBUG pnpm dev deploy ./cli-center/dist -k cli -v 0.0.4 -u -y y