From 6aeff117dc9e2ff889fe0eb4ab9fc9168b7dfee5 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Mon, 9 Mar 2026 01:24:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20@kevisual/router?= =?UTF-8?q?=20=E7=89=88=E6=9C=AC=E8=87=B3=200.0.89=EF=BC=9B=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20download=20=E5=91=BD=E4=BB=A4=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E6=96=87=E4=BB=B6=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assistant/package.json | 2 +- assistant/src/main.ts | 5 +- assistant/src/router.ts | 3 + assistant/src/server.ts | 2 + package.json | 1 + pnpm-lock.yaml | 14 ++++- src/command/download.ts | 123 ++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + tsconfig.json | 2 +- 9 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 assistant/src/router.ts create mode 100644 src/command/download.ts diff --git a/assistant/package.json b/assistant/package.json index cd68a02..2b74403 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -49,7 +49,7 @@ "@kevisual/local-app-manager": "^0.1.32", "@kevisual/logger": "^0.0.4", "@kevisual/query": "0.0.53", - "@kevisual/router": "^0.0.88", + "@kevisual/router": "^0.0.89", "@kevisual/types": "^0.0.12", "@kevisual/use-config": "^1.0.30", "@opencode-ai/plugin": "^1.2.21", diff --git a/assistant/src/main.ts b/assistant/src/main.ts index c0ba715..86bc7da 100644 --- a/assistant/src/main.ts +++ b/assistant/src/main.ts @@ -5,5 +5,6 @@ import './routes/index.ts'; import './routes-simple/index.ts'; export const AgentPlugin: Plugin = createRouterAgentPluginFn({ - router: app, -}) \ No newline at end of file + router: app as any, +}) +export { app } \ No newline at end of file diff --git a/assistant/src/router.ts b/assistant/src/router.ts new file mode 100644 index 0000000..b773a99 --- /dev/null +++ b/assistant/src/router.ts @@ -0,0 +1,3 @@ +import { parse } from '@kevisual/router/commander'; +import { app, program } from './server.ts'; +parse({ app: app as any, program: program as any }) \ No newline at end of file diff --git a/assistant/src/server.ts b/assistant/src/server.ts index 4c3228a..f35ed74 100644 --- a/assistant/src/server.ts +++ b/assistant/src/server.ts @@ -164,3 +164,5 @@ export const runParser = async (argv: string[]) => { console.error('执行错误:', error.message); } }; + +export { app, program }; diff --git a/package.json b/package.json index 87028c0..3e39f05 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@kevisual/app": "^0.0.2", "@kevisual/auth": "^2.0.3", "@kevisual/context": "^0.0.8", + "@kevisual/router": "^0.0.89", "@kevisual/use-config": "^1.0.30", "@opencode-ai/sdk": "^1.2.21", "@types/busboy": "^1.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55aed04..926eada 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@kevisual/context': specifier: ^0.0.8 version: 0.0.8 + '@kevisual/router': + specifier: ^0.0.89 + version: 0.0.89 '@kevisual/use-config': specifier: ^1.0.30 version: 1.0.30(dotenv@17.3.1) @@ -188,8 +191,8 @@ importers: specifier: 0.0.53 version: 0.0.53 '@kevisual/router': - specifier: ^0.0.88 - version: 0.0.88 + specifier: ^0.0.89 + version: 0.0.89 '@kevisual/types': specifier: ^0.0.12 version: 0.0.12 @@ -1365,6 +1368,9 @@ packages: '@kevisual/router@0.0.88': resolution: {integrity: sha512-T8kEbxyTGxZpbxAKDplDjZMIY+HCnXOeEdjwQ11AQetrLuqLFDZS5PnaWdVAHnONUDLhYoftkNj7bGWLtyQDlg==} + '@kevisual/router@0.0.89': + resolution: {integrity: sha512-Sa8Fk3CeZzeZ78CUP9EDZnylkaKp9ibC1AzrF6Mivh5ifHJfyS5TZBw4NpNE11qoJN84MN/irX+poFU662V08A==} + '@kevisual/types@0.0.12': resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==} @@ -6759,6 +6765,10 @@ snapshots: dependencies: es-toolkit: 1.45.1 + '@kevisual/router@0.0.89': + dependencies: + es-toolkit: 1.45.1 + '@kevisual/types@0.0.12': {} '@kevisual/use-config@1.0.30(dotenv@17.2.3)': diff --git a/src/command/download.ts b/src/command/download.ts new file mode 100644 index 0000000..67a2455 --- /dev/null +++ b/src/command/download.ts @@ -0,0 +1,123 @@ +/** + * download 命令模块 + * 用于从 Kevisual 平台下载项目文件 + */ +import path from 'path'; +import fs from 'fs'; +import { queryLogin } from '@/module/query.ts'; +import { program, Command } from '@/program.ts'; +import { fetchLink } from '@/module/download/install.ts'; +import { chalk } from '@/module/chalk.ts'; + +/** + * 文件项类型定义 + * 代表从 Kevisual 平台获取的文件元信息 + */ +export type FileItem = { + /** 文件名称,包含完整路径 */ + name: string; + /** 文件大小(字节) */ + size: number; + /** 最后修改时间 */ + lastModified: string; + /** 文件 ETag 值,用于缓存验证 */ + etag: string; + /** 相对路径 */ + path: string; + /** 完整路径名 */ + pathname: string; + /** 文件下载 URL */ + url: string; +}; + +const downloadCmd = new Command('download') + .description('下载项目') + .option('-l, --link ', '下载链接') + .option('-d, --directory ', '下载目录', process.cwd()) + .action(async (opts) => { + let link = opts.link || ''; + if (!link) { + console.log('请提供下载链接'); + return; + } + let url = new URL(link); + if (!url.pathname.endsWith('/')) { + url.pathname += '/'; + } + url.searchParams.set('recursive', 'true'); + const directory = opts.directory || process.cwd(); + const token = await queryLogin.getToken(); + + const res = await queryLogin.query.fetchText({ + url: url.toString(), + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (res.code === 200 && res.data) { + const files = res.data as FileItem[]; + console.log(`获取到 ${files.length} 个文件`); + await downloadFiles(files, { directory }); + } else { + console.log(chalk.red('获取文件列表失败:'), res.message || '未知错误'); + } + }); + +program.addCommand(downloadCmd); + +/** + * 下载文件列表 + * @param files 文件列表 + * @param opts 下载选项 + * @param opts.directory 下载目录,默认为当前目录 + */ +export const downloadFiles = async (files: FileItem[], opts?: { directory?: string }) => { + const directory = opts?.directory || process.cwd(); + let successCount = 0; + let failCount = 0; + + for (const file of files) { + try { + const downloadPath = path.join(directory, file.path); + const dir = path.dirname(downloadPath); + + // 创建目录 + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + // 下载文件 + console.log(`下载中: ${file.name}`); + const { blob, type } = await fetchLink(file.url); + + // 检查是否为错误响应 + if (type.includes('text/html')) { + const text = await blob.text(); + if (text === 'fetchRes is error') { + console.log(chalk.red('下载失败:'), file.name); + failCount++; + continue; + } + } + + // 写入文件 + fs.writeFileSync(downloadPath, Buffer.from(await blob.arrayBuffer())); + successCount++; + console.log(chalk.green('下载成功:'), file.name); + } catch (error) { + failCount++; + console.log(chalk.red('下载失败:'), file.name, error); + } + } + + console.log(chalk.blue('下载完成')); + console.log(chalk.green(`成功: ${successCount}`)); + console.log(chalk.red(`失败: ${failCount}`)); + + return { + successCount, + failCount, + }; +}; diff --git a/src/index.ts b/src/index.ts index 2d2d9b3..8963af8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,7 @@ import './command/docker.ts'; import './command/jwks.ts'; import './command/cnb/index.ts'; +import './command/download.ts'; // program.parse(process.argv); diff --git a/tsconfig.json b/tsconfig.json index f3f7926..141c811 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,7 @@ } }, "include": [ - "src/**/*.ts", + "src/**/*.ts" ], "exclude": [], } \ No newline at end of file