feat: enhance AI commands and logging system

- Update @kevisual/query to 0.0.32 and @kevisual/router to 0.0.37
- Restructure AI command interface with run and deploy subcommands
- Add comprehensive logging throughout cmd-execution flow
- Improve sync module with better configuration handling
- Add clickable link functionality in logger
- Enhance error handling and debugging capabilities

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-10 17:45:09 +08:00
parent 5b83f7a6d1
commit 4aeb3637bf
13 changed files with 167 additions and 73 deletions

View File

@@ -45,9 +45,9 @@
"@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.31", "@kevisual/query": "0.0.32",
"@kevisual/query-login": "0.0.7", "@kevisual/query-login": "0.0.7",
"@kevisual/router": "^0.0.36", "@kevisual/router": "^0.0.37",
"@kevisual/types": "^0.0.10", "@kevisual/types": "^0.0.10",
"@kevisual/use-config": "^1.0.21", "@kevisual/use-config": "^1.0.21",
"@types/bun": "^1.3.4", "@types/bun": "^1.3.4",

View File

@@ -57,7 +57,7 @@
"@kevisual/dts": "^0.0.3", "@kevisual/dts": "^0.0.3",
"@kevisual/load": "^0.0.6", "@kevisual/load": "^0.0.6",
"@kevisual/logger": "^0.0.4", "@kevisual/logger": "^0.0.4",
"@kevisual/query": "0.0.31", "@kevisual/query": "0.0.32",
"@kevisual/query-login": "0.0.7", "@kevisual/query-login": "0.0.7",
"@types/bun": "^1.3.4", "@types/bun": "^1.3.4",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",

40
pnpm-lock.yaml generated
View File

@@ -52,11 +52,11 @@ importers:
specifier: ^0.0.4 specifier: ^0.0.4
version: 0.0.4 version: 0.0.4
'@kevisual/query': '@kevisual/query':
specifier: 0.0.31 specifier: 0.0.32
version: 0.0.31 version: 0.0.32
'@kevisual/query-login': '@kevisual/query-login':
specifier: 0.0.7 specifier: 0.0.7
version: 0.0.7(@kevisual/query@0.0.31) version: 0.0.7(@kevisual/query@0.0.32)
'@types/bun': '@types/bun':
specifier: ^1.3.4 specifier: ^1.3.4
version: 1.3.4 version: 1.3.4
@@ -146,14 +146,14 @@ importers:
specifier: ^0.0.4 specifier: ^0.0.4
version: 0.0.4 version: 0.0.4
'@kevisual/query': '@kevisual/query':
specifier: 0.0.31 specifier: 0.0.32
version: 0.0.31 version: 0.0.32
'@kevisual/query-login': '@kevisual/query-login':
specifier: 0.0.7 specifier: 0.0.7
version: 0.0.7(@kevisual/query@0.0.31) version: 0.0.7(@kevisual/query@0.0.32)
'@kevisual/router': '@kevisual/router':
specifier: ^0.0.36 specifier: ^0.0.37
version: 0.0.36(supports-color@10.2.2) version: 0.0.37(supports-color@10.2.2)
'@kevisual/types': '@kevisual/types':
specifier: ^0.0.10 specifier: ^0.0.10
version: 0.0.10 version: 0.0.10
@@ -580,12 +580,18 @@ packages:
'@kevisual/query@0.0.31': '@kevisual/query@0.0.31':
resolution: {integrity: sha512-bBdepjmMICLpcj/a9fnn82/0CGGYUZiCV+usWsJZKAwVlZcnj+WtKmbgKT09KpP6g3jjYzYOaXHiNFB8N0bQAQ==} resolution: {integrity: sha512-bBdepjmMICLpcj/a9fnn82/0CGGYUZiCV+usWsJZKAwVlZcnj+WtKmbgKT09KpP6g3jjYzYOaXHiNFB8N0bQAQ==}
'@kevisual/query@0.0.32':
resolution: {integrity: sha512-9WN9cjmwSW8I5A0SqITdts9oxlLBGdPP7kJ8vwrxkaQteHS9FzxKuMBJxZzGKZdyte/zJDvdrE+lMf254BGbbg==}
'@kevisual/router@0.0.33': '@kevisual/router@0.0.33':
resolution: {integrity: sha512-9z7TkSzCIGbXn9SuHPBdZpGwHlAuwA8iN5jNAZBUvbEvBRkBxlrbdCSe9fBYiAHueLm2AceFNrW74uulOiAkqA==} resolution: {integrity: sha512-9z7TkSzCIGbXn9SuHPBdZpGwHlAuwA8iN5jNAZBUvbEvBRkBxlrbdCSe9fBYiAHueLm2AceFNrW74uulOiAkqA==}
'@kevisual/router@0.0.36': '@kevisual/router@0.0.36':
resolution: {integrity: sha512-o7GAb5T0WwRuHnWe3KB0/SPVaNHrnsFSNAQ9XuWokobfDP1ACFvOR9/rjbC0fbGFaeTeRKAprixxKkY1sfunBw==} resolution: {integrity: sha512-o7GAb5T0WwRuHnWe3KB0/SPVaNHrnsFSNAQ9XuWokobfDP1ACFvOR9/rjbC0fbGFaeTeRKAprixxKkY1sfunBw==}
'@kevisual/router@0.0.37':
resolution: {integrity: sha512-f/siDSqO0g6cQhBrWyPIVv8WMgxjC+olRS8GNxqzkBvAj5M4x3cmfAj1bxTn7neOejTjkGd+ZeoDQbhIpFKDZQ==}
'@kevisual/types@0.0.10': '@kevisual/types@0.0.10':
resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==} resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==}
@@ -2834,7 +2840,7 @@ snapshots:
'@kevisual/ai': 0.0.19 '@kevisual/ai': 0.0.19
'@kevisual/context': 0.0.4 '@kevisual/context': 0.0.4
'@kevisual/query': 0.0.31 '@kevisual/query': 0.0.31
'@kevisual/router': 0.0.36(supports-color@10.2.2) '@kevisual/router': 0.0.36
'@kevisual/use-config': 1.0.21(dotenv@17.2.3) '@kevisual/use-config': 1.0.21(dotenv@17.2.3)
mitt: 3.0.1 mitt: 3.0.1
transitivePeerDependencies: transitivePeerDependencies:
@@ -2893,16 +2899,18 @@ snapshots:
'@kevisual/permission@0.0.3': {} '@kevisual/permission@0.0.3': {}
'@kevisual/query-login@0.0.7(@kevisual/query@0.0.31)': '@kevisual/query-login@0.0.7(@kevisual/query@0.0.32)':
dependencies: dependencies:
'@kevisual/cache': 0.0.3 '@kevisual/cache': 0.0.3
'@kevisual/query': 0.0.31 '@kevisual/query': 0.0.32
dotenv: 17.2.3 dotenv: 17.2.3
'@kevisual/query@0.0.30': {} '@kevisual/query@0.0.30': {}
'@kevisual/query@0.0.31': {} '@kevisual/query@0.0.31': {}
'@kevisual/query@0.0.32': {}
'@kevisual/router@0.0.33(supports-color@10.2.2)': '@kevisual/router@0.0.33(supports-color@10.2.2)':
dependencies: dependencies:
path-to-regexp: 8.3.0 path-to-regexp: 8.3.0
@@ -2911,7 +2919,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@kevisual/router@0.0.36(supports-color@10.2.2)': '@kevisual/router@0.0.36':
dependencies:
path-to-regexp: 8.3.0
selfsigned: 5.2.0
send: 1.2.0(supports-color@10.2.2)
transitivePeerDependencies:
- supports-color
'@kevisual/router@0.0.37(supports-color@10.2.2)':
dependencies: dependencies:
path-to-regexp: 8.3.0 path-to-regexp: 8.3.0
selfsigned: 5.2.0 selfsigned: 5.2.0

View File

@@ -1,4 +1,4 @@
import { App } from '@kevisual/app/src/app.ts'; import { App } from '@kevisual/app/src/app.ts';
import { storage } from '../module/query.ts'; import { storage } from '../module/query.ts';
export const app = new App({ token: storage.getItem('token') || '' }); export const app = new App({ token: storage.getItem('token') || '', storage });

View File

@@ -1,11 +1,12 @@
import { app } from '../ai.ts'; import { app } from '../ai.ts';
import { execSync } from 'node:child_process' import { execSync } from 'node:child_process'
import { logger } from '@/module/logger.ts';
const promptTemplate = `# CMD 结果判断器 const promptTemplate = `# CMD 结果判断器
分析上一条 CMD 命令的执行结果,判断是否需要执行下一条命令。 分析上一条 CMD 命令的执行结果,判断是否需要执行下一条命令。
- 若结果中隐含或明确指示需继续执行 → 返回:\`{"cmd": "推断出的下一条命令", "type": "cmd"}\` - 若结果中隐含或明确指示需继续执行 → 返回:\`{"cmd": "推断出的下一条命令", "type": "cmd"}\`
- 若无后续操作 → 返回:\`{"type": "none"}\` - 若无后续操作,甚至上一次执行的返回为空或者成功 → 返回:\`{"type": "none"}\`
1. 仅输出合法 JSON无任何额外文本。 1. 仅输出合法 JSON无任何额外文本。
2. \`cmd\` 必须从执行结果中合理推断得出,非预设或猜测。 2. \`cmd\` 必须从执行结果中合理推断得出,非预设或猜测。
@@ -23,8 +24,10 @@ app.router.route({
ctx.state.steps = ctx.state?.steps || []; ctx.state.steps = ctx.state?.steps || [];
try { try {
logger.info('执行命令:', cmd);
result = execSync(cmd, { encoding: 'utf-8' }); result = execSync(cmd, { encoding: 'utf-8' });
ctx.state.steps.push({ cmd, result }); ctx.state.steps.push({ cmd, result });
logger.info(result);
} catch (error: any) { } catch (error: any) {
result = error.message || ''; result = error.message || '';
ctx.state.steps.push({ cmd, result, error: true }); ctx.state.steps.push({ cmd, result, error: true });
@@ -33,19 +36,24 @@ app.router.route({
} }
return; return;
} }
await app.loadAI()
const prompt = `${promptTemplate}\n上一条命令:\n${cmd}\n执行结果:\n${result}\n`; const prompt = `${promptTemplate}\n上一条命令:\n${cmd}\n执行结果:\n${result}\n`;
const response = await app.ai.question(prompt); const response = await app.ai.question(prompt);
const msg = app.ai.utils.extractJsonFromMarkdown(app.ai.responseText); const msg = app.ai.utils.extractJsonFromMarkdown(app.ai.responseText);
try { try {
logger.debug('AI Prompt', prompt);
logger.debug('AI 分析结果:', msg);
const { cmd, type } = msg; const { cmd, type } = msg;
if (type === 'cmd' && cmd) { if (type === 'cmd' && cmd) {
await app.router.call({ path: 'cmd-run', payload: { cmd } }, { state: ctx.state }); await app.router.call({ path: 'cmd-run', payload: { cmd } }, { state: ctx.state });
} else { } else {
logger.info('无后续命令,结束执行');
ctx.state.steps.push({ type: 'none' }); ctx.state.steps.push({ type: 'none' });
} }
} catch (error) { } catch (error) {
result = '执行错误,无法解析返回结果为合法 JSON' + app.ai.responseText result = '执行错误,无法解析返回结果为合法 JSON' + app.ai.responseText
logger.error(result);
ctx.state.steps.push({ cmd, result, parseError: true }); ctx.state.steps.push({ cmd, result, parseError: true });
} }
ctx.body = { ctx.body = {

View File

@@ -1,30 +1,41 @@
import { App } from '@kevisual/app/src/app.ts'; import { program, Command } from '@/program.ts';
import { storage } from '../module/query.ts'; import { app } from '../ai/index.ts';
import util from 'util';
import { chalk } from '@/module/chalk.ts';
import { logger } from '@/module/logger.ts';
const aiCmd = new Command('ai')
.description('AI 相关命令')
.action(async (opts) => {
});
export const runAIApp = async () => { const runCmd = async (cmd: string) => {
const token = storage.getItem('token') || ''; const res = await app.router.call({ path: 'cmd-run', payload: { cmd } });
if (!token) { const { body } = res;
console.log('Please login first.'); const steps = body?.steps || [];
return; for (const step of steps) {
logger.debug(chalk.blue(`\n==== 步骤: ${step.cmd || '结束'} ====`));
logger.debug(step.result || 'No result');
} }
const aiApp = new App({ token })
await aiApp.loadAI();
const router= aiApp.router;
router.route({
description: '今天的天气怎么样?',
}).define(async (ctx) => {
ctx.body = '今天的天气晴朗,适合外出活动!';
}).addTo(router)
router.route({
description: '当前时间是几点?',
}).define(async (ctx) => {
ctx.body = `当前时间是:${new Date().toLocaleTimeString()}`;
}).addTo(router)
const chat = await aiApp.chat('今天的天气怎么样?');
console.log('AI Response:', aiApp.ai.responseText);
console.log('chat', chat);
} }
runAIApp(); const aiRun = new Command('run')
.description('执行 AI 命令')
.option('-c, --cmd <cmd>', '要执行的 CMD 命令')
.action(async (opts) => {
if (opts.cmd) {
await runCmd(opts.cmd);
} else {
console.log('请提供要执行的 CMD 命令');
}
});
const aiRunDeploy = new Command('deploy')
.description('部署 AI 后端应用')
.action(async (opts) => {
const cmd = 'ev pack -p -u';
const res = await runCmd(cmd);
});
aiCmd.addCommand(aiRun);
aiCmd.addCommand(aiRunDeploy);
program.addCommand(aiCmd);

View File

@@ -106,8 +106,8 @@ const command = new Command('deploy')
const filename = path.basename(directory); const filename = path.basename(directory);
_relativeFiles = [filename]; _relativeFiles = [filename];
} }
console.log('upload Files', _relativeFiles); logger.debug('upload Files', _relativeFiles);
console.log('upload Files Key', key, version); logger.debug('upload Files Key', key, version);
if (!yes) { if (!yes) {
// 确认是否上传 // 确认是否上传
const confirm = await inquirer.prompt([ const confirm = await inquirer.prompt([
@@ -150,9 +150,7 @@ const command = new Command('deploy')
} }
logger.debug('deploy success', res2.data); logger.debug('deploy success', res2.data);
if (id && showBackend) { if (id && showBackend) {
console.log('\n'); console.log(chalk.blue('下一个步骤服务端应用部署:\n'), 'envision pack-deploy', id);
console.log(chalk.blue('服务端应用部署:\n'), 'envision pack-deploy', id);
console.log('\n');
} }
} else { } else {
console.error('File upload failed', res?.message); console.error('File upload failed', res?.message);

View File

@@ -7,6 +7,7 @@ import { fileIsExist } from '@/uitls/file.ts';
import { chalk } from '@/module/chalk.ts'; import { chalk } from '@/module/chalk.ts';
import * as backServices from '@/query/services/index.ts'; import * as backServices from '@/query/services/index.ts';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import { logger } from '@/module/logger.ts';
// 查找文件(忽略大小写) // 查找文件(忽略大小写)
async function findFileInsensitive(targetFile: string): Promise<string | null> { async function findFileInsensitive(targetFile: string): Promise<string | null> {
const files = fs.readdirSync('.'); const files = fs.readdirSync('.');
@@ -139,9 +140,9 @@ export const pack = async (opts: { packDist?: string, mergeDist?: boolean }) =>
const allFiles = (await Promise.all(filesToInclude.map((file) => collectFileInfo(file)))).flat(); const allFiles = (await Promise.all(filesToInclude.map((file) => collectFileInfo(file)))).flat();
// 输出文件详细信息 // 输出文件详细信息
console.log('文件列表:'); logger.debug('文件列表:');
allFiles.forEach((file) => { allFiles.forEach((file) => {
console.log(`${file.size}B ${file.path}`); logger.debug(`${file.size}B ${file.path}`);
}); });
const totalSize = allFiles.reduce((sum, file) => sum + file.size, 0); const totalSize = allFiles.reduce((sum, file) => sum + file.size, 0);
@@ -150,10 +151,10 @@ export const pack = async (opts: { packDist?: string, mergeDist?: boolean }) =>
collection.totalSize = totalSize; collection.totalSize = totalSize;
collection.tags = packageJson.app?.tags || packageJson.keywords || []; collection.tags = packageJson.app?.tags || packageJson.keywords || [];
console.log('\n基本信息'); logger.debug('\n基本信息');
console.log(`name: ${packageJson.name}`); logger.debug(`name: ${packageJson.name}`);
console.log(`version: ${packageJson.version}`); logger.debug(`version: ${packageJson.version}`);
console.log(`total files: ${allFiles.length}`); logger.debug(`total files: ${allFiles.length}`);
try { try {
copyFilesToPackDist(filesToInclude, cwd, opts.packDist, mergeDist); copyFilesToPackDist(filesToInclude, cwd, opts.packDist, mergeDist);
} catch (error) { } catch (error) {
@@ -222,7 +223,7 @@ const deployLoadFn = async (id: string, fileKey: string, force = true, install =
console.log('deploy-load success. current version:', res.data?.pkg?.version); console.log('deploy-load success. current version:', res.data?.pkg?.version);
console.log('run: ', 'envision services -s', res.data?.showAppInfo?.key); console.log('run: ', 'envision services -s', res.data?.showAppInfo?.key);
} else { } else {
console.error('deploy-load failed', res.message); console.error('deploy-load 失败', res.message);
} }
return res; return res;
}; };
@@ -303,7 +304,7 @@ const packCommand = new Command('pack')
if (yes) { if (yes) {
deployCommand.push('-y', 'yes'); deployCommand.push('-y', 'yes');
} }
console.log(chalk.blue('deploy doing: '), deployCommand.slice(2).join(' '), '\n'); logger.debug(chalk.blue('deploy doing: '), deployCommand.slice(2).join(' '), '\n');
// console.log('pack deploy services', chalk.blue('example: '), runDeployCommand); // console.log('pack deploy services', chalk.blue('example: '), runDeployCommand);
program.parse(deployCommand); program.parse(deployCommand);

View File

@@ -1,6 +1,6 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import { Config, SyncList, SyncConfigType } from './type.ts'; import { Config, SyncList, SyncConfigType, SyncConfig } from './type.ts';
import { fileIsExist } from '@/uitls/file.ts'; import { fileIsExist } from '@/uitls/file.ts';
import { getHash } from '@/uitls/hash.ts'; import { getHash } from '@/uitls/hash.ts';
import glob from 'fast-glob'; import glob from 'fast-glob';
@@ -32,6 +32,15 @@ export class SyncBase {
this.baseURL = opts?.baseURL ?? ''; this.baseURL = opts?.baseURL ?? '';
this.init(); this.init();
} }
get dir() {
return this.#dir;
}
get configFilename() {
return this.#filename;
}
get configPath() {
return path.join(this.#dir, this.#filename);
}
async init() { async init() {
try { try {
const dir = this.#dir; const dir = this.#dir;
@@ -120,38 +129,67 @@ export class SyncBase {
return syncList; return syncList;
} }
async getCheckList() { async getCheckList() {
const checkDir = this.config?.checkDir || {}; const checkDir = this.config?.clone || {};
const dirKeys = Object.keys(checkDir); const dirKeys = Object.keys(checkDir);
const registry = this.config?.registry || '';
const files = dirKeys.map((key) => { const files = dirKeys.map((key) => {
return { key, ...this.getRelativePath(key) }; return { key, ...this.getRelativePath(key) };
}); });
return files return files
.map((item) => { .map((item) => {
if (!item) return; if (!item) return;
let auth = checkAuth(checkDir[item.key]?.url, this.baseURL); let url = checkDir[item.key]?.url || registry;
let auth = checkAuth(url, this.baseURL);
return { return {
key: item.key, key: item.key,
...checkDir[item.key], ...checkDir[item.key],
url: url,
filepath: item?.absolute, filepath: item?.absolute,
auth, auth,
}; };
}) })
.filter((item) => item); .filter((item) => item);
} }
/**
* sync 是已有的,优先级高于 fileSync
*
* @param sync
* @param fileSync
* @returns
*/
getMergeSync(sync: Config['sync'] = {}, fileSync: Config['sync'] = {}) { getMergeSync(sync: Config['sync'] = {}, fileSync: Config['sync'] = {}) {
const syncFileSyncKeys = Object.keys(fileSync); const syncFileSyncKeys = Object.keys(fileSync);
const syncKeys = Object.keys(sync); const syncKeys = Object.keys(sync);
const config = this.config!;
const registry = config?.registry;
const keys = [...syncKeys, ...syncFileSyncKeys]; const keys = [...syncKeys, ...syncFileSyncKeys];
const obj: Config['sync'] = {}; const obj: Config['sync'] = {};
const wrapperRegistry = (value: SyncConfig | string) => {
if (typeof value === 'object') {
const url = value.url;
if (registry && !url.startsWith('http')) {
return {
...value,
url: registry.replace(/\/+$/g, '') + '/' + url.replace(/^\/+/g, ''),
};
}
return value;
}
const url = value;
if (registry && !url.startsWith('http')) {
return registry.replace(/\/+$/g, '') + '/' + url.replace(/^\/+/g, '');
}
return url;
}
for (let key of keys) { for (let key of keys) {
const value = sync[key] ?? fileSync[key]; const value = sync[key] ?? fileSync[key];
obj[key] = value; obj[key] = wrapperRegistry(value);
} }
return obj; return obj;
} }
async getSyncDirectoryList() { async getSyncDirectoryList() {
const config = this.config; const config = this.config;
const syncDirectory = config?.syncDirectory || []; const syncDirectory = config?.syncd || [];
let obj: Record<string, any> = {}; let obj: Record<string, any> = {};
const keys: string[] = []; const keys: string[] = [];
for (let item of syncDirectory) { for (let item of syncDirectory) {

View File

@@ -11,7 +11,7 @@ export type SyncDirectory = {
**/ **/
ignore?: string[]; ignore?: string[];
/** /**
* 合并路径的源地址https://kevisual.xiongxiao.me/root/ai/kevisual * 合并路径的源地址https://kevisual.cn/root/ai/kevisual
*/ */
registry?: string; registry?: string;
files?: string[]; files?: string[];
@@ -21,13 +21,13 @@ export type SyncDirectory = {
export interface Config { export interface Config {
name?: string; // 项目名称 name?: string; // 项目名称
version?: string; // 项目版本号 version?: string; // 项目版本号
registry?: string; // 项目仓库地址 registry?: string; // 当前模块的root
metadata?: Record<string, any>; // 元数据, 统一的配置 metadata?: Record<string, any>; // 元数据, 统一的配置
syncDirectory?: SyncDirectory[]; syncd?: SyncDirectory[];
sync?: { sync?: {
[key: string]: SyncConfig | string; [key: string]: SyncConfig | string;
}; };
checkDir?: { clone?: {
[key: string]: { [key: string]: {
url: string; // 需要检查的 url url: string; // 需要检查的 url
replace?: Record<string, string>; // 替换的路径 replace?: Record<string, string>; // 替换的路径

View File

@@ -1,10 +1,10 @@
import { program as app, Command } from '@/program.ts'; import { program as app, Command } from '@/program.ts';
import { SyncBase } from './modules/base.ts'; import { SyncBase } from './modules/base.ts';
import { baseURL, storage } from '@/module/query.ts'; import { baseURL, query, storage } from '@/module/query.ts';
import { fetchLink, fetchAiList } from '@/module/download/install.ts'; import { fetchLink, fetchAiList } from '@/module/download/install.ts';
import fs from 'node:fs'; import fs from 'node:fs';
import { upload } from '@/module/download/upload.ts'; import { upload } from '@/module/download/upload.ts';
import { logger } from '@/module/logger.ts'; import { logger, printClickableLink } from '@/module/logger.ts';
import { chalk } from '@/module/chalk.ts'; import { chalk } from '@/module/chalk.ts';
import path from 'node:path'; import path from 'node:path';
import { fileIsExist } from '@/uitls/file.ts'; import { fileIsExist } from '@/uitls/file.ts';
@@ -124,7 +124,9 @@ const syncList = new Command('list')
syncList.forEach((item) => { syncList.forEach((item) => {
if (opts.all) { if (opts.all) {
logger.info(item); logger.info(item);
} else logger.info(chalk.blue(item.key), chalk.gray(item.type), chalk.green(item.url)); } else {
logger.info(chalk.green(printClickableLink({ url: item.url, text: item.key, print: false })), chalk.gray(item.type));
}
}); });
}); });
const syncCreateList = new Command('create') const syncCreateList = new Command('create')
@@ -154,16 +156,26 @@ const syncCreateList = new Command('create')
} }
}); });
const checkDir = new Command('check') const clone = new Command('clone')
.option('-d --dir <dir>', '配置目录') .option('-d --dir <dir>', '配置目录')
.option('-c --config <config>', '配置文件的名字', 'kevisual.json') .option('-c --config <config>', '配置文件的名字', 'kevisual.json')
.option('-i --link <link>', '克隆链接, 比 kevisual.json 优先级更高')
.description('检查目录') .description('检查目录')
.action(async (opts) => { .action(async (opts) => {
const link = opts.link || '';
const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config }); const sync = new SyncBase({ dir: opts.dir, baseURL: baseURL, configFilename: opts.config });
if (link) {
const res = await query.fetchText(link);
if (res.code === 200) {
fs.writeFileSync(sync.configPath, JSON.stringify(res.data, null, 2));
}
sync.init()
}
const syncList = await sync.getSyncList(); const syncList = await sync.getSyncList();
logger.debug(syncList); logger.debug(syncList);
logger.info('检查目录\n'); logger.info('检查目录\n');
const checkList = await sync.getCheckList(); const checkList = await sync.getCheckList();
logger.info('检查列表', checkList);
for (const item of checkList) { for (const item of checkList) {
if (!item.auth) { if (!item.auth) {
continue; continue;
@@ -226,6 +238,6 @@ command.addCommand(syncUpload);
command.addCommand(syncDownload); command.addCommand(syncDownload);
command.addCommand(syncList); command.addCommand(syncList);
command.addCommand(syncCreateList); command.addCommand(syncCreateList);
command.addCommand(checkDir); command.addCommand(clone);
app.addCommand(command); app.addCommand(command);

View File

@@ -16,7 +16,7 @@ import './command/app/index.ts';
import './command/gist/index.ts'; import './command/gist/index.ts';
import './command/config-remote.ts'; import './command/config-remote.ts';
import './command/config-secret-remote.ts'; import './command/config-secret-remote.ts';
import './command/ai.ts';
// program.parse(process.argv); // program.parse(process.argv);
export const runParser = async (argv: string[]) => { export const runParser = async (argv: string[]) => {

View File

@@ -3,3 +3,13 @@ const level = process.env.LOG_LEVEL || 'info';
export const logger = new Logger({ export const logger = new Logger({
level: level as any, level: level as any,
}); });
export function printClickableLink({ url, text, print = true }: { url: string; text: string, print?: boolean }) {
const escape = '\x1B'; // ESC 字符
const linkStart = `${escape}]8;;${url}${escape}\\`;
const linkEnd = `${escape}]8;;${escape}\\`;
if (print) {
console.log(`${linkStart}${text}${linkEnd}`);
}
return `${linkStart}${text}${linkEnd}`;
}