Compare commits
13 Commits
c00062d704
...
22ef776b27
| Author | SHA1 | Date | |
|---|---|---|---|
| 22ef776b27 | |||
| 37e00a9ff3 | |||
| d3104bd5a5 | |||
|
|
6795cef3d5 | ||
| dff858d820 | |||
| f67ca752f3 | |||
| bda7fc3b92 | |||
| 58a0e9e61f | |||
| 4b935036ad | |||
| 8875c0ea3d | |||
| d6e3f67ac3 | |||
|
|
88313d5b8e | ||
|
|
e377557587 |
30
.cnb.yml
30
.cnb.yml
@@ -4,8 +4,7 @@ include:
|
||||
|
||||
.common_env: &common_env
|
||||
env:
|
||||
TO_REPO: kevisual/cli
|
||||
TO_URL: git.xiongxiao.me
|
||||
USERNAME: root
|
||||
imports:
|
||||
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
||||
|
||||
@@ -16,29 +15,6 @@ $:
|
||||
services:
|
||||
- vscode
|
||||
- docker
|
||||
env: !reference [.common_env, env]
|
||||
imports: !reference [.common_env, imports]
|
||||
# 开发环境启动后会执行的任务
|
||||
# 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
|
||||
stages: !reference [.dev_template, stages]
|
||||
@@ -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
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
name: kill-opencode
|
||||
description: 自动查找并杀死所有opencode相关的进程,确保系统资源释放。
|
||||
tags:
|
||||
- opencode
|
||||
- process-management
|
||||
- automation
|
||||
metadata:
|
||||
tags:
|
||||
- opencode
|
||||
- process-management
|
||||
- automation
|
||||
---
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
77
assistant/.opencode/skills/opencode-skill-creator/SKILL.md
Normal file
77
assistant/.opencode/skills/opencode-skill-creator/SKILL.md
Normal 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 语法
|
||||
- 可以包含代码块、表格、列表等
|
||||
213
assistant/.opencode/skills/router-creator/SKILL.md
Normal file
213
assistant/.opencode/skills/router-creator/SKILL.md
Normal 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**,错误信息中会包含找不到的中间件列表。
|
||||
@@ -42,14 +42,14 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@inquirer/prompts": "^8.2.1",
|
||||
"@inquirer/prompts": "^8.3.0",
|
||||
"@kevisual/ai": "^0.0.24",
|
||||
"@kevisual/api": "^0.0.58",
|
||||
"@kevisual/api": "^0.0.59",
|
||||
"@kevisual/load": "^0.0.6",
|
||||
"@kevisual/local-app-manager": "^0.1.32",
|
||||
"@kevisual/logger": "^0.0.4",
|
||||
"@kevisual/query": "0.0.49",
|
||||
"@kevisual/router": "^0.0.83",
|
||||
"@kevisual/router": "^0.0.84",
|
||||
"@kevisual/types": "^0.0.12",
|
||||
"@kevisual/use-config": "^1.0.30",
|
||||
"@opencode-ai/plugin": "^1.2.10",
|
||||
|
||||
@@ -380,6 +380,7 @@ export class AssistantConfig {
|
||||
}
|
||||
protected getDefaultInitAssistantConfig() {
|
||||
const id = randomId();
|
||||
const isCNB = !!useKey('CNB');
|
||||
return {
|
||||
app: {
|
||||
url: 'https://kevisual.cn',
|
||||
@@ -387,10 +388,10 @@ export class AssistantConfig {
|
||||
},
|
||||
description: '助手配置文件',
|
||||
docs: "https://kevisual.cn/root/cli/docs/",
|
||||
home: '/root/home',
|
||||
home: isCNB ? '/root/cli-center' : '/root/home',
|
||||
proxy: [],
|
||||
share: {
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
url: 'https://kevisual.cn/ws/proxy',
|
||||
},
|
||||
} as AssistantConfigData;
|
||||
|
||||
@@ -37,6 +37,23 @@ export class AssistantApp extends Manager {
|
||||
this.config = config;
|
||||
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() {
|
||||
const pages = await glob(['*/*/package.json'], {
|
||||
cwd: this.pagesPath,
|
||||
@@ -99,7 +116,11 @@ export class AssistantApp extends Manager {
|
||||
}
|
||||
let token = opts?.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;
|
||||
if (!shareUrl) {
|
||||
@@ -118,7 +139,7 @@ export class AssistantApp extends Manager {
|
||||
// 使用 RemoteApp 内置的自动重连机制
|
||||
autoReconnect: true,
|
||||
reconnectDelay: 5000, // 首次重连延迟 5 秒
|
||||
maxReconnectAttempts: Infinity, // 无限重连
|
||||
maxReconnectAttempts: 50, // 最大重连次数
|
||||
enableBackoff: true, // 启用指数退避
|
||||
});
|
||||
remoteApp.isConnect();
|
||||
@@ -140,7 +161,7 @@ export class AssistantApp extends Manager {
|
||||
logger.info('[remote-app] 远程连接已关闭,自动重连机制正在处理...');
|
||||
});
|
||||
remoteApp.on('maxReconnectAttemptsReached', () => {
|
||||
logger.error('远程应用重连达到最大次数,停止重连');
|
||||
logger.error('[remote-app] 远程应用重连达到最大次数,停止重连');
|
||||
});
|
||||
this.remoteApp = remoteApp;
|
||||
} else {
|
||||
@@ -161,11 +182,10 @@ export class AssistantApp extends Manager {
|
||||
* - lightcode: 轻代码模块配置
|
||||
* @returns
|
||||
*/
|
||||
async initRouterApp() {
|
||||
async initRouterProxyApp() {
|
||||
const config = this.config.getConfig();
|
||||
const routerProxy = config?.router?.proxy || [];
|
||||
let routerProxy = config?.router?.proxy || [];
|
||||
const base = config.router?.base ?? false;
|
||||
const lightcode = config.router?.lightcode ?? true;
|
||||
if (base) {
|
||||
routerProxy.push({
|
||||
type: 'router',
|
||||
@@ -174,42 +194,13 @@ export class AssistantApp extends Manager {
|
||||
}
|
||||
})
|
||||
}
|
||||
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 === 'router');
|
||||
if (routerProxy.length === 0) {
|
||||
return
|
||||
}
|
||||
for (const proxyInfo of routerProxy) {
|
||||
if (proxyInfo.type !== 'router' && proxyInfo.type !== 'lightcode') {
|
||||
console.warn('路由的type必须是"router", 或者lightcode');
|
||||
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,
|
||||
});
|
||||
if (proxyInfo.type !== 'router') {
|
||||
console.warn('路由的type必须是"router"');
|
||||
continue;
|
||||
}
|
||||
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 不需要登录
|
||||
@@ -265,34 +299,57 @@ export class AssistantApp extends Manager {
|
||||
const checkLocalUser = async (opts: { assistantApp: AssistantApp }) => {
|
||||
const { assistantApp } = opts;
|
||||
const config = assistantApp.config.getConfig();
|
||||
const auth = config?.auth;
|
||||
let checkCNB = false;
|
||||
if (!auth?.username) {
|
||||
checkCNB = true;
|
||||
// 没有登录过,自动检测ci进行登录
|
||||
// 检测条件1:环境变量中存在 CNB_TOKEN
|
||||
} else {
|
||||
let temp = await assistantQuery.getToken()
|
||||
logger.info('[assistant] 当前登录用户', auth.username, 'token有效性检查结果', !!temp);
|
||||
const auth = config?.auth || {};
|
||||
const isLogin = await assistantQuery.queryLogin.getToken();
|
||||
logger.log('[assistant] 正在检查本地用户登录状态...', 'user', auth.username, '是否已登录', !!isLogin);
|
||||
|
||||
const saveAuth = (auth: typeof config.auth, opts: { appId?: string, username: string; share: 'protected' | 'private' }) => {
|
||||
auth.username = auth.username ?? opts.username;
|
||||
auth.share = auth.share ?? opts.share;
|
||||
const app = config?.app || {};
|
||||
if (!app?.id) {
|
||||
app.id = opts.appId || 'dev-cnb'
|
||||
}
|
||||
assistantApp.config.setConfig({
|
||||
auth,
|
||||
app
|
||||
});
|
||||
}
|
||||
const cnbToken = useKey('CNB_TOKEN');
|
||||
if (!checkCNB && cnbToken) {
|
||||
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) {
|
||||
logger.info('CNB登录成功,用户信息已更新');
|
||||
const userInfo = await assistantQuery.queryLogin.checkLocalUser()
|
||||
auth.username = userInfo.username;
|
||||
auth.share = 'protected'
|
||||
const app = config?.app || {};
|
||||
if (!app?.id) {
|
||||
app.id = 'dev-cnb'
|
||||
if (!userInfo.username) {
|
||||
saveAuth(auth, { username: userInfo.username, share: 'protected' })
|
||||
}
|
||||
assistantApp.config.setConfig({
|
||||
auth,
|
||||
app
|
||||
});
|
||||
return
|
||||
} 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' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,6 @@ export class AssistantQuery {
|
||||
}
|
||||
async 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import fs from 'node:fs';
|
||||
import glob from 'fast-glob';
|
||||
import { runCode } from './run.ts';
|
||||
const codeDemoId = '0e700dc8-90dd-41b7-91dd-336ea51de3d2'
|
||||
import { filter } from "@kevisual/js-filter";
|
||||
import { getHash, getStringHash } from '../file-hash.ts';
|
||||
import { AssistantConfig } from '@/lib.ts';
|
||||
import { assistantQuery } from '@/app.ts';
|
||||
import { logger } from '../logger.ts';
|
||||
|
||||
const codeDemo = `// 这是一个示例代码文件
|
||||
import {App} from '@kevisual/router';
|
||||
@@ -45,15 +45,19 @@ type LightCodeFile = {
|
||||
id?: string, code?: string, hash?: string, filepath: string
|
||||
}
|
||||
export const initLightCode = async (opts: Opts) => {
|
||||
const token = await assistantQuery.getToken();
|
||||
if (!token) {
|
||||
logger.error('[light-code] 当前未登录,无法初始化 light-code');
|
||||
return;
|
||||
}
|
||||
// 注册 light-code 路由
|
||||
console.log('初始化 light-code 路由');
|
||||
const config = opts.config as AssistantInit;
|
||||
const app = opts.router;
|
||||
const token = await assistantQuery.getToken();
|
||||
logger.log('[light-code] 初始化 light-code 路由');
|
||||
const query = config.query;
|
||||
const sync = opts.sync ?? 'remote';
|
||||
if (!config || !app) {
|
||||
console.error('[light-code] initLightCode 缺少必要参数, config 或 app');
|
||||
logger.error('[light-code] initLightCode 缺少必要参数, config 或 app');
|
||||
return;
|
||||
}
|
||||
const lightcodeDir = opts.rootPath;
|
||||
|
||||
@@ -207,7 +207,7 @@ export class RemoteApp {
|
||||
this.emitter.emit('message', data);
|
||||
}
|
||||
onError(error: any) {
|
||||
console.error('远程应用错误:', this.id, error);
|
||||
console.error(`[remote-app] 远程应用错误: ${this.id}`, error);
|
||||
this.isError = true;
|
||||
this.emitter.emit('error', error);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ app.route({
|
||||
path: 'call',
|
||||
key: '',
|
||||
description: '调用',
|
||||
middleware: ['auth'],
|
||||
middleware: ['auth-admin'],
|
||||
metadata: {
|
||||
tags: ['opencode'],
|
||||
...createSkill({
|
||||
@@ -21,11 +21,11 @@ app.route({
|
||||
})
|
||||
},
|
||||
}).define(async (ctx) => {
|
||||
const { path, key = '' } = ctx.query;
|
||||
const { path, key = '' } = ctx.args;
|
||||
if (!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.forward(res);
|
||||
|
||||
@@ -33,7 +33,8 @@ app.route({
|
||||
title: '查看客户端 IP 地址',
|
||||
summary: '获取当前客户端的 IP 地址信息',
|
||||
})
|
||||
}
|
||||
},
|
||||
middleware: ['auth-admin']
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
|
||||
@@ -76,10 +76,6 @@ app.route({
|
||||
message: '客户端重启命令已执行',
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.status = 500;
|
||||
ctx.body = {
|
||||
message: '重启客户端失败',
|
||||
error: error.message,
|
||||
};
|
||||
ctx.throw(500, '重启客户端失败');
|
||||
}
|
||||
}).addTo(app);
|
||||
|
||||
424
assistant/src/routes/cnb-board/cnb-dev-env.ts
Normal file
424
assistant/src/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
assistant/src/routes/cnb-board/common.ts
Normal file
0
assistant/src/routes/cnb-board/common.ts
Normal file
29
assistant/src/routes/cnb-board/index.ts
Normal file
29
assistant/src/routes/cnb-board/index.ts
Normal 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);
|
||||
340
assistant/src/routes/cnb-board/live/live-content.ts
Normal file
340
assistant/src/routes/cnb-board/live/live-content.ts
Normal 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
|
||||
}
|
||||
1
assistant/src/routes/cnb-board/modules/index.ts
Normal file
1
assistant/src/routes/cnb-board/modules/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './is-cnb.ts';
|
||||
6
assistant/src/routes/cnb-board/modules/is-cnb.ts
Normal file
6
assistant/src/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;
|
||||
}
|
||||
@@ -28,9 +28,14 @@ app
|
||||
|
||||
app.route({
|
||||
path: 'config',
|
||||
key: 'getId'
|
||||
key: 'getId',
|
||||
description: '获取appId',
|
||||
|
||||
}).define(async (ctx) => {
|
||||
const config = assistantConfig.getCacheAssistantConfig();
|
||||
ctx.body = config?.app?.id || null;
|
||||
const appId = config?.app?.id || null;
|
||||
ctx.body = {
|
||||
id: appId,
|
||||
}
|
||||
|
||||
}).addTo(app);
|
||||
@@ -2,13 +2,14 @@ import { app, assistantConfig } from '../app.ts';
|
||||
import './config/index.ts';
|
||||
import './client/index.ts';
|
||||
import './shop-install/index.ts';
|
||||
import './ai/index.ts';
|
||||
// import './ai/index.ts';
|
||||
import './user/index.ts';
|
||||
import './call/index.ts'
|
||||
|
||||
import './opencode/index.ts';
|
||||
import './remote/index.ts';
|
||||
// import './kevisual/index.ts'
|
||||
import './cnb-board/index.ts';
|
||||
|
||||
import { authCache } from '@/module/cache/auth.ts';
|
||||
|
||||
@@ -70,7 +71,7 @@ export const checkAuth = async (ctx: any, isAdmin = false) => {
|
||||
if (!auth.username) {
|
||||
// 初始管理员账号
|
||||
auth.username = username;
|
||||
assistantConfig.setConfig({ auth, token: token });
|
||||
assistantConfig.setConfig({ auth });
|
||||
}
|
||||
if (isAdmin && auth.username) {
|
||||
const admins = config.auth?.admin || [];
|
||||
@@ -78,12 +79,6 @@ export const checkAuth = async (ctx: any, isAdmin = false) => {
|
||||
const admin = auth.username;
|
||||
if (admin === username) {
|
||||
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)) {
|
||||
isCheckAdmin = true;
|
||||
@@ -127,6 +122,7 @@ app
|
||||
if (!ctx.query?.token && ctx.appId === app.appId) {
|
||||
return;
|
||||
}
|
||||
ctx.state.isAdmin = true;
|
||||
const authResult = await checkAuth(ctx, true);
|
||||
if (authResult.code !== 200) {
|
||||
ctx.throw(authResult.code, authResult.message);
|
||||
|
||||
@@ -48,14 +48,7 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
|
||||
]);
|
||||
const manager = useContextKey('manager', new AssistantApp(assistantConfig, app));
|
||||
setTimeout(async () => {
|
||||
await manager.load({ runtime: 'client' });
|
||||
console.log('Assistant App Loaded');
|
||||
await manager.checkLocalUser()
|
||||
await manager.initRemoteApp();
|
||||
await manager.initRouterApp();
|
||||
if (runtime.isServer) {
|
||||
await manager.initRoutes();
|
||||
}
|
||||
manager.init({ isServer: runtime.isServer });
|
||||
}, 1000);
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,11 +9,13 @@ import type { WebSocketListenerFun } from "@kevisual/router";
|
||||
import WebSocket from 'ws';
|
||||
import { renderNoAuthAndLogin } from '@/module/assistant/html/login.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({});
|
||||
localProxy.initFromAssistantConfig(assistantConfig);
|
||||
|
||||
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) {
|
||||
if (pathname.startsWith(openPath)) {
|
||||
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) => {
|
||||
const _assistantConfig = assistantConfig.getCacheAssistantConfig();
|
||||
const home = _assistantConfig?.home || '/root/home';
|
||||
let home = _assistantConfig?.home
|
||||
const auth = _assistantConfig?.auth || {};
|
||||
// 没有管理员,需要去登陆
|
||||
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 = () => {
|
||||
res.writeHead(302, { Location: `/root/cli-center/` });
|
||||
const toLogin = (redirect?: string) => {
|
||||
res.writeHead(302, { Location: `/root/login/` + (redirect ? `?redirect=${encodeURIComponent(redirect)}` : '') });
|
||||
res.end();
|
||||
return true;
|
||||
}
|
||||
@@ -94,10 +108,11 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
||||
const pathname = decodeURIComponent(url.pathname);
|
||||
if (pathname === '/') {
|
||||
if (noAdmin) {
|
||||
return toSetting();
|
||||
return toLogin(home + '/');
|
||||
}
|
||||
res.writeHead(302, { Location: `${home}/` });
|
||||
return res.end();
|
||||
res.writeHead(302, { Location: home });
|
||||
res.end();
|
||||
return
|
||||
}
|
||||
if (pathname.startsWith('/favicon.ico')) {
|
||||
res.statusCode = 404;
|
||||
@@ -162,8 +177,9 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
||||
}
|
||||
const isOpen = isOpenPath(pathname)
|
||||
logger.debug('proxyRoute', { _user, _app, pathname, noAdmin, isOpen });
|
||||
// 没有管理员,且不是开放路径,去登录
|
||||
if (noAdmin && !isOpen) {
|
||||
return toSetting();
|
||||
return toLogin();
|
||||
}
|
||||
if (_app && urls.length === 3) {
|
||||
// 重定向到
|
||||
|
||||
40
assistant/src/test/cnb.ts
Normal file
40
assistant/src/test/cnb.ts
Normal 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()
|
||||
@@ -1,3 +0,0 @@
|
||||
# envision-cli
|
||||
|
||||
## 上传文件
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/cli",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.12",
|
||||
"description": "envision 命令行工具",
|
||||
"type": "module",
|
||||
"basename": "/root/cli",
|
||||
@@ -41,7 +41,7 @@
|
||||
],
|
||||
"author": "abearxiong",
|
||||
"dependencies": {
|
||||
"@inquirer/prompts": "^8.2.1",
|
||||
"@inquirer/prompts": "^8.3.0",
|
||||
"@kevisual/app": "^0.0.2",
|
||||
"@kevisual/auth": "^2.0.3",
|
||||
"@kevisual/context": "^0.0.8",
|
||||
@@ -59,7 +59,7 @@
|
||||
"unstorage": "^1.17.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/api": "^0.0.58",
|
||||
"@kevisual/api": "^0.0.59",
|
||||
"@kevisual/cnb": "^0.0.28",
|
||||
"@kevisual/dts": "^0.0.4",
|
||||
"@kevisual/load": "^0.0.6",
|
||||
|
||||
169
pnpm-lock.yaml
generated
169
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@inquirer/prompts':
|
||||
specifier: ^8.2.1
|
||||
version: 8.2.1(@types/node@25.3.0)
|
||||
specifier: ^8.3.0
|
||||
version: 8.3.0(@types/node@25.3.0)
|
||||
'@kevisual/app':
|
||||
specifier: ^0.0.2
|
||||
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))
|
||||
devDependencies:
|
||||
'@kevisual/api':
|
||||
specifier: ^0.0.58
|
||||
version: 0.0.58(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^0.0.59
|
||||
version: 0.0.59(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@kevisual/cnb':
|
||||
specifier: ^0.0.28
|
||||
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
|
||||
devDependencies:
|
||||
'@inquirer/prompts':
|
||||
specifier: ^8.2.1
|
||||
version: 8.2.1(@types/node@25.3.0)
|
||||
specifier: ^8.3.0
|
||||
version: 8.3.0(@types/node@25.3.0)
|
||||
'@kevisual/ai':
|
||||
specifier: ^0.0.24
|
||||
version: 0.0.24
|
||||
'@kevisual/api':
|
||||
specifier: ^0.0.58
|
||||
version: 0.0.58(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^0.0.59
|
||||
version: 0.0.59(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@kevisual/load':
|
||||
specifier: ^0.0.6
|
||||
version: 0.0.6
|
||||
@@ -185,8 +185,8 @@ importers:
|
||||
specifier: 0.0.49
|
||||
version: 0.0.49
|
||||
'@kevisual/router':
|
||||
specifier: ^0.0.83
|
||||
version: 0.0.83
|
||||
specifier: ^0.0.84
|
||||
version: 0.0.84
|
||||
'@kevisual/types':
|
||||
specifier: ^0.0.12
|
||||
version: 0.0.12
|
||||
@@ -1128,8 +1128,8 @@ packages:
|
||||
resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
|
||||
'@inquirer/checkbox@5.0.6':
|
||||
resolution: {integrity: sha512-qLZ1gOpsqsieB5k98GQ9bWYggvMsCXTc7HUwhEQpTsxFQYGthqR9UysCwqB7L9h47THYdXhJegnYb1IqURMjng==}
|
||||
'@inquirer/checkbox@5.1.0':
|
||||
resolution: {integrity: sha512-/HjF1LN0a1h4/OFsbGKHNDtWICFU/dqXCdym719HFTyJo9IG7Otr+ziGWc9S0iQuohRZllh+WprSgd5UW5Fw0g==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1137,8 +1137,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/confirm@6.0.6':
|
||||
resolution: {integrity: sha512-9ZkrGYiWnOKQPc3xfLIORE3lZW1qvtgRoJcoqopr5zssBn7yk4yONmzGynEOjc16FnUXzkAejj/I29BbfcoUfQ==}
|
||||
'@inquirer/confirm@6.0.8':
|
||||
resolution: {integrity: sha512-Di6dgmiZ9xCSUxWUReWTqDtbhXCuG2MQm2xmgSAIruzQzBqNf49b8E07/vbCYY506kDe8BiwJbegXweG8M1klw==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1146,8 +1146,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/core@11.1.3':
|
||||
resolution: {integrity: sha512-TBAGPDGvpwFSQ4nkawQzq5/X7DhElANjvKeUtcjpVnBIfuH/OEu4M+79R3+bGPtwxST4DOIGRtF933mUH2bRVw==}
|
||||
'@inquirer/core@11.1.5':
|
||||
resolution: {integrity: sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1155,8 +1155,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/editor@5.0.6':
|
||||
resolution: {integrity: sha512-dxTi/TB29NaW18u0pQl3B140695izGUMzr340a4Yhxll3oa0/iwxl6C88sX9LDUPFaaM4FDASEMnLm8XVk2VVg==}
|
||||
'@inquirer/editor@5.0.8':
|
||||
resolution: {integrity: sha512-sLcpbb9B3XqUEGrj1N66KwhDhEckzZ4nI/W6SvLXyBX8Wic3LDLENlWRvkOGpCPoserabe+MxQkpiMoI8irvyA==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1164,8 +1164,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/expand@5.0.6':
|
||||
resolution: {integrity: sha512-HmgMzFdMk/gmPXfuFy4xgWkyIVbdH81otQkrFbhklFZcGauwDFD1EbgmZdgmYCN5pWhSEnYIadg1kysLgPIYag==}
|
||||
'@inquirer/expand@5.0.8':
|
||||
resolution: {integrity: sha512-QieW3F1prNw3j+hxO7/NKkG1pk3oz7pOB6+5Upwu3OIwADfPX0oZVppsqlL+Vl/uBHHDSOBY0BirLctLnXwGGg==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1186,8 +1186,8 @@ packages:
|
||||
resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
|
||||
'@inquirer/input@5.0.6':
|
||||
resolution: {integrity: sha512-RZsJcjMJA3QNI9q9OiAi1fAom+Pb8on6alJB1Teh5jjKaiG5C79P69cG955ZRfgPdxTmI4uyhf33+94Xj7xWig==}
|
||||
'@inquirer/input@5.0.8':
|
||||
resolution: {integrity: sha512-p0IJslw0AmedLEkOU+yrEX3Aj2RTpQq7ZOf8nc1DIhjzaxRWrrgeuE5Kyh39fVRgtcACaMXx/9WNo8+GjgBOfw==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1195,8 +1195,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/number@4.0.6':
|
||||
resolution: {integrity: sha512-owMkAY+gR0BggomDTL+Z22x/yfE4ocFrmNyJacOiaDVA/d+iL4IWyk7Ds7JEuDMxuhHFB46Dubdxg1uiD7GlCA==}
|
||||
'@inquirer/number@4.0.8':
|
||||
resolution: {integrity: sha512-uGLiQah9A0F9UIvJBX52m0CnqtLaym0WpT9V4YZrjZ+YRDKZdwwoEPz06N6w8ChE2lrnsdyhY9sL+Y690Kh9gQ==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1204,8 +1204,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/password@5.0.6':
|
||||
resolution: {integrity: sha512-c4BT4SB79iYwPhtGVBSvrlTnn4oFSYnwocafmktpay8RK75T2c2+fLlR0i1Cxw0QOhdy/YULdmpHoy1sOrPzvA==}
|
||||
'@inquirer/password@5.0.8':
|
||||
resolution: {integrity: sha512-zt1sF4lYLdvPqvmvHdmjOzuUUjuCQ897pdUCO8RbXMUDKXJTTyOQgtn23le+jwcb+MpHl3VAFvzIdxRAf6aPlA==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1213,8 +1213,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/prompts@8.2.1':
|
||||
resolution: {integrity: sha512-76knJFW2oXdI6If5YRmEoT5u7l+QroXYrMiINFcb97LsyECgsbO9m6iWlPuhBtaFgNITPHQCk3wbex38q8gsjg==}
|
||||
'@inquirer/prompts@8.3.0':
|
||||
resolution: {integrity: sha512-JAj66kjdH/F1+B7LCigjARbwstt3SNUOSzMdjpsvwJmzunK88gJeXmcm95L9nw1KynvFVuY4SzXh/3Y0lvtgSg==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1222,8 +1222,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/rawlist@5.2.2':
|
||||
resolution: {integrity: sha512-ld2EhLlf3fsBv7QfxR31NdBecGdS6eeFFZ+Nx88ApjtifeCEc9TNrw8x5tGe+gd6HG1ERczOb4B/bMojiGIp1g==}
|
||||
'@inquirer/rawlist@5.2.4':
|
||||
resolution: {integrity: sha512-fTuJ5Cq9W286isLxwj6GGyfTjx1Zdk4qppVEPexFuA6yioCCXS4V1zfKroQqw7QdbDPN73xs2DiIAlo55+kBqg==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1231,8 +1231,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/search@4.1.2':
|
||||
resolution: {integrity: sha512-kdGbbbWYKldWxpxodKYPmFl/ctBi3DjWlA4LX48jXtqJ7NEeoEKlyFTbE4xNEFcGDi15tvaxRLzCV4A53zqYIw==}
|
||||
'@inquirer/search@4.1.4':
|
||||
resolution: {integrity: sha512-9yPTxq7LPmYjrGn3DRuaPuPbmC6u3fiWcsE9ggfLcdgO/ICHYgxq7mEy1yJ39brVvgXhtOtvDVjDh9slJxE4LQ==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1240,8 +1240,8 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@inquirer/select@5.0.6':
|
||||
resolution: {integrity: sha512-9DyVbNCo4q0C3CkGd6zW0SW3NQuuk4Hy0NSbP6zErz2YNWF4EHHJCRzcV34/CDQLraeAQXbHYlMofuUrs6BBZQ==}
|
||||
'@inquirer/select@5.1.0':
|
||||
resolution: {integrity: sha512-OyYbKnchS1u+zRe14LpYrN8S0wH1vD0p2yKISvSsJdH2TpI87fh4eZdWnpdbrGauCRWDph3NwxRmM4Pcm/hx1Q==}
|
||||
engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'}
|
||||
peerDependencies:
|
||||
'@types/node': '>=18'
|
||||
@@ -1290,8 +1290,8 @@ packages:
|
||||
'@kevisual/api@0.0.28':
|
||||
resolution: {integrity: sha512-WQluRlu2qGM1qktIhPLODie8x382a6jEMfFOcay/rnkCgXK0BRpnqOKwlX7IMLdMqka7GY/BD69kSMnK1Exf5g==}
|
||||
|
||||
'@kevisual/api@0.0.58':
|
||||
resolution: {integrity: sha512-ontJswmD5LS4EjYZDGF/SI1oYI1/zCM6ovC8rviNzxI22294yQ5y8Lb/rYV2qsKaxJbC3uuOjjsjPWap0nSvAQ==}
|
||||
'@kevisual/api@0.0.59':
|
||||
resolution: {integrity: sha512-2w6GBG2mS92dz8afB0hLfjTw8lBGD5oXU5bw/QglFugHHp24fISZkEW1Hc+jP/jOiYlMzphws2/31DIXdDoGkg==}
|
||||
|
||||
'@kevisual/app@0.0.1':
|
||||
resolution: {integrity: sha512-PEx8P3l0iNSqrz9Ib9kVCYfqNMX6/LfNu+cEafmY6ECP1cV5Vmv+TH2fuasMosKjtbH2fAdDi97sbd29tdEK+g==}
|
||||
@@ -1363,8 +1363,8 @@ packages:
|
||||
'@kevisual/router@0.0.80':
|
||||
resolution: {integrity: sha512-rVwi6Yf411bnNm2x94lMm+s4Csw0Yb7u/aj+VJJ59iouAYhjLuL7Rs1EcARhnQf47cegBJi6zozfGHgLsLHN2w==}
|
||||
|
||||
'@kevisual/router@0.0.83':
|
||||
resolution: {integrity: sha512-CVazzM1rXVyvU7QcMQr0/EuqacRNEGalThDDLGQcvKEVHyduJ9yWddn6kezgWFCpNlPKhzSCKkIFuZVixNVxDQ==}
|
||||
'@kevisual/router@0.0.84':
|
||||
resolution: {integrity: sha512-l/TUFuqTJegB/S3FZQRBMUoz0Spvg8EzV3C/kBi/VO9KKCzjqZDVvhZJJbTQh9879CBY6vUy1ajo9WcLYnwbNA==}
|
||||
|
||||
'@kevisual/types@0.0.12':
|
||||
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
|
||||
@@ -3036,15 +3036,6 @@ packages:
|
||||
supports-color:
|
||||
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:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -6445,23 +6436,23 @@ snapshots:
|
||||
|
||||
'@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:
|
||||
'@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/type': 4.0.3(@types/node@25.3.0)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@inquirer/ansi': 2.0.3
|
||||
'@inquirer/figures': 2.0.3
|
||||
@@ -6473,17 +6464,17 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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/type': 4.0.3(@types/node@25.3.0)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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)
|
||||
optionalDependencies:
|
||||
'@types/node': 25.3.0
|
||||
@@ -6497,62 +6488,62 @@ snapshots:
|
||||
|
||||
'@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:
|
||||
'@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)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@inquirer/checkbox': 5.0.6(@types/node@25.3.0)
|
||||
'@inquirer/confirm': 6.0.6(@types/node@25.3.0)
|
||||
'@inquirer/editor': 5.0.6(@types/node@25.3.0)
|
||||
'@inquirer/expand': 5.0.6(@types/node@25.3.0)
|
||||
'@inquirer/input': 5.0.6(@types/node@25.3.0)
|
||||
'@inquirer/number': 4.0.6(@types/node@25.3.0)
|
||||
'@inquirer/password': 5.0.6(@types/node@25.3.0)
|
||||
'@inquirer/rawlist': 5.2.2(@types/node@25.3.0)
|
||||
'@inquirer/search': 4.1.2(@types/node@25.3.0)
|
||||
'@inquirer/select': 5.0.6(@types/node@25.3.0)
|
||||
'@inquirer/checkbox': 5.1.0(@types/node@25.3.0)
|
||||
'@inquirer/confirm': 6.0.8(@types/node@25.3.0)
|
||||
'@inquirer/editor': 5.0.8(@types/node@25.3.0)
|
||||
'@inquirer/expand': 5.0.8(@types/node@25.3.0)
|
||||
'@inquirer/input': 5.0.8(@types/node@25.3.0)
|
||||
'@inquirer/number': 4.0.8(@types/node@25.3.0)
|
||||
'@inquirer/password': 5.0.8(@types/node@25.3.0)
|
||||
'@inquirer/rawlist': 5.2.4(@types/node@25.3.0)
|
||||
'@inquirer/search': 4.1.4(@types/node@25.3.0)
|
||||
'@inquirer/select': 5.1.0(@types/node@25.3.0)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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/type': 4.0.3(@types/node@25.3.0)
|
||||
optionalDependencies:
|
||||
'@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:
|
||||
'@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/type': 4.0.3(@types/node@25.3.0)
|
||||
optionalDependencies:
|
||||
@@ -6609,7 +6600,7 @@ snapshots:
|
||||
fuse.js: 7.1.0
|
||||
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:
|
||||
'@kevisual/context': 0.0.8
|
||||
'@kevisual/js-filter': 0.0.5
|
||||
@@ -6809,7 +6800,7 @@ snapshots:
|
||||
dependencies:
|
||||
es-toolkit: 1.44.0
|
||||
|
||||
'@kevisual/router@0.0.83':
|
||||
'@kevisual/router@0.0.84':
|
||||
dependencies:
|
||||
es-toolkit: 1.44.0
|
||||
|
||||
@@ -8850,12 +8841,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
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):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -10456,7 +10441,7 @@ snapshots:
|
||||
|
||||
pm2-axon-rpc@0.7.1(supports-color@10.2.2):
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@10.2.2)
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -10464,7 +10449,7 @@ snapshots:
|
||||
dependencies:
|
||||
amp: 0.3.1
|
||||
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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -10481,7 +10466,7 @@ snapshots:
|
||||
pm2-sysmonit@1.2.8(supports-color@10.2.2):
|
||||
dependencies:
|
||||
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
|
||||
systeminformation: 5.25.11
|
||||
tx2: 1.0.5
|
||||
@@ -10557,7 +10542,7 @@ snapshots:
|
||||
proxy-agent@6.4.0(supports-color@10.2.2):
|
||||
dependencies:
|
||||
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)
|
||||
https-proxy-agent: 7.0.6(supports-color@10.2.2)
|
||||
lru-cache: 7.18.3
|
||||
@@ -10832,7 +10817,7 @@ snapshots:
|
||||
|
||||
require-in-the-middle@5.2.0(supports-color@10.2.2):
|
||||
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
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
|
||||
@@ -19,7 +19,9 @@ const checkAuth = (value: string = '', baseURL: string = '') => {
|
||||
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 {
|
||||
config: Config;
|
||||
#filename: string;
|
||||
|
||||
Reference in New Issue
Block a user