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:
@@ -87,6 +87,7 @@
|
||||
"lowdb": "^7.0.1",
|
||||
"lru-cache": "^11.2.4",
|
||||
"pm2": "^6.0.14",
|
||||
"unstorage": "^1.17.4"
|
||||
"unstorage": "^1.17.4",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { checkFileExists, createDir } from '../file/index.ts';
|
||||
import { ProxyInfo } from '../proxy/proxy.ts';
|
||||
import dotenv from 'dotenv';
|
||||
import { logger } from '@/module/logger.ts';
|
||||
import { z } from 'zod'
|
||||
|
||||
let kevisualDir = path.join(homedir(), 'kevisual');
|
||||
const envKevisualDir = process.env.ASSISTANT_CONFIG_DIR
|
||||
@@ -28,12 +29,15 @@ export const initConfig = (configRootPath: string) => {
|
||||
const pageConfigPath = path.join(configDir, 'assistant-page-config.json');
|
||||
const pagesDir = createDir(path.join(configDir, 'pages'));
|
||||
const appsDir = createDir(path.join(configDir, 'apps'));
|
||||
const skillsDir = createDir(path.join(configDir, 'skills'), false);
|
||||
const pluginsDir = createDir(path.join(configDir, 'plugins'), false);
|
||||
|
||||
const appsConfigPath = path.join(configDir, 'assistant-apps-config.json');
|
||||
const appPidPath = path.join(configDir, 'assistant-app.pid');
|
||||
const envConfigPath = path.join(configDir, '.env');
|
||||
return {
|
||||
/**
|
||||
* 助手配置文件路径
|
||||
* 助手配置文件路径, assistant-app 目录
|
||||
*/
|
||||
configDir,
|
||||
/**
|
||||
@@ -41,7 +45,7 @@ export const initConfig = (configRootPath: string) => {
|
||||
*/
|
||||
configPath,
|
||||
/**
|
||||
* 服务目录, 后端服务目录
|
||||
* 服务目录, 后端服务目录, apps 目录
|
||||
*/
|
||||
appsDir,
|
||||
/**
|
||||
@@ -49,7 +53,7 @@ export const initConfig = (configRootPath: string) => {
|
||||
*/
|
||||
appsConfigPath,
|
||||
/**
|
||||
* 应用目录, 前端应用目录
|
||||
* 应用目录, 前端应用目录, pages 目录
|
||||
*/
|
||||
pagesDir,
|
||||
/**
|
||||
@@ -64,6 +68,14 @@ export const initConfig = (configRootPath: string) => {
|
||||
* 环境变量配置文件路径
|
||||
*/
|
||||
envConfigPath,
|
||||
/**
|
||||
* 技能目录,配置给 opencode 去用的
|
||||
*/
|
||||
skillsDir,
|
||||
/**
|
||||
* 插件目录, 给 cli 用的,动态加载插件,每一个都是独立的
|
||||
*/
|
||||
pluginsDir,
|
||||
};
|
||||
};
|
||||
export type ReturnInitConfigType = ReturnType<typeof initConfig>;
|
||||
|
||||
49
assistant/src/module/cmd/run.ts
Normal file
49
assistant/src/module/cmd/run.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { spawn } from 'node:child_process'
|
||||
|
||||
type RunCmdOptions = {
|
||||
cmd: string;
|
||||
cwd?: string;
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
type RunResult = {
|
||||
code: number;
|
||||
data: string;
|
||||
}
|
||||
/**
|
||||
* 运行命令行指令
|
||||
* @param opts
|
||||
* @returns
|
||||
*/
|
||||
export const runCmd = (opts: RunCmdOptions): Promise<RunResult> => {
|
||||
const { cmd, cwd } = opts || {};
|
||||
return new Promise<RunResult>((resolve) => {
|
||||
const parts = cmd.split(' ');
|
||||
const command = parts[0];
|
||||
const args = parts.slice(1);
|
||||
const proc = spawn(command, args, {
|
||||
cwd: cwd || process.cwd(),
|
||||
shell: true,
|
||||
env: { ...process.env, ...opts?.env },
|
||||
});
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
let result = ''
|
||||
proc.stdout.on('data', (data: Buffer) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
proc.stderr.on('data', (data: Buffer) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
proc.on('close', (code: number) => {
|
||||
result = stdout;
|
||||
if (stderr) {
|
||||
result += '\n' + stderr;
|
||||
}
|
||||
resolve({ code: code === 0 ? 200 : code, data: result });
|
||||
});
|
||||
proc.on('error', (err: Error) => {
|
||||
resolve({ code: 500, data: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fork } from 'child_process'
|
||||
import { fork } from 'node:child_process'
|
||||
import fs from 'fs';
|
||||
|
||||
export const fileExists = (path: string): boolean => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
67
assistant/src/routes/kevisual/auth.ts
Normal file
67
assistant/src/routes/kevisual/auth.ts
Normal 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);
|
||||
45
assistant/src/routes/kevisual/deploy.ts
Normal file
45
assistant/src/routes/kevisual/deploy.ts
Normal 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);
|
||||
2
assistant/src/routes/kevisual/index.ts
Normal file
2
assistant/src/routes/kevisual/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import './auth.ts'
|
||||
import './deploy.ts'
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user