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

@@ -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"
}
}