feat: add user info command and package management for assistant app

- Implemented a new command 'me' to view current user information in the assistant application.
- Created a common configuration file for the assistant app with package details and scripts.
- Added functionality to check and update package.json dependencies and devDependencies in the assistant app.
- Refactored storage initialization in query module to use StorageNode.
This commit is contained in:
2026-02-21 03:08:39 +08:00
parent e53c166c29
commit 68c1976754
26 changed files with 657 additions and 1453 deletions

View File

@@ -3,30 +3,7 @@
"metadata": { "metadata": {
"share": "public" "share": "public"
}, },
"checkDir": { "checkDir": {},
"src/query/query-login": { "syncDirectory": [],
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query/query-login", "sync": {}
"enabled": true
},
"src/query/query-ai": {
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query/query-ai",
"enabled": true
}
},
"syncDirectory": [
{
"files": [
"src/query/query-login/**/*",
"src/query/query-ai/**/*"
],
"ignore": [],
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
"replace": {
"src/": ""
}
}
],
"sync": {
"src/query/index.ts": "https://kevisual.xiongxiao.me/root/ai/code/registry/query/index.ts"
}
} }

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.30.0", "packageManager": "pnpm@10.30.1",
"type": "module", "type": "module",
"files": [ "files": [
"dist", "dist",
@@ -44,17 +44,17 @@
"devDependencies": { "devDependencies": {
"@inquirer/prompts": "^8.2.1", "@inquirer/prompts": "^8.2.1",
"@kevisual/ai": "^0.0.24", "@kevisual/ai": "^0.0.24",
"@kevisual/api": "^0.0.52", "@kevisual/api": "^0.0.55",
"@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.49", "@kevisual/query": "0.0.49",
"@kevisual/router": "^0.0.80", "@kevisual/router": "^0.0.83",
"@kevisual/types": "^0.0.12", "@kevisual/types": "^0.0.12",
"@kevisual/use-config": "^1.0.30", "@kevisual/use-config": "^1.0.30",
"@opencode-ai/plugin": "^1.2.6", "@opencode-ai/plugin": "^1.2.10",
"@types/bun": "^1.3.9", "@types/bun": "^1.3.9",
"@types/node": "^25.2.3", "@types/node": "^25.3.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",
@@ -76,11 +76,11 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.992.0", "@aws-sdk/client-s3": "^3.994.0",
"@kevisual/js-filter": "^0.0.5", "@kevisual/js-filter": "^0.0.5",
"@kevisual/oss": "^0.0.19", "@kevisual/oss": "^0.0.19",
"@kevisual/video-tools": "^0.0.13", "@kevisual/video-tools": "^0.0.13",
"@opencode-ai/sdk": "^1.2.6", "@opencode-ai/sdk": "^1.2.10",
"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",

View File

@@ -1,5 +1,4 @@
import { program, Command, assistantConfig } from '@/program.ts'; import { program, Command, assistantConfig } from '@/program.ts';
import { spawnSync } from 'node:child_process';
import { parseHomeArg, HomeConfigDir } from '@/module/assistant/config/args.ts'; import { parseHomeArg, HomeConfigDir } from '@/module/assistant/config/args.ts';
import { execCommand } from '@/module/npm-install.ts'; import { execCommand } from '@/module/npm-install.ts';

View File

@@ -0,0 +1,14 @@
import { program, Command, assistantConfig } from '@/program.ts';
import { AssistantQuery } from '@/lib.ts';
import { logger } from '@/module/logger.ts';
const me = new Command('me')
.description('查看当前用户信息')
.action(async () => {
const aq = new AssistantQuery(assistantConfig);
await aq.init()
const info = await aq.queryLogin.checkLocalUser()
logger.info(info);
});
program.addCommand(me);

View File

@@ -6,6 +6,7 @@ import './command/app/index.ts';
import './command/run-scripts/index.ts'; import './command/run-scripts/index.ts';
import './command/ai/index.ts'; import './command/ai/index.ts';
import './command/plugins/install.ts'; import './command/plugins/install.ts';
import './command/user/me.ts';
/** /**
* 通过命令行解析器解析参数 * 通过命令行解析器解析参数

View File

@@ -1,5 +1,4 @@
import fs from 'node:fs'; import fs from 'node:fs';
import { useKey } from '@kevisual/use-config';
export const getFileConfig = (filePath: string): any => { export const getFileConfig = (filePath: string): any => {
return JSON.parse(fs.readFileSync(filePath, 'utf8')); return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} }

View File

@@ -1,15 +1,11 @@
import path from 'path'; import path from 'path';
import { homedir } from 'os';
import fs from 'fs'; import fs from 'fs';
import { checkFileExists, createDir } from '../file/index.ts'; import { checkFileExists, createDir } from '../file/index.ts';
import { ProxyInfo } from '../proxy/proxy.ts'; import { ProxyInfo } from '../proxy/proxy.ts';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import { logger } from '@/module/logger.ts';
import { z } from 'zod'
import { HomeConfigDir } from './args.ts' import { HomeConfigDir } from './args.ts'
import { getFileConfig } from './get-assistan-config.ts'; import { getFileConfig } from './get-assistan-config.ts';
import { useKey } from '@kevisual/use-config'; import { useKey } from '@kevisual/use-config';
import { env } from 'pm2';
/** /**
* 助手配置文件路径, 全局配置文件目录 * 助手配置文件路径, 全局配置文件目录
@@ -231,25 +227,15 @@ export class AssistantConfig {
getConfig(): AssistantConfigData { getConfig(): AssistantConfigData {
try { try {
if (!checkFileExists(this.configPath.configPath)) { if (!checkFileExists(this.configPath.configPath)) {
fs.writeFileSync(this.configPath.configPath, JSON.stringify({ proxy: [] }, null, 2)); const defaultConfig = this.getDefaultInitAssistantConfig();
return { fs.writeFileSync(this.configPath.configPath, JSON.stringify(defaultConfig, null, 2));
app: { return defaultConfig;
url: 'https://kevisual.cn',
},
proxy: [],
};
} }
assistantConfig = getFileConfig(this.configPath.configPath); assistantConfig = getFileConfig(this.configPath.configPath);
return assistantConfig; return assistantConfig;
} catch (error) { } catch (error) {
console.error('file read', error.message); console.error('file read', error.message);
process.exit(1); process.exit(1);
return {
app: {
url: 'https://kevisual.cn',
},
proxy: [],
};
} }
} }
getCacheAssistantConfig() { getCacheAssistantConfig() {
@@ -393,6 +379,23 @@ export class AssistantConfig {
// 如果没有找到助手配置文件目录,则返回当前目录, 执行默认创建助手配置文件目录 // 如果没有找到助手配置文件目录,则返回当前目录, 执行默认创建助手配置文件目录
return checkConfigDir; return checkConfigDir;
} }
protected getDefaultInitAssistantConfig() {
const id = randomId();
return {
app: {
url: 'https://kevisual.cn',
id,
},
description: '助手配置文件',
docs: "https://kevisual.cn/root/cli/docs/",
home: '/root/home',
proxy: [],
share: {
enabled: false,
url: 'https://kevisual.cn/ws/proxy',
},
} as AssistantConfigData;
}
} }
type AppConfig = { type AppConfig = {
@@ -409,3 +412,5 @@ export const parseIfJson = (content: string) => {
}; };
export * from './args.ts'; export * from './args.ts';
const randomId = () => Math.random().toString(36).substring(2, 8);

View File

@@ -216,7 +216,7 @@ export class AssistantApp extends Manager {
const routeStr = typeof route === 'string' ? route : route.path; const routeStr = typeof route === 'string' ? route : route.path;
const resolvedPath = this.resolver.resolve(routeStr); const resolvedPath = this.resolver.resolve(routeStr);
await import(resolvedPath); await import(resolvedPath);
console.log('[routes] 路由已初始化', route, resolvedPath); console.log('[routes] 路由已初始化', route);
} catch (err) { } catch (err) {
console.error('初始化路由失败', route, err); console.error('初始化路由失败', route, err);
} }

View File

@@ -1,14 +1,33 @@
import { DataOpts, Query } from "@kevisual/query/query"; import { DataOpts, Query } from "@kevisual/query/query";
import { AssistantConfig } from "../config/index.ts"; import { AssistantConfig } from "../config/index.ts";
import { QueryLoginNode } from '@kevisual/api/query-login-node'
import { EventEmitter } from 'eventemitter3'
export class AssistantQuery { export class AssistantQuery {
config: AssistantConfig; config: AssistantConfig;
query: Query ; query: Query;
queryLogin: QueryLoginNode;
emitter = new EventEmitter();
isLoad = false;
constructor(config: AssistantConfig) { constructor(config: AssistantConfig) {
config.checkMounted(); config.checkMounted();
this.config = config; this.config = config;
this.query = new Query({ url: config.getRegistry() + '/api/router' }); this.query = new Query({ url: config.getRegistry() + '/api/router' });
this.queryLogin = new QueryLoginNode({
query: this.query, onLoad: () => {
this.isLoad = true;
this.emitter.emit('load')
}
});
}
async init() {
if (this.isLoad) return true;
return new Promise((resolve) => {
this.emitter.on('load', () => {
this.isLoad = true;
resolve(true);
})
})
} }
post(body: any, options?: DataOpts) { post(body: any, options?: DataOpts) {
return this.query.post(body, options); return this.query.post(body, options);
@@ -16,4 +35,7 @@ export class AssistantQuery {
get(body: any, options?: DataOpts) { get(body: any, options?: DataOpts) {
return this.query.get(body, options); return this.query.get(body, options);
} }
getToken() {
return this.queryLogin.getToken();
}
} }

View File

@@ -29,9 +29,9 @@ export const installDeps = async (opts: InstallDepsOptions) => {
console.log('installDeps', appPath, params); console.log('installDeps', appPath, params);
const syncSpawn = opts.sync ? spawnSync : spawn; const syncSpawn = opts.sync ? spawnSync : spawn;
if (isPnpm) { if (isPnpm) {
syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: process.env }); syncSpawn('pnpm', params, { cwd: appPath, stdio: 'inherit', env: { ...process.env, CI: 'true' }, shell: true });
} else { } else {
syncSpawn('npm', params, { cwd: appPath, stdio: 'inherit', env: process.env }); syncSpawn('npm', params, { cwd: appPath, stdio: 'inherit', env: process.env, shell: true });
} }
}; };

View File

@@ -1,7 +0,0 @@
import { Query } from '@kevisual/query';
export const query = new Query();
export const clientQuery = new Query({ url: '/client/router' });
export { QueryUtil } from '@kevisual/router/define';

View File

@@ -1,42 +0,0 @@
import { QueryUtil } from '@/query/index.ts';
type Message = {
role?: 'user' | 'assistant' | 'system' | 'tool';
content?: string;
name?: string;
};
export type PostChat = {
messages?: Message[];
model?: string;
group?: string;
user?: string;
};
export type ChatDataOpts = {
id?: string;
title?: string;
messages?: any[];
data?: any;
type?: 'temp' | 'keep' | string;
};
export type ChatOpts = {
username: string;
model: string;
/**
* 获取完整消息回复
*/
getFull?: boolean;
group: string;
/**
* openai的参数
*/
options?: any;
};
export const appDefine = QueryUtil.create({
chat: {
path: 'ai',
key: 'chat',
description: '与 AI 进行对话, 调用 GPT 的AI 服务,生成结果,并返回。',
},
});

View File

@@ -1,101 +0,0 @@
import { appDefine } from './defines/ai.ts';
import { PostChat, ChatOpts, ChatDataOpts } from './defines/ai.ts';
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
export { appDefine };
export class QueryApp<T extends Query = Query> extends BaseQuery<T, typeof appDefine> {
constructor(opts?: { query: T }) {
super({
...opts,
query: opts?.query!,
queryDefine: appDefine,
});
}
/**
* 与 AI 进行对话, 调用 GPT 的AI 服务,生成结果,并返回。
* @param data
* @param opts
* @returns
*/
postChat(data: PostChat, opts?: DataOpts) {
return this.chain('chat').post(data, opts);
}
/**
* 获取模型列表
* @param opts
* @returns
*/
getModelList(data?: { usernames?: string[] }, opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'get-model-list',
data,
},
opts,
);
}
/**
* 聊天对话模型
* @param data
* @param chatOpts
* @param opts
* @returns
*/
chat(data: ChatDataOpts, chatOpts: ChatOpts, opts?: DataOpts) {
const { username, model, group, getFull = true } = chatOpts;
if (!username || !model || !group) {
throw new Error('username, model, group is required');
}
return this.query.post(
{
path: 'ai',
key: 'chat',
...chatOpts,
getFull,
data,
},
opts,
);
}
clearConfigCache(opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'clear-cache',
},
opts,
);
}
/**
* 获取聊天使用情况
* @param opts
* @returns
*/
getChatUsage(opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'get-chat-usage',
},
opts,
);
}
/**
* 清除当前用户模型自己的统计
* @param opts
* @returns
*/
clearSelfUsage(opts?: DataOpts) {
return this.query.post(
{
path: 'ai',
key: 'clear-chat-limit',
},
opts,
);
}
}

View File

@@ -1,204 +0,0 @@
export interface Cache {
/**
* @update 获取缓存
*/
get(key: string): Promise<any>;
/**
* @update 设置缓存
*/
set(key: string, value: any): Promise<any>;
/**
* @update 删除缓存
*/
del(): Promise<void>;
/**
* 初始化
*/
init?: () => Promise<any>;
}
type User = {
avatar?: string;
description?: string;
id?: string;
needChangePassword?: boolean;
orgs?: string[];
type?: string;
username?: string;
};
export type CacheLoginUser = {
user?: User;
id?: string;
accessToken?: string;
refreshToken?: string;
};
type CacheLogin = {
loginUsers: CacheLoginUser[];
} & CacheLoginUser;
export type CacheStore<T = Cache> = {
name: string;
/**
* 缓存数据
* @important 需要先调用init
*/
cacheData: CacheLogin;
/**
* 实际操作的cache, 需要先调用init
*/
cache: T;
/**
* 设置当前用户
*/
setLoginUser(user: CacheLoginUser): Promise<void>;
/**
* 获取当前用户
*/
getCurrentUser(): Promise<User>;
/**
* 获取当前用户列表
*/
getCurrentUserList(): Promise<CacheLoginUser[]>;
/**
* 获取缓存的refreshToken
*/
getRefreshToken(): Promise<string>;
/**
* 获取缓存的accessToken
*/
getAccessToken(): Promise<string>;
/**
* 清除当前用户
*/
clearCurrentUser(): Promise<void>;
/**
* 清除所有用户
*/
clearAll(): Promise<void>;
getValue(): Promise<CacheLogin>;
setValue(value: CacheLogin): Promise<CacheLogin>;
delValue(): Promise<void>;
init(): Promise<any>;
};
export type LoginCacheStoreOpts = {
name: string;
cache: Cache;
};
export class LoginCacheStore implements CacheStore<any> {
cache: Cache;
name: string;
cacheData: CacheLogin;
constructor(opts: LoginCacheStoreOpts) {
if (!opts.cache) {
throw new Error('cache is required');
}
// @ts-ignore
this.cache = opts.cache;
this.cacheData = {
loginUsers: [],
user: undefined,
id: undefined,
accessToken: undefined,
refreshToken: undefined,
};
this.name = opts.name;
}
/**
* 设置缓存
* @param key
* @param value
* @returns
*/
async setValue(value: CacheLogin) {
await this.cache.set(this.name, value);
this.cacheData = value;
return value;
}
/**
* 删除缓存
*/
async delValue() {
await this.cache.del();
}
getValue(): Promise<CacheLogin> {
return this.cache.get(this.name);
}
/**
* 初始化,设置默认值
*/
async init() {
const defaultData = {
loginUsers: [],
user: null,
id: null,
accessToken: null,
refreshToken: null,
};
if (this.cache.init) {
try {
const cacheData = await this.cache.init();
this.cacheData = cacheData || defaultData;
} catch (error) {
console.log('cacheInit error', error);
}
} else {
this.cacheData = (await this.getValue()) || defaultData;
}
}
/**
* 设置当前用户
* @param user
*/
async setLoginUser(user: CacheLoginUser) {
const has = this.cacheData.loginUsers.find((u) => u.id === user.id);
if (has) {
this.cacheData.loginUsers = this.cacheData?.loginUsers?.filter((u) => u?.id && u.id !== user.id);
}
this.cacheData.loginUsers.push(user);
this.cacheData.user = user.user;
this.cacheData.id = user.id;
this.cacheData.accessToken = user.accessToken;
this.cacheData.refreshToken = user.refreshToken;
await this.setValue(this.cacheData);
}
getCurrentUser(): Promise<CacheLoginUser> {
const cacheData = this.cacheData;
return Promise.resolve(cacheData.user!);
}
getCurrentUserList(): Promise<CacheLoginUser[]> {
return Promise.resolve(this.cacheData.loginUsers.filter((u) => u?.id));
}
getRefreshToken(): Promise<string> {
const cacheData = this.cacheData;
return Promise.resolve(cacheData.refreshToken || '');
}
getAccessToken(): Promise<string> {
const cacheData = this.cacheData;
return Promise.resolve(cacheData.accessToken || '');
}
async clearCurrentUser() {
const user = await this.getCurrentUser();
const has = this.cacheData.loginUsers.find((u) => u.id === user.id);
if (has) {
this.cacheData.loginUsers = this.cacheData?.loginUsers?.filter((u) => u?.id && u.id !== user.id);
}
this.cacheData.user = undefined;
this.cacheData.id = undefined;
this.cacheData.accessToken = undefined;
this.cacheData.refreshToken = undefined;
await this.setValue(this.cacheData);
}
async clearAll() {
this.cacheData.loginUsers = [];
this.cacheData.user = undefined;
this.cacheData.id = undefined;
this.cacheData.accessToken = undefined;
this.cacheData.refreshToken = undefined;
await this.setValue(this.cacheData);
}
}

View File

@@ -1,132 +0,0 @@
import { Cache } from './login-cache.ts';
import { homedir } from 'node:os';
import { join, dirname } from 'node:path';
import fs from 'node:fs';
import { readFileSync, writeFileSync, accessSync } from 'node:fs';
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
export const fileExists = async (
filePath: string,
{ createIfNotExists = true, isFile = true, isDir = false }: { createIfNotExists?: boolean; isFile?: boolean; isDir?: boolean } = {},
) => {
try {
accessSync(filePath, fs.constants.F_OK);
return true;
} catch (error) {
if (createIfNotExists && isDir) {
await mkdir(filePath, { recursive: true });
return true;
} else if (createIfNotExists && isFile) {
await mkdir(dirname(filePath), { recursive: true });
return false;
}
return false;
}
};
export const readConfigFile = (filePath: string) => {
try {
const data = readFileSync(filePath, 'utf-8');
const jsonData = JSON.parse(data);
return jsonData;
} catch (error) {
return {};
}
};
export const writeConfigFile = (filePath: string, data: any) => {
writeFileSync(filePath, JSON.stringify(data, null, 2));
};
export const getHostName = () => {
const configDir = join(homedir(), '.config', 'envision');
const configFile = join(configDir, 'config.json');
const config = readConfigFile(configFile);
const baseURL = config.baseURL || 'https://kevisual.cn';
const hostname = new URL(baseURL).hostname;
return hostname;
};
export class StorageNode implements Storage {
cacheData: any;
filePath: string;
constructor() {
this.cacheData = {};
const configDir = join(homedir(), '.config', 'envision');
const hostname = getHostName();
this.filePath = join(configDir, 'config', `${hostname}-storage.json`);
fileExists(this.filePath, { isFile: true });
}
async loadCache() {
const filePath = this.filePath;
try {
const data = await readConfigFile(filePath);
this.cacheData = data;
} catch (error) {
this.cacheData = {};
await writeFile(filePath, JSON.stringify(this.cacheData, null, 2));
}
}
get length() {
return Object.keys(this.cacheData).length;
}
getItem(key: string) {
return this.cacheData[key];
}
setItem(key: string, value: any) {
this.cacheData[key] = value;
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
}
removeItem(key: string) {
delete this.cacheData[key];
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
}
clear() {
this.cacheData = {};
writeFile(this.filePath, JSON.stringify(this.cacheData, null, 2));
}
key(index: number) {
return Object.keys(this.cacheData)[index];
}
}
export class LoginNodeCache implements Cache {
filepath: string;
constructor(filepath?: string) {
this.filepath = filepath || join(homedir(), '.config', 'envision', 'config', `${getHostName()}-login.json`);
fileExists(this.filepath, { isFile: true });
}
async get(_key: string) {
try {
const filePath = this.filepath;
const data = readConfigFile(filePath);
return data;
} catch (error) {
console.log('get error', error);
return {};
}
}
async set(_key: string, value: any) {
try {
const data = readConfigFile(this.filepath);
const newData = { ...data, ...value };
writeConfigFile(this.filepath, newData);
} catch (error) {
console.log('set error', error);
}
}
async del() {
await unlink(this.filepath);
}
async loadCache(filePath: string) {
try {
const data = await readFile(filePath, 'utf-8');
const jsonData = JSON.parse(data);
return jsonData;
} catch (error) {
// console.log('loadCache error', error);
console.log('create new cache file:', filePath);
const defaultData = { loginUsers: [] };
writeConfigFile(filePath, defaultData);
return defaultData;
}
}
async init() {
return await this.loadCache(this.filepath);
}
}

View File

@@ -1,12 +0,0 @@
import { QueryLogin, QueryLoginOpts } from './query-login.ts';
import { MyCache } from '@kevisual/cache';
type QueryLoginNodeOptsWithoutCache = Omit<QueryLoginOpts, 'cache'>;
export class QueryLoginBrowser extends QueryLogin {
constructor(opts: QueryLoginNodeOptsWithoutCache) {
super({
...opts,
cache: new MyCache('login'),
});
}
}

View File

@@ -1,14 +0,0 @@
import { QueryLogin, QueryLoginOpts } from './query-login.ts';
import { LoginNodeCache, StorageNode } from './login-node-cache.ts';
type QueryLoginNodeOptsWithoutCache = Omit<QueryLoginOpts, 'cache'>;
export const storage = new StorageNode();
await storage.loadCache();
export class QueryLoginNode extends QueryLogin {
constructor(opts: QueryLoginNodeOptsWithoutCache) {
super({
...opts,
storage,
cache: new LoginNodeCache(),
});
}
}

View File

@@ -1,434 +0,0 @@
import { Query, BaseQuery } from '@kevisual/query';
import type { Result, DataOpts } from '@kevisual/query/query';
import { setBaseResponse } from '@kevisual/query/query';
import { LoginCacheStore, CacheStore } from './login-cache.ts';
import { Cache } from './login-cache.ts';
export type QueryLoginOpts = {
query?: Query;
isBrowser?: boolean;
onLoad?: () => void;
storage?: Storage;
cache: Cache;
};
export type QueryLoginData = {
username?: string;
password: string;
email?: string;
};
export type QueryLoginResult = {
accessToken: string;
refreshToken: string;
};
export class QueryLogin extends BaseQuery {
/**
* query login cache 非实际操作, 一个cache的包裹模块
*/
cacheStore: CacheStore;
isBrowser: boolean;
load?: boolean;
storage: Storage;
onLoad?: () => void;
constructor(opts?: QueryLoginOpts) {
super({
query: opts?.query || new Query(),
});
this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts?.cache! });
this.isBrowser = opts?.isBrowser ?? true;
this.init();
this.onLoad = opts?.onLoad;
this.storage = opts?.storage || localStorage;
}
setQuery(query: Query) {
this.query = query;
}
private async init() {
await this.cacheStore.init();
this.load = true;
this.onLoad?.();
}
async post<T = any>(data: any, opts?: DataOpts) {
try {
return this.query.post<T>({ path: 'user', ...data }, opts);
} catch (error) {
console.log('error', error);
return {
code: 400,
} as any;
}
}
/**
* 登录,
* @param data
* @returns
*/
async login(data: QueryLoginData) {
const res = await this.post<QueryLoginResult>({ key: 'login', ...data });
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
}
return res;
}
/**
* 手机号登录
* @param data
* @returns
*/
async loginByCode(data: { phone: string; code: string }) {
const res = await this.post<QueryLoginResult>({ path: 'sms', key: 'login', data });
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
}
return res;
}
/**
* 设置token
* @param token
*/
async setLoginToken(token: { accessToken: string; refreshToken: string }) {
const { accessToken, refreshToken } = token;
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
}
async loginByWechat(data: { code: string }) {
const res = await this.post<QueryLoginResult>({ path: 'wx', key: 'open-login', code: data.code });
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
}
return res;
}
/**
* 检测微信登录登陆成功后调用onSuccess否则调用onError
* @param param0
*/
async checkWechat({ onSuccess, onError }: { onSuccess?: (res: QueryLoginResult) => void; onError?: (res: any) => void }) {
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
if (code && state) {
const res = await this.loginByWechat({ code });
if (res.code === 200) {
onSuccess?.(res.data);
} else {
onError?.(res);
}
}
}
/**
* 登陆成功,需要获取用户信息进行缓存
* @param param0
*/
async beforeSetLoginUser({ accessToken, refreshToken, check401 }: { accessToken?: string; refreshToken?: string; check401?: boolean }) {
if (accessToken && refreshToken) {
const resUser = await this.getMe(accessToken, check401);
if (resUser.code === 200) {
const user = resUser.data;
if (user) {
this.cacheStore.setLoginUser({
user,
id: user.id,
accessToken,
refreshToken,
});
} else {
console.error('登录失败');
}
}
}
}
/**
* 刷新token
* @param refreshToken
* @returns
*/
async queryRefreshToken(refreshToken?: string) {
const _refreshToken = refreshToken || this.cacheStore.getRefreshToken();
let data = { refreshToken: _refreshToken };
if (!_refreshToken) {
await this.cacheStore.clearCurrentUser();
return {
code: 401,
message: '请先登录',
data: {} as any,
};
}
return this.post(
{ key: 'refreshToken', data },
{
afterResponse: async (response, ctx) => {
setBaseResponse(response);
return response as any;
},
},
);
}
/**
* 检查401错误并刷新token, 如果refreshToken存在则刷新token, 否则返回401
* 拦截请求请使用run401Action 不要直接使用 afterCheck401ToRefreshToken
* @param response
* @param ctx
* @param refetch
* @returns
*/
async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) {
const that = this;
if (response?.code === 401) {
const hasRefreshToken = await that.cacheStore.getRefreshToken();
if (hasRefreshToken) {
const res = await that.queryRefreshToken(hasRefreshToken);
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
that.storage.setItem('token', accessToken || '');
await that.beforeSetLoginUser({ accessToken, refreshToken, check401: false });
if (refetch && ctx && ctx.req && ctx.req.url && ctx.fetch) {
await new Promise((resolve) => setTimeout(resolve, 1500));
const url = ctx.req?.url;
const body = ctx.req?.body;
const headers = ctx.req?.headers;
const res = await ctx.fetch(url, {
method: 'POST',
body: body,
headers: { ...headers, Authorization: `Bearer ${accessToken}` },
});
setBaseResponse(res);
return res;
}
} else {
that.storage.removeItem('token');
await that.cacheStore.clearCurrentUser();
}
return res;
}
}
return response as any;
}
/**
* 一个简单的401处理 如果401则刷新token, 如果refreshToken不存在则返回401
* refetch 是否重新请求, 会有bug无限循环按需要使用
* TODO:
* @param response
* @param ctx
* @param opts
* @returns
*/
async run401Action(
response: Result,
ctx?: { req?: any; res?: any; fetch?: any },
opts?: {
/**
* 是否重新请求, 会有bug无限循环按需要使用
*/
refetch?: boolean;
/**
* check之后的回调
*/
afterCheck?: (res: Result) => any;
/**
* 401处理后 还是401 则回调
*/
afterAlso401?: (res: Result) => any;
},
) {
const that = this;
const refetch = opts?.refetch ?? false;
if (response?.code === 401) {
if (that.query.stop === true) {
return { code: 500, success: false, message: 'refresh token loading...' };
}
that.query.stop = true;
const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch);
that.query.stop = false;
opts?.afterCheck?.(res);
if (res.code === 401) {
opts?.afterAlso401?.(res);
}
return res;
} else {
return response as any;
}
}
/**
* 获取用户信息
* @param token
* @returns
*/
async getMe(token?: string, check401: boolean = true) {
const _token = token || this.storage.getItem('token');
const that = this;
return that.post(
{ key: 'me' },
{
beforeRequest: async (config) => {
if (config.headers) {
config.headers['Authorization'] = `Bearer ${_token}`;
}
if (!_token) {
return false;
}
return config;
},
afterResponse: async (response, ctx) => {
if (response?.code === 401 && check401 && !token) {
return await that.afterCheck401ToRefreshToken(response, ctx);
}
return response as any;
},
},
);
}
/**
* 检查本地用户如果本地用户存在则返回本地用户否则返回null
* @returns
*/
async checkLocalUser() {
const user = await this.cacheStore.getCurrentUser();
if (user) {
return user;
}
return null;
}
/**
* 检查本地token是否存在简单的判断是否已经属于登陆状态
* @returns
*/
async checkLocalToken() {
const token = this.storage.getItem('token');
return !!token;
}
/**
* 检查本地用户列表
* @returns
*/
async getToken() {
const token = this.storage.getItem('token');
return token || '';
}
async beforeRequest(opts: any = {}) {
const token = this.storage.getItem('token');
if (token) {
opts.headers = { ...opts.headers, Authorization: `Bearer ${token}` };
}
return opts;
}
/**
* 请求更新,切换用户, 使用switchUser
* @param username
* @returns
*/
private async postSwitchUser(username: string) {
return this.post({ key: 'switchCheck', data: { username } });
}
/**
* 切换用户
* @param username
* @returns
*/
async switchUser(username: string) {
const localUserList = await this.cacheStore.getCurrentUserList();
const user = localUserList.find((userItem) => userItem.user!.username === username);
if (user) {
this.storage.setItem('token', user.accessToken || '');
await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken });
return {
code: 200,
data: {
accessToken: user.accessToken,
refreshToken: user.refreshToken,
},
success: true,
message: '切换用户成功',
};
}
const res = await this.postSwitchUser(username);
if (res.code === 200) {
const { accessToken, refreshToken } = res?.data || {};
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken });
}
return res;
}
/**
* 退出登陆去掉token 并删除缓存
* @returns
*/
async logout() {
this.storage.removeItem('token');
const users = await this.cacheStore.getCurrentUserList();
const tokens = users
.map((user) => {
return user?.accessToken;
})
.filter(Boolean);
this.cacheStore.delValue();
return this.post<Result>({ key: 'logout', data: { tokens } });
}
/**
* 检查用户名的组,这个用户是否存在
* @param username
* @returns
*/
async hasUser(username: string) {
const that = this;
return this.post<Result>(
{
path: 'org',
key: 'hasUser',
data: {
username,
},
},
{
afterResponse: async (response, ctx) => {
if (response?.code === 401) {
const res = await that.afterCheck401ToRefreshToken(response, ctx, true);
return res;
}
return response as any;
},
},
);
}
/**
* 检查登录状态
* @param token
* @returns
*/
async checkLoginStatus(token: string) {
const res = await this.post({
path: 'user',
key: 'checkLoginStatus',
loginToken: token,
});
if (res.code === 200) {
const accessToken = res.data?.accessToken;
this.storage.setItem('token', accessToken || '');
await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken });
return res;
}
return false;
}
/**
* 使用web登录,创建url地址, 需要MD5和jsonwebtoken
*/
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
const randomId = Math.random().toString(36).substring(2, 15);
const timestamp = Date.now();
const tokenSecret = 'xiao' + randomId;
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
// 10分钟过期
expiresIn: 60 * 10, // 10分钟
});
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
return { url, token, tokenSecret };
}
}

View File

@@ -30,12 +30,7 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
} }
_port = isPortAvailable; _port = isPortAvailable;
} }
const hasSocket = listenPath.includes('.sock');
if (hasSocket) {
app.listen(listenPath, () => {
console.log(`Server is running on ${listenPath}`);
});
} else {
app.listen(_port, listenPath, () => { app.listen(_port, listenPath, () => {
let showListenPath = listenPath; let showListenPath = listenPath;
if (listenPath === '::') { if (listenPath === '::') {
@@ -43,7 +38,6 @@ export const runServer = async (port: number = 51515, listenPath = '127.0.0.1')
} }
console.log(`Server is running on ${'http'}://${showListenPath}:${_port}`); console.log(`Server is running on ${'http'}://${showListenPath}:${_port}`);
}); });
}
app.server.on([{ app.server.on([{
id: 'handle-all', id: 'handle-all',
func: proxyRoute as any, func: proxyRoute as any,

View File

@@ -0,0 +1,38 @@
export const configJson = `{
"name": "assistant-app",
"version": "1.0.1",
"description": "assistant-app package pnpm, node pkgs projects",
"type": "module",
"scripts": {
"start": "pm2 start apps/code-center/dist/app.mjs --name code-center",
"proxy": "pm2 start apps/page-proxy/dist/app.mjs --name page-proxy",
"preview": "pnpm i && ASSISTANT_CONFIG_DIR=/workspace/kevisual asst server -s"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-s3": "^3.990.0",
"@kevisual/oss": "^0.0.19",
"@kevisual/query": "^0.0.40",
"eventemitter3": "^5.0.4",
"@kevisual/router": "^0.0.70",
"@kevisual/use-config": "^1.0.30",
"ioredis": "^5.9.3",
"pg": "^8.18.0",
"pm2": "^6.0.14",
"crypto-js": "^4.2.0",
"unstorage": "^1.17.4",
"dayjs": "^1.11.19",
"es-toolkit": "^1.44.0",
"node-cron": "^4.2.1",
"dotenv": "^17.3.1"
},
"devDependencies": {
"@kevisual/types": "^0.0.12",
"@types/bun": "^1.3.9",
"@types/crypto-js": "^4.2.2",
"@types/node": "^25.2.3"
}
}
`

View File

@@ -3,7 +3,7 @@ import path from 'node:path';
import { checkFileExists, AssistantConfig, AssistantConfigData, parseHomeArg, parseHelpArg } from '@/module/assistant/index.ts'; import { checkFileExists, AssistantConfig, AssistantConfigData, parseHomeArg, parseHelpArg } from '@/module/assistant/index.ts';
import { chalk } from '@/module/chalk.ts'; import { chalk } from '@/module/chalk.ts';
import { Query } from '@kevisual/query/query'; import { Query } from '@kevisual/query/query';
import { installDeps } from '@/module/npm-install.ts' import { checkNpmConfg } from './update-pkgs.ts';
export { parseHomeArg, parseHelpArg }; export { parseHomeArg, parseHelpArg };
export type AssistantInitOptions = { export type AssistantInitOptions = {
path?: string; path?: string;
@@ -118,56 +118,7 @@ export class AssistantInit extends AssistantConfig {
console.log(chalk.green('助手 pnpm-workspace.yaml 文件创建成功')); console.log(chalk.green('助手 pnpm-workspace.yaml 文件创建成功'));
} }
const packagePath = path.join(this.configDir, 'assistant-app', 'package.json'); const packagePath = path.join(this.configDir, 'assistant-app', 'package.json');
if (!checkFileExists(packagePath, true)) { checkNpmConfg(packagePath);
create = true;
fs.writeFileSync(
packagePath,
`{
"name": "assistant-app",
"version": "1.0.1",
"description": "assistant-app package pnpm, node pkgs projects",
"type": "module",
"scripts": {
"start": "pm2 start apps/code-center/dist/app.mjs --name code-center",
"proxy": "pm2 start apps/page-proxy/dist/app.mjs --name page-proxy",
"preview": "pnpm i && ASSISTANT_CONFIG_DIR=/workspace/kevisual asst server -s"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-s3": "^3.990.0",
"@kevisual/oss": "^0.0.19",
"@kevisual/query": "^0.0.40",
"eventemitter3": "^5.0.4",
"@kevisual/router": "^0.0.70",
"@kevisual/use-config": "^1.0.30",
"ioredis": "^5.9.3",
"pg": "^8.18.0",
"pm2": "^6.0.14",
"crypto-js": "^4.2.0",
"unstorage": "^1.17.4",
"dayjs": "^1.11.19",
"es-toolkit": "^1.44.0",
"node-cron": "^4.2.1",
"dotenv": "^17.3.1"
},
"devDependencies": {
"@kevisual/types": "^0.0.12",
"@types/bun": "^1.3.9",
"@types/crypto-js": "^4.2.2",
"@types/node": "^25.2.3"
}
}
`,
);
console.log(chalk.green('助手 package.json 文件创建成功, 正在安装依赖...'));
installDeps({ appPath: path.dirname(packagePath), isProduction: true }).then(() => {
console.log('------------------------------------------------');
console.log(chalk.green('助手依赖安装完成'));
console.log('------------------------------------------------');
});
}
return { return {
create, create,
}; };
@@ -205,21 +156,4 @@ export class AssistantInit extends AssistantConfig {
console.log(chalk.green('.gitignore 文件更新成功')); console.log(chalk.green('.gitignore 文件更新成功'));
} }
} }
protected getDefaultInitAssistantConfig() {
const id = randomId();
return {
app: {
url: 'https://kevisual.cn',
id,
},
description: '助手配置文件',
docs: "https://kevisual.cn/root/cli/docs/",
home: '/root/home',
proxy: [],
share: {
enabled: false,
url: 'https://kevisual.cn/ws/proxy',
},
} as AssistantConfigData;
}
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "assistant-app", "name": "assistant-app",
"version": "1.0.1", "version": "1.0.3",
"description": "assistant-app package pnpm, node pkgs projects", "description": "assistant-app package pnpm, node pkgs projects",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -12,26 +12,25 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.990.0", "@aws-sdk/client-s3": "^3.994.0",
"@kevisual/oss": "^0.0.19", "@kevisual/oss": "^0.0.19",
"@kevisual/query": "^0.0.40", "@kevisual/query": "^0.0.49",
"eventemitter3": "^5.0.4", "@kevisual/router": "^0.0.83",
"@kevisual/router": "^0.0.70",
"@kevisual/use-config": "^1.0.30", "@kevisual/use-config": "^1.0.30",
"dayjs": "^1.11.19",
"dotenv": "^17.3.1",
"es-toolkit": "^1.44.0",
"eventemitter3": "^5.0.4",
"ioredis": "^5.9.3", "ioredis": "^5.9.3",
"node-cron": "^4.2.1",
"pg": "^8.18.0", "pg": "^8.18.0",
"pm2": "^6.0.14", "pm2": "^6.0.14",
"crypto-js": "^4.2.0", "unstorage": "^1.17.4"
"unstorage": "^1.17.4",
"dayjs": "^1.11.19",
"es-toolkit": "^1.44.0",
"node-cron": "^4.2.1",
"dotenv": "^17.3.1"
}, },
"devDependencies": { "devDependencies": {
"@kevisual/types": "^0.0.12", "@kevisual/types": "^0.0.12",
"@types/bun": "^1.3.9", "@types/bun": "^1.3.9",
"@types/crypto-js": "^4.2.2", "@types/node": "^25.3.0",
"@types/node": "^25.2.3" "semver": "^7.7.4"
} }
} }

View File

@@ -0,0 +1,111 @@
import { configJson } from './common.ts';
import { checkFileExists } from '@/module/assistant/index.ts';
import { chalk } from '@/module/chalk.ts';
import { installDeps } from '@/module/npm-install.ts'
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
import semver from 'semver';
export const checkNpmConfg = (packagePath: string) => {
let create = false;
if (!checkFileExists(packagePath, true)) {
create = true;
fs.writeFileSync(packagePath, configJson);
console.log(chalk.green('助手 package.json 文件创建成功, 正在安装依赖...'));
installDeps({ appPath: path.dirname(packagePath), isProduction: true }).then(() => {
console.log('------------------------------------------------');
console.log(chalk.green('助手依赖安装完成'));
console.log('------------------------------------------------');
});
} else {
checkNpmFileConfig(packagePath);
}
return {
create,
}
}
const checkNpmFileConfig = async (packagePath: string) => {
const existingConfig = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
const defaultConfig = JSON.parse(configJson);
const appPath = path.dirname(packagePath);
let needUpdate = false;
if (defaultConfig.version !== existingConfig.version) {
existingConfig.version = defaultConfig.version;
existingConfig.scripts = {
...existingConfig.scripts,
...defaultConfig.scripts,
};
needUpdate = true;
fs.writeFileSync(packagePath, JSON.stringify(existingConfig, null, 2));
console.log(chalk.yellow('助手 package.json 文件版本不匹配,正在更新...'));
}
// 收集需要更新的依赖列表
const depsToUpdate: string[] = [];
const devDepsToUpdate: string[] = [];
// 收集需要添加的依赖(不存在的)
const depsToAdd: string[] = [];
const devDepsToAdd: string[] = [];
// 检查 dependencies - 不存在或版本小于 defaultConfig
for (const dep in defaultConfig.dependencies) {
const defaultVersion = defaultConfig.dependencies[dep].replace(/^[~^]/, '');
const existingVersion = existingConfig.dependencies?.[dep]?.replace(/^[~^]/, '');
// console.log(chalk.blue(`检查 dependency: ${dep}, defaultVersion: ${defaultVersion}, existingVersion: ${existingVersion}`));
if (!existingVersion) {
// 依赖不存在,先添加到 package.json
existingConfig.dependencies = existingConfig.dependencies || {};
existingConfig.dependencies[dep] = defaultConfig.dependencies[dep];
depsToAdd.push(dep);
} else if (semver.lt(existingVersion, defaultVersion)) {
depsToUpdate.push(dep);
}
}
// 检查 devDependencies - 不存在或版本小于 defaultConfig
for (const devDep in defaultConfig.devDependencies) {
const defaultVersion = defaultConfig.devDependencies[devDep].replace(/^[~^]/, '');
const existingVersion = existingConfig.devDependencies?.[devDep]?.replace(/^[~^]/, '');
// console.log(chalk.blue(`检查 devDependency: ${devDep}, defaultVersion: ${defaultVersion}, existingVersion: ${existingVersion}`));
if (!existingVersion) {
// 依赖不存在,先添加到 package.json
existingConfig.devDependencies = existingConfig.devDependencies || {};
existingConfig.devDependencies[devDep] = defaultConfig.devDependencies[devDep];
devDepsToAdd.push(devDep);
} else if (semver.lt(existingVersion, defaultVersion)) {
devDepsToUpdate.push(devDep);
}
}
// 如果有新增的依赖,先写入 package.json
if (depsToAdd.length > 0 || devDepsToAdd.length > 0) {
fs.writeFileSync(packagePath, JSON.stringify(existingConfig, null, 2));
console.log(chalk.yellow(`新增的 dependencies: ${depsToAdd.join(', ') || '无'}`));
console.log(chalk.yellow(`新增的 devDependencies: ${devDepsToAdd.join(', ') || '无'}`));
}
if (depsToUpdate.length > 0 || devDepsToUpdate.length > 0 || needUpdate) {
console.log(chalk.yellow(`需要更新的 dependencies: ${depsToUpdate.join(', ') || '无'}`));
console.log(chalk.yellow(`需要更新的 devDependencies: ${devDepsToUpdate.join(', ') || '无'}`));
console.log(chalk.green('正在执行 ncu -u 更新依赖版本...'));
// 执行 ncu -u 更新 package.json
try {
execSync('ncu -u', { cwd: appPath, stdio: 'inherit' });
console.log(chalk.green('ncu -u 执行完成'));
} catch (error) {
console.error(chalk.red('ncu -u 执行失败:', error));
}
console.log(chalk.green('正在安装依赖...'));
installDeps({ appPath, isProduction: true }).then(() => {
console.log('------------------------------------------------');
console.log(chalk.green('助手依赖安装完成'));
console.log('------------------------------------------------');
});
} else {
console.log(chalk.green('助手 package.json 文件已存在且依赖完整,无需更新'));
}
}

View File

@@ -46,7 +46,7 @@
"@kevisual/auth": "^2.0.3", "@kevisual/auth": "^2.0.3",
"@kevisual/context": "^0.0.8", "@kevisual/context": "^0.0.8",
"@kevisual/use-config": "^1.0.30", "@kevisual/use-config": "^1.0.30",
"@opencode-ai/sdk": "^1.2.6", "@opencode-ai/sdk": "^1.2.10",
"@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",
@@ -59,8 +59,8 @@
"unstorage": "^1.17.4" "unstorage": "^1.17.4"
}, },
"devDependencies": { "devDependencies": {
"@kevisual/api": "^0.0.52", "@kevisual/api": "^0.0.55",
"@kevisual/cnb": "^0.0.26", "@kevisual/cnb": "^0.0.28",
"@kevisual/dts": "^0.0.4", "@kevisual/dts": "^0.0.4",
"@kevisual/load": "^0.0.6", "@kevisual/load": "^0.0.6",
"@kevisual/logger": "^0.0.4", "@kevisual/logger": "^0.0.4",
@@ -69,7 +69,7 @@
"@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.2.3", "@types/node": "^25.3.0",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"commander": "^14.0.3", "commander": "^14.0.3",

751
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
import { Query } from '@kevisual/query/query'; import { Query } from '@kevisual/query/query';
import { getConfig, getEnvToken } from './get-config.ts'; import { getConfig, getEnvToken } from './get-config.ts';
import { QueryLoginNode, storage } from '@kevisual/api/login-node'; import { QueryLoginNode, StorageNode } from '@kevisual/api/login-node';
const config = getConfig(); const config = getConfig();
export const baseURL = config?.baseURL || 'https://kevisual.cn'; export const baseURL = config?.baseURL || 'https://kevisual.cn';
export { storage }; export const storage = new StorageNode({ load: true });
export const getBaseURL = () => { export const getBaseURL = () => {
return baseURL; return baseURL;
}; };