feat(client): add routes for version, time, system info, and restart functionality

This commit is contained in:
2026-01-31 00:48:15 +08:00
parent ef891e529a
commit 51822506d7
16 changed files with 693 additions and 582 deletions

View File

@@ -10,7 +10,7 @@
],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.28.1",
"packageManager": "pnpm@10.28.2",
"type": "module",
"files": [
"dist",
@@ -41,19 +41,21 @@
}
},
"devDependencies": {
"@kevisual/ai": "^0.0.22",
"@kevisual/api": "^0.0.26",
"@inquirer/prompts": "^8.2.0",
"@kevisual/ai": "^0.0.24",
"@kevisual/api": "^0.0.35",
"@kevisual/cnb": "^0.0.13",
"@kevisual/load": "^0.0.6",
"@kevisual/local-app-manager": "^0.1.32",
"@kevisual/logger": "^0.0.4",
"@kevisual/query": "0.0.38",
"@kevisual/query-login": "0.0.7",
"@kevisual/router": "^0.0.62",
"@kevisual/router": "^0.0.64",
"@kevisual/types": "^0.0.12",
"@kevisual/use-config": "^1.0.28",
"@opencode-ai/plugin": "^1.1.36",
"@types/bun": "^1.3.6",
"@types/node": "^25.0.10",
"@opencode-ai/plugin": "^1.1.44",
"@types/bun": "^1.3.8",
"@types/node": "^25.1.0",
"@types/send": "^1.2.1",
"@types/ws": "^8.18.1",
"chalk": "^5.6.2",
@@ -62,7 +64,6 @@
"dayjs": "^1.11.19",
"dotenv": "^17.2.3",
"get-port": "^7.1.0",
"inquirer": "^13.2.1",
"nanoid": "^5.1.6",
"send": "^1.2.1",
"supports-color": "^10.2.2",
@@ -76,16 +77,16 @@
"access": "public"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.975.0",
"@aws-sdk/client-s3": "^3.978.0",
"@kevisual/ha-api": "^0.0.6",
"@kevisual/js-filter": "^0.0.5",
"@kevisual/oss": "^0.0.16",
"@kevisual/video-tools": "^0.0.13",
"@opencode-ai/sdk": "^1.1.36",
"@opencode-ai/sdk": "^1.1.44",
"es-toolkit": "^1.44.0",
"eventemitter3": "^5.0.4",
"lowdb": "^7.0.1",
"lru-cache": "^11.2.4",
"lru-cache": "^11.2.5",
"pm2": "^6.0.14",
"unstorage": "^1.17.4",
"zod": "^4.3.6"

View File

@@ -84,9 +84,6 @@ importers:
get-port:
specifier: ^7.1.0
version: 7.1.0
inquirer:
specifier: ^12.6.3
version: 12.6.3(@types/node@22.15.29)
lodash-es:
specifier: ^4.17.21
version: 4.17.21
@@ -2136,18 +2133,6 @@ snapshots:
ini@1.3.8: {}
inquirer@12.6.3(@types/node@22.15.29):
dependencies:
'@inquirer/core': 10.1.13(@types/node@22.15.29)
'@inquirer/prompts': 7.5.3(@types/node@22.15.29)
'@inquirer/type': 3.0.7(@types/node@22.15.29)
ansi-escapes: 4.3.2
mute-stream: 2.0.0
run-async: 3.0.0
rxjs: 7.8.2
optionalDependencies:
'@types/node': 22.15.29
ip-address@9.0.5:
dependencies:
jsbn: 1.1.0

View File

@@ -24,9 +24,15 @@ export const assistantQuery = useContextKey('assistantQuery', () => {
return new AssistantQuery(assistantConfig);
});
export const runtime = useContextKey('runtime', () => {
type Runtime = {
type: 'client' | 'server';
isServer?: boolean;
}
export const runtime: Runtime = useContextKey('runtime', () => {
console.log('Runtime detected:', manualParse);
return {
type: 'client',
isServer: manualParse.isServer,
};
});

View File

@@ -28,7 +28,6 @@ const command = new Command('server')
shellCommands.push(`-e ${options.interpreter}`);
}
const basename = _interpreter.split('/').pop();
if (basename.includes('bun')) {
console.log(`Assistant server shell command: bun src/run-server.ts server ${shellCommands.join(' ')}`);
const child = spawnSync(_interpreter, ['src/run-server.ts', ...shellCommands], {

View File

@@ -2,7 +2,7 @@ import { program, Command } from '@/program.ts';
import { AssistantInit } from '@/services/init/index.ts';
import path from 'node:path';
import fs from 'node:fs';
import inquirer from 'inquirer';
import { confirm } from '@inquirer/prompts';
import chalk from 'chalk';
type InitCommandOptions = {
@@ -41,23 +41,17 @@ const removeCommand = new Command('remove')
const assistantDir = path.join(configDir, 'assistant-app');
if (fs.existsSync(assistantDir)) {
inquirer
.prompt([
{
type: 'confirm',
name: 'confirm',
message: `确定要删除助手配置文件吗?\n助手配置文件路径${assistantDir}`,
default: false,
},
])
.then((answers) => {
if (answers.confirm) {
fs.rmSync(assistantDir, { recursive: true, force: true });
console.log(chalk.green('助手配置文件已删除'));
} else {
console.log(chalk.blue('助手配置文件未删除'));
}
});
confirm({
message: `确定要删除助手配置文件吗?\n助手配置文件路径${assistantDir}`,
default: false,
}).then((confirmed) => {
if (confirmed) {
fs.rmSync(assistantDir, { recursive: true, force: true });
console.log(chalk.green('助手配置文件已删除'));
} else {
console.log(chalk.blue('助手配置文件未删除'));
}
});
} else {
console.log(chalk.blue('助手配置文件不存在'));
}

View File

@@ -85,6 +85,7 @@ type AuthPermission = {
username?: string; // 用户名
admin?: string[];
};
type AssistantRoutes = { type: "npm" | "file", path: string } | string
export type AssistantConfigData = {
app?: {
/**
@@ -117,6 +118,7 @@ export type AssistantConfigData = {
base?: boolean;
lightcode?: boolean;
}
routes?: AssistantRoutes[],
/**
* API 代理配置, 比如api开头的v1开头的等等
*/
@@ -418,10 +420,27 @@ export const parseHomeArg = (homedir?: string) => {
}
const checkUrl = ['.opencode', 'bin/opencode', 'opencode.exe']
const isOpencode = checkUrl.some((item) => execPath.includes(item))
let isServer = false;
// 如果args包含 server 则认为是服务端运行。其中config中server必须存在
console.log('parseHomeArg args:', args);
if (args.includes('server') || args.includes('assistant-server')) {
let isDaemon = false;
// 判断 --daemon 参数, 如果有则认为是守护进程运行
if (args.includes('--daemon') || args.includes('-d')) {
isDaemon = true;
}
if (!isDaemon) {
// 判断 -s 或者 --start 参数
if (args.includes('-s') || args.includes('--start')) {
isServer = true;
}
}
}
return {
isOpencode,
options,
configDir: _configDir,
isServer
};
};

View File

@@ -211,4 +211,22 @@ export class AssistantApp extends Manager {
}
}
}
async initRoutes() {
// TODO 初始化应用内置路由
const routes = this.config.getConfig().routes || [];
for (const route of routes) {
try {
if (typeof route === 'string') {
await import(route);
console.log('安装路由', route);
} else if (typeof route === 'object' && route.path) {
const routePath = route.path;
await import(routePath);
console.log('安装路由', routePath);
}
} catch (err) {
console.error('初始化路由失败', route, err);
}
}
}
}

View File

@@ -1,4 +1,4 @@
import { ChildProcess, fork, ForkOptions } from 'child_process';
import { ChildProcess, fork, ForkOptions } from 'node:child_process';
class BaseProcess {
private process: ChildProcess;
status: 'running' | 'stopped' | 'error' = 'stopped';

View File

@@ -0,0 +1,84 @@
import { app, assistantConfig } from '../../app.ts';
import { createSkill } from '@kevisual/router';
import os from 'node:os';
import { runCommand } from '@/services/app/index.ts';
app
.route({
path: 'client',
key: 'version',
description: '获取客户端版本号',
})
.define(async (ctx) => {
ctx.body = 'v1.0.0';
})
.addTo(app);
app
.route({
path: 'client',
key: 'time',
description: '获取当前时间',
})
.define(async (ctx) => {
ctx.body = {
time: new Date().getTime(),
date: new Date().toLocaleDateString(),
};
})
.addTo(app);
// 调用 path: client key: system
app
.route({
path: 'client',
key: 'system',
description: '获取系统信息',
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'view-system-info',
title: '查看系统信息',
summary: '获取服务器操作系统平台、架构和版本信息',
})
}
})
.define(async (ctx) => {
const { platform, arch, release } = os;
ctx.body = {
platform: platform(),
arch: arch(),
release: release(),
};
})
.addTo(app);
app.route({
path: 'client',
key: 'restart',
description: '重启客户端',
middleware: ['admin-auth'],
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'restart-client',
title: '重启客户端',
summary: '重启当前运行的客户端应用程序',
})
}
}).define(async (ctx) => {
const cmd = 'pm2 restart assistant-server --update-env';
try {
runCommand(cmd, []);
ctx.body = {
message: '客户端重启命令已执行',
};
} catch (error) {
ctx.status = 500;
ctx.body = {
message: '重启客户端失败',
error: error.message,
};
}
}).addTo(app);

View File

@@ -11,9 +11,8 @@ import './opencode/index.ts';
import './remote/index.ts';
import './kevisual/index.ts'
import os from 'node:os';
import { authCache } from '@/module/cache/auth.ts';
import { createSkill } from '@kevisual/router';
import { logger } from '@/module/logger.ts';
const getTokenUser = async (token: string) => {
const query = assistantConfig.query
@@ -135,53 +134,3 @@ app
}
})
.addTo(app);
app
.route({
path: 'client',
key: 'version',
description: '获取客户端版本号',
})
.define(async (ctx) => {
ctx.body = 'v1.0.0';
})
.addTo(app);
app
.route({
path: 'client',
key: 'time',
description: '获取当前时间',
})
.define(async (ctx) => {
ctx.body = {
time: new Date().getTime(),
date: new Date().toLocaleDateString(),
};
})
.addTo(app);
// 调用 path: client key: system
app
.route({
path: 'client',
key: 'system',
description: '获取系统信息',
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'view-system-info',
title: '查看系统信息',
summary: '获取服务器操作系统平台、架构和版本信息',
})
}
})
.define(async (ctx) => {
const { platform, arch, release } = os;
ctx.body = {
platform: platform(),
arch: arch(),
release: release(),
};
})
.addTo(app);

View File

@@ -1,5 +1,5 @@
import { useContextKey } from '@kevisual/context';
import { app, assistantConfig } from './app.ts';
import { app, assistantConfig, runtime } from './app.ts';
import { proxyRoute, proxyWs } from './services/proxy/proxy-page-index.ts';
import './routes/index.ts';
import './routes-simple/index.ts';
@@ -58,6 +58,9 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
});
manager.initRemoteApp()
manager.initRouterApp()
if (runtime.isServer) {
manager.initRoutes();
}
}, 1000);
return {

View File

@@ -1,7 +1,7 @@
import { checkFileExists, AssistantConfig } from '@/module/assistant/index.ts';
import path from 'path';
import fs from 'fs';
import inquirer from 'inquirer';
import { confirm } from '@inquirer/prompts';
import { spawnSync } from 'child_process';
export const runCommand = (command: string, args: string[]) => {
@@ -91,15 +91,10 @@ export class AppDownload {
return runCommand(command, args);
}
async confirm(message?: string) {
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: message || '是否继续删除应用?',
default: false,
},
]);
return confirm;
return await confirm({
message: message || '是否继续删除应用?',
default: false,
});
}
async deleteApp(opts: DeleteAppOptions) {
const { id, type = 'web', yes = false } = opts;

View File

@@ -136,11 +136,11 @@ export class AssistantInit extends AssistantConfig {
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-s3": "^3.975.0",
"@aws-sdk/client-s3": "^3.978.0",
"@kevisual/oss": "^0.0.16",
"@kevisual/query": "^0.0.38",
"eventemitter3": "^5.0.4",
"@kevisual/router": "^0.0.62",
"@kevisual/router": "^0.0.64",
"@kevisual/use-config": "^1.0.28",
"ioredis": "^5.9.2",
"minio": "^8.0.6",
@@ -157,16 +157,18 @@ export class AssistantInit extends AssistantConfig {
},
"devDependencies": {
"@kevisual/types": "^0.0.12",
"@types/bun": "^1.3.6",
"@types/bun": "^1.3.8",
"@types/crypto-js": "^4.2.2",
"@types/node": "^25.0.10"
"@types/node": "^25.1.0"
}
}
`,
);
console.log(chalk.green('助手 package.json 文件创建成功, 正在安装依赖...'));
installDeps({ appPath: path.dirname(packagePath), isProduction: true }).then(() => {
console.log('------------------------------------------------');
console.log(chalk.green('助手依赖安装完成'));
console.log('------------------------------------------------');
});
}
return {

View File

@@ -12,11 +12,11 @@
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-s3": "^3.975.0",
"@aws-sdk/client-s3": "^3.978.0",
"@kevisual/oss": "^0.0.16",
"@kevisual/query": "^0.0.38",
"eventemitter3": "^5.0.4",
"@kevisual/router": "^0.0.62",
"@kevisual/router": "^0.0.64",
"@kevisual/use-config": "^1.0.28",
"ioredis": "^5.9.2",
"minio": "^8.0.6",
@@ -33,8 +33,8 @@
},
"devDependencies": {
"@kevisual/types": "^0.0.12",
"@types/bun": "^1.3.6",
"@types/bun": "^1.3.8",
"@types/crypto-js": "^4.2.2",
"@types/node": "^25.0.10"
"@types/node": "^25.1.0"
}
}