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

View File

@@ -84,9 +84,6 @@ importers:
get-port: get-port:
specifier: ^7.1.0 specifier: ^7.1.0
version: 7.1.0 version: 7.1.0
inquirer:
specifier: ^12.6.3
version: 12.6.3(@types/node@22.15.29)
lodash-es: lodash-es:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
@@ -2136,18 +2133,6 @@ snapshots:
ini@1.3.8: {} 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: ip-address@9.0.5:
dependencies: dependencies:
jsbn: 1.1.0 jsbn: 1.1.0

View File

@@ -24,9 +24,15 @@ export const assistantQuery = useContextKey('assistantQuery', () => {
return new AssistantQuery(assistantConfig); 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 { return {
type: 'client', type: 'client',
isServer: manualParse.isServer,
}; };
}); });

View File

@@ -28,7 +28,6 @@ const command = new Command('server')
shellCommands.push(`-e ${options.interpreter}`); shellCommands.push(`-e ${options.interpreter}`);
} }
const basename = _interpreter.split('/').pop(); const basename = _interpreter.split('/').pop();
if (basename.includes('bun')) { if (basename.includes('bun')) {
console.log(`Assistant server shell command: bun src/run-server.ts server ${shellCommands.join(' ')}`); console.log(`Assistant server shell command: bun src/run-server.ts server ${shellCommands.join(' ')}`);
const child = spawnSync(_interpreter, ['src/run-server.ts', ...shellCommands], { 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 { AssistantInit } from '@/services/init/index.ts';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import inquirer from 'inquirer'; import { confirm } from '@inquirer/prompts';
import chalk from 'chalk'; import chalk from 'chalk';
type InitCommandOptions = { type InitCommandOptions = {
@@ -41,23 +41,17 @@ const removeCommand = new Command('remove')
const assistantDir = path.join(configDir, 'assistant-app'); const assistantDir = path.join(configDir, 'assistant-app');
if (fs.existsSync(assistantDir)) { if (fs.existsSync(assistantDir)) {
inquirer confirm({
.prompt([ message: `确定要删除助手配置文件吗?\n助手配置文件路径${assistantDir}`,
{ default: false,
type: 'confirm', }).then((confirmed) => {
name: 'confirm', if (confirmed) {
message: `确定要删除助手配置文件吗?\n助手配置文件路径${assistantDir}`, fs.rmSync(assistantDir, { recursive: true, force: true });
default: false, console.log(chalk.green('助手配置文件已删除'));
}, } else {
]) console.log(chalk.blue('助手配置文件未删除'));
.then((answers) => { }
if (answers.confirm) { });
fs.rmSync(assistantDir, { recursive: true, force: true });
console.log(chalk.green('助手配置文件已删除'));
} else {
console.log(chalk.blue('助手配置文件未删除'));
}
});
} else { } else {
console.log(chalk.blue('助手配置文件不存在')); console.log(chalk.blue('助手配置文件不存在'));
} }

View File

@@ -85,6 +85,7 @@ type AuthPermission = {
username?: string; // 用户名 username?: string; // 用户名
admin?: string[]; admin?: string[];
}; };
type AssistantRoutes = { type: "npm" | "file", path: string } | string
export type AssistantConfigData = { export type AssistantConfigData = {
app?: { app?: {
/** /**
@@ -117,6 +118,7 @@ export type AssistantConfigData = {
base?: boolean; base?: boolean;
lightcode?: boolean; lightcode?: boolean;
} }
routes?: AssistantRoutes[],
/** /**
* API 代理配置, 比如api开头的v1开头的等等 * API 代理配置, 比如api开头的v1开头的等等
*/ */
@@ -418,10 +420,27 @@ export const parseHomeArg = (homedir?: string) => {
} }
const checkUrl = ['.opencode', 'bin/opencode', 'opencode.exe'] const checkUrl = ['.opencode', 'bin/opencode', 'opencode.exe']
const isOpencode = checkUrl.some((item) => execPath.includes(item)) 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 { return {
isOpencode, isOpencode,
options, options,
configDir: _configDir, 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 { class BaseProcess {
private process: ChildProcess; private process: ChildProcess;
status: 'running' | 'stopped' | 'error' = 'stopped'; 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 './remote/index.ts';
import './kevisual/index.ts' import './kevisual/index.ts'
import os from 'node:os';
import { authCache } from '@/module/cache/auth.ts'; import { authCache } from '@/module/cache/auth.ts';
import { createSkill } from '@kevisual/router';
import { logger } from '@/module/logger.ts'; import { logger } from '@/module/logger.ts';
const getTokenUser = async (token: string) => { const getTokenUser = async (token: string) => {
const query = assistantConfig.query const query = assistantConfig.query
@@ -135,53 +134,3 @@ app
} }
}) })
.addTo(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 { 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 { proxyRoute, proxyWs } from './services/proxy/proxy-page-index.ts';
import './routes/index.ts'; import './routes/index.ts';
import './routes-simple/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.initRemoteApp()
manager.initRouterApp() manager.initRouterApp()
if (runtime.isServer) {
manager.initRoutes();
}
}, 1000); }, 1000);
return { return {

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@kevisual/cli", "name": "@kevisual/cli",
"version": "0.0.96", "version": "0.0.97",
"description": "envision 命令行工具", "description": "envision 命令行工具",
"type": "module", "type": "module",
"basename": "/root/cli", "basename": "/root/cli",
@@ -49,13 +49,13 @@
"@kevisual/auth": "^2.0.3", "@kevisual/auth": "^2.0.3",
"@kevisual/context": "^0.0.4", "@kevisual/context": "^0.0.4",
"@kevisual/use-config": "^1.0.28", "@kevisual/use-config": "^1.0.28",
"@opencode-ai/sdk": "^1.1.36", "@opencode-ai/sdk": "^1.1.44",
"@types/busboy": "^1.5.4", "@types/busboy": "^1.5.4",
"busboy": "^1.6.0", "busboy": "^1.6.0",
"eventemitter3": "^5.0.4", "eventemitter3": "^5.0.4",
"jose": "^6.1.3", "jose": "^6.1.3",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"lru-cache": "^11.2.4", "lru-cache": "^11.2.5",
"micromatch": "^4.0.8", "micromatch": "^4.0.8",
"pm2": "latest", "pm2": "latest",
"semver": "^7.7.3", "semver": "^7.7.3",
@@ -67,11 +67,11 @@
"@kevisual/logger": "^0.0.4", "@kevisual/logger": "^0.0.4",
"@kevisual/query": "0.0.38", "@kevisual/query": "0.0.38",
"@kevisual/query-login": "0.0.7", "@kevisual/query-login": "0.0.7",
"@types/bun": "^1.3.6", "@types/bun": "^1.3.8",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.10",
"@types/micromatch": "^4.0.10", "@types/micromatch": "^4.0.10",
"@types/node": "^25.0.10", "@types/node": "^25.1.0",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"commander": "^14.0.2", "commander": "^14.0.2",
@@ -82,7 +82,7 @@
"ignore": "^7.0.5", "ignore": "^7.0.5",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"pm2": "^6.0.14", "pm2": "^6.0.14",
"tar": "^7.5.6", "tar": "^7.5.7",
"zustand": "^5.0.10" "zustand": "^5.0.10"
}, },
"engines": { "engines": {

972
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff