feat: add zod dependency and implement kevisual routes for CLI commands

- Added zod as a dependency in package.json.
- Enhanced assistant configuration to include skills and plugins directories.
- Implemented runCmd function to execute CLI commands in run.ts.
- Updated light-code module to use node:child_process.
- Created new kevisual routes for checking CLI login status and deploying web pages.
- Added restart functionality for OpenCode client in opencode module.
This commit is contained in:
2026-01-28 00:02:38 +08:00
parent 98f21d8aaa
commit 742a7a2992
11 changed files with 352 additions and 146 deletions

View File

@@ -9,6 +9,7 @@ import './call/index.ts'
// import './hot-api/key-sender/index.ts';
import './opencode/index.ts';
import './remote/index.ts';
import './kevisual/index.ts'
import os from 'node:os';
import { authCache } from '@/module/cache/auth.ts';
@@ -160,6 +161,7 @@ app
})
.addTo(app);
// 调用 path: client key: system
app
.route({
path: 'client',

View File

@@ -0,0 +1,67 @@
import { app } from '@/app.ts'
import { runCmd } from '@/module/cmd/run.ts';
import { createSkill, tool } from "@kevisual/router";
import { useKey } from '@kevisual/use-config';
// 查看 ev cli 是否登录
app.route({
path: 'kevisual',
key: ' me',
description: '查看 ev cli 是否登录',
middleware: ['admin-auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'kevisual-me',
title: '查看 ev cli 是否登录',
summary: '查看 ev cli 是否登录',
args: {
}
})
},
}).define(async (ctx) => {
const cmd = 'ev me';
const res = await runCmd({ cmd })
if (res.code === 200) {
ctx.body = { content: res.data };
} else {
ctx.throw(500, res.data);
}
}).addTo(app);
// 执行工具 kevisual-login-by-admin
// 执行工具 通过当前登录用户 ev cl
// 调用 path: kevisual key: loginByAdmin
app.route({
path: 'kevisual',
key: 'loginByAdmin',
description: '通过当前登录用户 ev cli',
middleware: ['admin-auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'kevisual-login-by-admin',
title: '通过当前登录用户 ev cli',
summary: '通过当前登录用户登录 ev cli, 直接用当前的用户的 token 直接设置 token 给 ev cli, 登录失败直接停止任务',
args: {}
})
},
}).define(async (ctx) => {
const token = ctx.query?.token || useKey('KEVISUAL_TOKEN');
if (!token) {
ctx.throw(400, '登录的 token 不能为空,请传入 token 参数');
return;
}
const cmd = `ev login -e `;
const res = await runCmd({
cmd,
env: {
'KEVISUAL_TOKEN': token
}
})
if (res.code === 200) {
ctx.body = { content: res.data };
} else {
ctx.throw(500, res.data);
}
}).addTo(app);

View File

@@ -0,0 +1,45 @@
import { app } from '@/app.ts'
import { runCmd } from '@/module/cmd/run.ts';
import { createSkill, tool } from "@kevisual/router";
// 调用 path: kevisual key: deploy
app.route({
path: 'kevisual',
key: 'deploy',
description: '部署一个网页',
middleware: ['admin-auth'],
metadata: {
tags: ['kevisual'],
...createSkill({
skill: 'kevisual-deploy',
title: '部署一个网页',
summary: '部署一个网页到 kevisual 平台',
args: {
filepath: tool.schema.string().describe('要部署的网页文件路径'),
appKey: tool.schema.string().optional().describe('应用的 appKey如果不传则创建一个新的应用'),
version: tool.schema.string().optional().describe('应用的版本号,默认为 1.0.0'),
update: tool.schema.boolean().optional().describe('是否同时更新部署,默认为 false'),
}
})
},
}).define(async (ctx) => {
const { filepath, appKey, update } = ctx.query;
console.log('部署网页filepath:', filepath, 'appKey:', appKey);
ctx.body = { content: '部署功能正在开发中,敬请期待!' };
// ev deloly ${filepath} -k ${appKey} -v 1.0.0 -u -y y
// if (!filepath) {
// ctx.throw(400, '文件路径 filepath 不能为空');
// return;
// }
// let cmd = `ev deploy ${filepath} --type web`;
// if (appKey) {
// cmd += ` --appKey ${appKey}`;
// }
// const res = await runCmd({ cmd });
// if (res.code === 200) {
// ctx.body = { content: res.data };
// } else {
// ctx.throw(500, res.data);
// }
}).addTo(app);

View File

@@ -0,0 +1,2 @@
import './auth.ts'
import './deploy.ts'

View File

@@ -1,8 +1,6 @@
import { app } from '@/app.ts'
import { createSkill, tool } from "@kevisual/router";
import { opencodeManager } from './module/open.ts'
import path from "node:path";
import { execSync } from "node:child_process";
import { useKey } from '@kevisual/use-config';
// 创建一个opencode 客户端
@@ -27,7 +25,7 @@ app.route({
ctx.body = { content: `${opencodeManager.url} OpenCode 客户端已就绪` };
}).addTo(app);
// 关闭 opencode 客户端
// 关闭 opencode 客户端 5000
app.route({
path: 'opencode',
key: 'close',
@@ -38,17 +36,39 @@ app.route({
...createSkill({
skill: 'close-opencode-client',
title: '关闭 OpenCode 客户端',
summary: '关闭 OpenCode 客户端',
summary: '关闭 OpenCode 客户端, 未提供端口则关闭默认端口',
args: {
port: tool.schema.number().optional().describe('OpenCode 服务端口,默认为 5000')
}
})
},
}).define(async (ctx) => {
await opencodeManager.close();
const port = ctx.query.port;
await opencodeManager.close({ port });
ctx.body = { content: 'OpenCode 客户端已关闭' };
}).addTo(app);
app.route({
path: 'opencode',
key: 'restart',
middleware: ['auth'],
description: '重启 OpenCode 客户端',
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'restart-opencode-client',
title: '重启 OpenCode 客户端',
summary: '重启 OpenCode 客户端',
args: {
port: tool.schema.number().optional().describe('OpenCode 服务端口,默认为 5000')
}
})
},
}).define(async (ctx) => {
const port = ctx.query.port;
const res = await opencodeManager.restart({ port });
ctx.body = { content: `${opencodeManager.url} OpenCode 客户端已经重启` };
}).addTo(app);
// 调用 path: opencode key: getUrl
app.route({
path: 'opencode',

View File

@@ -122,6 +122,11 @@ export class OpencodeManager {
}
return `http://localhost:${port}`;
}
async restart(opts?: { port?: number }): Promise<OpencodeClient> {
const port = opts?.port ?? DEFAULT_PORT;
await this.close({ port });
return await this.getClient({ port });
}
}
export const opencodeManager = OpencodeManager.getInstance();