From 4aeb3637bf857acce969bb19f85f3885306a0cc2 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Wed, 10 Dec 2025 17:45:09 +0800 Subject: [PATCH] feat: enhance AI commands and logging system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- assistant/package.json | 4 +- package.json | 2 +- pnpm-lock.yaml | 40 ++++++++++++++------ src/ai/ai.ts | 2 +- src/ai/routes/cmd-run.ts | 10 ++++- src/command/ai.ts | 65 +++++++++++++++++++------------- src/command/deploy.ts | 10 ++--- src/command/publish.ts | 17 +++++---- src/command/sync/modules/base.ts | 48 ++++++++++++++++++++--- src/command/sync/modules/type.ts | 8 ++-- src/command/sync/sync.ts | 22 ++++++++--- src/index.ts | 2 +- src/module/logger.ts | 10 +++++ 13 files changed, 167 insertions(+), 73 deletions(-) diff --git a/assistant/package.json b/assistant/package.json index 0f31478..94a412d 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -45,9 +45,9 @@ "@kevisual/load": "^0.0.6", "@kevisual/local-app-manager": "^0.1.32", "@kevisual/logger": "^0.0.4", - "@kevisual/query": "0.0.31", + "@kevisual/query": "0.0.32", "@kevisual/query-login": "0.0.7", - "@kevisual/router": "^0.0.36", + "@kevisual/router": "^0.0.37", "@kevisual/types": "^0.0.10", "@kevisual/use-config": "^1.0.21", "@types/bun": "^1.3.4", diff --git a/package.json b/package.json index 772032c..dd3d44c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@kevisual/dts": "^0.0.3", "@kevisual/load": "^0.0.6", "@kevisual/logger": "^0.0.4", - "@kevisual/query": "0.0.31", + "@kevisual/query": "0.0.32", "@kevisual/query-login": "0.0.7", "@types/bun": "^1.3.4", "@types/crypto-js": "^4.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aeae750..328013a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,11 +52,11 @@ importers: specifier: ^0.0.4 version: 0.0.4 '@kevisual/query': - specifier: 0.0.31 - version: 0.0.31 + specifier: 0.0.32 + version: 0.0.32 '@kevisual/query-login': specifier: 0.0.7 - version: 0.0.7(@kevisual/query@0.0.31) + version: 0.0.7(@kevisual/query@0.0.32) '@types/bun': specifier: ^1.3.4 version: 1.3.4 @@ -146,14 +146,14 @@ importers: specifier: ^0.0.4 version: 0.0.4 '@kevisual/query': - specifier: 0.0.31 - version: 0.0.31 + specifier: 0.0.32 + version: 0.0.32 '@kevisual/query-login': specifier: 0.0.7 - version: 0.0.7(@kevisual/query@0.0.31) + version: 0.0.7(@kevisual/query@0.0.32) '@kevisual/router': - specifier: ^0.0.36 - version: 0.0.36(supports-color@10.2.2) + specifier: ^0.0.37 + version: 0.0.37(supports-color@10.2.2) '@kevisual/types': specifier: ^0.0.10 version: 0.0.10 @@ -580,12 +580,18 @@ packages: '@kevisual/query@0.0.31': resolution: {integrity: sha512-bBdepjmMICLpcj/a9fnn82/0CGGYUZiCV+usWsJZKAwVlZcnj+WtKmbgKT09KpP6g3jjYzYOaXHiNFB8N0bQAQ==} + '@kevisual/query@0.0.32': + resolution: {integrity: sha512-9WN9cjmwSW8I5A0SqITdts9oxlLBGdPP7kJ8vwrxkaQteHS9FzxKuMBJxZzGKZdyte/zJDvdrE+lMf254BGbbg==} + '@kevisual/router@0.0.33': resolution: {integrity: sha512-9z7TkSzCIGbXn9SuHPBdZpGwHlAuwA8iN5jNAZBUvbEvBRkBxlrbdCSe9fBYiAHueLm2AceFNrW74uulOiAkqA==} '@kevisual/router@0.0.36': resolution: {integrity: sha512-o7GAb5T0WwRuHnWe3KB0/SPVaNHrnsFSNAQ9XuWokobfDP1ACFvOR9/rjbC0fbGFaeTeRKAprixxKkY1sfunBw==} + '@kevisual/router@0.0.37': + resolution: {integrity: sha512-f/siDSqO0g6cQhBrWyPIVv8WMgxjC+olRS8GNxqzkBvAj5M4x3cmfAj1bxTn7neOejTjkGd+ZeoDQbhIpFKDZQ==} + '@kevisual/types@0.0.10': resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==} @@ -2834,7 +2840,7 @@ snapshots: '@kevisual/ai': 0.0.19 '@kevisual/context': 0.0.4 '@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) mitt: 3.0.1 transitivePeerDependencies: @@ -2893,16 +2899,18 @@ snapshots: '@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: '@kevisual/cache': 0.0.3 - '@kevisual/query': 0.0.31 + '@kevisual/query': 0.0.32 dotenv: 17.2.3 '@kevisual/query@0.0.30': {} '@kevisual/query@0.0.31': {} + '@kevisual/query@0.0.32': {} + '@kevisual/router@0.0.33(supports-color@10.2.2)': dependencies: path-to-regexp: 8.3.0 @@ -2911,7 +2919,15 @@ snapshots: transitivePeerDependencies: - 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: path-to-regexp: 8.3.0 selfsigned: 5.2.0 diff --git a/src/ai/ai.ts b/src/ai/ai.ts index 3fbf539..53f902c 100644 --- a/src/ai/ai.ts +++ b/src/ai/ai.ts @@ -1,4 +1,4 @@ import { App } from '@kevisual/app/src/app.ts'; import { storage } from '../module/query.ts'; -export const app = new App({ token: storage.getItem('token') || '' }); \ No newline at end of file +export const app = new App({ token: storage.getItem('token') || '', storage }); \ No newline at end of file diff --git a/src/ai/routes/cmd-run.ts b/src/ai/routes/cmd-run.ts index c2f6d85..68f920f 100644 --- a/src/ai/routes/cmd-run.ts +++ b/src/ai/routes/cmd-run.ts @@ -1,11 +1,12 @@ import { app } from '../ai.ts'; import { execSync } from 'node:child_process' +import { logger } from '@/module/logger.ts'; const promptTemplate = `# CMD 结果判断器 分析上一条 CMD 命令的执行结果,判断是否需要执行下一条命令。 - 若结果中隐含或明确指示需继续执行 → 返回:\`{"cmd": "推断出的下一条命令", "type": "cmd"}\` -- 若无后续操作 → 返回:\`{"type": "none"}\` +- 若无后续操作,甚至上一次执行的返回为空或者成功 → 返回:\`{"type": "none"}\` 1. 仅输出合法 JSON,无任何额外文本。 2. \`cmd\` 必须从执行结果中合理推断得出,非预设或猜测。 @@ -23,8 +24,10 @@ app.router.route({ ctx.state.steps = ctx.state?.steps || []; try { + logger.info('执行命令:', cmd); result = execSync(cmd, { encoding: 'utf-8' }); ctx.state.steps.push({ cmd, result }); + logger.info(result); } catch (error: any) { result = error.message || ''; ctx.state.steps.push({ cmd, result, error: true }); @@ -33,19 +36,24 @@ app.router.route({ } return; } + await app.loadAI() const prompt = `${promptTemplate}\n上一条命令:\n${cmd}\n执行结果:\n${result}\n`; const response = await app.ai.question(prompt); const msg = app.ai.utils.extractJsonFromMarkdown(app.ai.responseText); try { + logger.debug('AI Prompt', prompt); + logger.debug('AI 分析结果:', msg); const { cmd, type } = msg; if (type === 'cmd' && cmd) { await app.router.call({ path: 'cmd-run', payload: { cmd } }, { state: ctx.state }); } else { + logger.info('无后续命令,结束执行'); ctx.state.steps.push({ type: 'none' }); } } catch (error) { result = '执行错误,无法解析返回结果为合法 JSON' + app.ai.responseText + logger.error(result); ctx.state.steps.push({ cmd, result, parseError: true }); } ctx.body = { diff --git a/src/command/ai.ts b/src/command/ai.ts index 4d715e3..6ac11ba 100644 --- a/src/command/ai.ts +++ b/src/command/ai.ts @@ -1,30 +1,41 @@ -import { App } from '@kevisual/app/src/app.ts'; -import { storage } from '../module/query.ts'; +import { program, Command } from '@/program.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 token = storage.getItem('token') || ''; - if (!token) { - console.log('Please login first.'); - return; +const runCmd = async (cmd: string) => { + const res = await app.router.call({ path: 'cmd-run', payload: { cmd } }); + const { body } = res; + const steps = body?.steps || []; + 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(); \ No newline at end of file +const aiRun = new Command('run') + .description('执行 AI 命令') + .option('-c, --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); \ No newline at end of file diff --git a/src/command/deploy.ts b/src/command/deploy.ts index a090632..de92a7c 100644 --- a/src/command/deploy.ts +++ b/src/command/deploy.ts @@ -59,7 +59,7 @@ const command = new Command('deploy') if (!key && pkgInfo?.appKey) { key = pkgInfo?.appKey || ''; } - logger.debug('start deploy'); + logger.debug('start deploy'); if (!version || !key) { const answers = await inquirer.prompt([ { @@ -106,8 +106,8 @@ const command = new Command('deploy') const filename = path.basename(directory); _relativeFiles = [filename]; } - console.log('upload Files', _relativeFiles); - console.log('upload Files Key', key, version); + logger.debug('upload Files', _relativeFiles); + logger.debug('upload Files Key', key, version); if (!yes) { // 确认是否上传 const confirm = await inquirer.prompt([ @@ -150,9 +150,7 @@ const command = new Command('deploy') } logger.debug('deploy success', res2.data); if (id && showBackend) { - console.log('\n'); - console.log(chalk.blue('服务端应用部署:\n'), 'envision pack-deploy', id); - console.log('\n'); + console.log(chalk.blue('下一个步骤服务端应用部署:\n'), 'envision pack-deploy', id); } } else { console.error('File upload failed', res?.message); diff --git a/src/command/publish.ts b/src/command/publish.ts index 2babd68..f172641 100644 --- a/src/command/publish.ts +++ b/src/command/publish.ts @@ -7,6 +7,7 @@ import { fileIsExist } from '@/uitls/file.ts'; import { chalk } from '@/module/chalk.ts'; import * as backServices from '@/query/services/index.ts'; import inquirer from 'inquirer'; +import { logger } from '@/module/logger.ts'; // 查找文件(忽略大小写) async function findFileInsensitive(targetFile: string): Promise { 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(); // 输出文件详细信息 - console.log('文件列表:'); + logger.debug('文件列表:'); 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); @@ -150,10 +151,10 @@ export const pack = async (opts: { packDist?: string, mergeDist?: boolean }) => collection.totalSize = totalSize; collection.tags = packageJson.app?.tags || packageJson.keywords || []; - console.log('\n基本信息'); - console.log(`name: ${packageJson.name}`); - console.log(`version: ${packageJson.version}`); - console.log(`total files: ${allFiles.length}`); + logger.debug('\n基本信息'); + logger.debug(`name: ${packageJson.name}`); + logger.debug(`version: ${packageJson.version}`); + logger.debug(`total files: ${allFiles.length}`); try { copyFilesToPackDist(filesToInclude, cwd, opts.packDist, mergeDist); } 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('run: ', 'envision services -s', res.data?.showAppInfo?.key); } else { - console.error('deploy-load failed', res.message); + console.error('deploy-load 失败', res.message); } return res; }; @@ -303,7 +304,7 @@ const packCommand = new Command('pack') if (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); program.parse(deployCommand); diff --git a/src/command/sync/modules/base.ts b/src/command/sync/modules/base.ts index 7a10e10..82b4745 100644 --- a/src/command/sync/modules/base.ts +++ b/src/command/sync/modules/base.ts @@ -1,6 +1,6 @@ import path from 'node:path'; 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 { getHash } from '@/uitls/hash.ts'; import glob from 'fast-glob'; @@ -32,6 +32,15 @@ export class SyncBase { this.baseURL = opts?.baseURL ?? ''; this.init(); } + get dir() { + return this.#dir; + } + get configFilename() { + return this.#filename; + } + get configPath() { + return path.join(this.#dir, this.#filename); + } async init() { try { const dir = this.#dir; @@ -120,38 +129,67 @@ export class SyncBase { return syncList; } async getCheckList() { - const checkDir = this.config?.checkDir || {}; + const checkDir = this.config?.clone || {}; const dirKeys = Object.keys(checkDir); + const registry = this.config?.registry || ''; const files = dirKeys.map((key) => { return { key, ...this.getRelativePath(key) }; }); return files .map((item) => { 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 { key: item.key, ...checkDir[item.key], + url: url, filepath: item?.absolute, auth, }; }) .filter((item) => item); } + /** + * sync 是已有的,优先级高于 fileSync + * + * @param sync + * @param fileSync + * @returns + */ getMergeSync(sync: Config['sync'] = {}, fileSync: Config['sync'] = {}) { const syncFileSyncKeys = Object.keys(fileSync); const syncKeys = Object.keys(sync); + const config = this.config!; + const registry = config?.registry; const keys = [...syncKeys, ...syncFileSyncKeys]; 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) { const value = sync[key] ?? fileSync[key]; - obj[key] = value; + obj[key] = wrapperRegistry(value); } return obj; } async getSyncDirectoryList() { const config = this.config; - const syncDirectory = config?.syncDirectory || []; + const syncDirectory = config?.syncd || []; let obj: Record = {}; const keys: string[] = []; for (let item of syncDirectory) { diff --git a/src/command/sync/modules/type.ts b/src/command/sync/modules/type.ts index 3b1c2dd..3caa634 100644 --- a/src/command/sync/modules/type.ts +++ b/src/command/sync/modules/type.ts @@ -11,7 +11,7 @@ export type SyncDirectory = { **/ ignore?: string[]; /** - * 合并路径的源地址,https://kevisual.xiongxiao.me/root/ai/kevisual + * 合并路径的源地址,https://kevisual.cn/root/ai/kevisual */ registry?: string; files?: string[]; @@ -21,13 +21,13 @@ export type SyncDirectory = { export interface Config { name?: string; // 项目名称 version?: string; // 项目版本号 - registry?: string; // 项目仓库地址 + registry?: string; // 当前模块的root metadata?: Record; // 元数据, 统一的配置 - syncDirectory?: SyncDirectory[]; + syncd?: SyncDirectory[]; sync?: { [key: string]: SyncConfig | string; }; - checkDir?: { + clone?: { [key: string]: { url: string; // 需要检查的 url replace?: Record; // 替换的路径 diff --git a/src/command/sync/sync.ts b/src/command/sync/sync.ts index e2a2162..9a6cbfd 100644 --- a/src/command/sync/sync.ts +++ b/src/command/sync/sync.ts @@ -1,10 +1,10 @@ import { program as app, Command } from '@/program.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 fs from 'node:fs'; 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 path from 'node:path'; import { fileIsExist } from '@/uitls/file.ts'; @@ -124,7 +124,9 @@ const syncList = new Command('list') syncList.forEach((item) => { if (opts.all) { 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') @@ -154,16 +156,26 @@ const syncCreateList = new Command('create') } }); -const checkDir = new Command('check') +const clone = new Command('clone') .option('-d --dir ', '配置目录') .option('-c --config ', '配置文件的名字', 'kevisual.json') + .option('-i --link ', '克隆链接, 比 kevisual.json 优先级更高') .description('检查目录') .action(async (opts) => { + const link = opts.link || ''; 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(); logger.debug(syncList); logger.info('检查目录\n'); const checkList = await sync.getCheckList(); + logger.info('检查列表', checkList); for (const item of checkList) { if (!item.auth) { continue; @@ -226,6 +238,6 @@ command.addCommand(syncUpload); command.addCommand(syncDownload); command.addCommand(syncList); command.addCommand(syncCreateList); -command.addCommand(checkDir); +command.addCommand(clone); app.addCommand(command); diff --git a/src/index.ts b/src/index.ts index 05f4a72..f558d9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ import './command/app/index.ts'; import './command/gist/index.ts'; import './command/config-remote.ts'; import './command/config-secret-remote.ts'; - +import './command/ai.ts'; // program.parse(process.argv); export const runParser = async (argv: string[]) => { diff --git a/src/module/logger.ts b/src/module/logger.ts index ea78b20..9dacf7d 100644 --- a/src/module/logger.ts +++ b/src/module/logger.ts @@ -3,3 +3,13 @@ const level = process.env.LOG_LEVEL || 'info'; export const logger = new Logger({ 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}`; +}