UNPKG

sce-tools-mcp

Version:

SCE Tools MCP Server with full Python CLI feature parity - Model Context Protocol server for SCE (Spark Creative Editor) game development

238 lines 8.73 kB
/** * SCE OpenHands Kit 初始化服务 * 负责自动下载、解压和设置 sce-openhands-kit */ import axios from 'axios'; import { copyFileSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, rmSync } from 'fs'; import { homedir } from 'os'; import { dirname, join } from 'path'; import { pipeline } from 'stream/promises'; import * as unzipper from 'unzipper'; // sce-openhands-kit 的下载信息 const SCE_KIT_CONFIG = { downloadUrl: 'https://tapcode-sce.spark.xd.com/sce-openhands-kit/release.zip', version: 'v0.0.1', extractedFolderName: '' // 如果压缩包没有顶层目录,使用空字符串 }; export class KitInitializer { constructor(kitPath) { this.kitPath = kitPath; } /** * 检查 sce-openhands-kit 是否已存在 */ isKitAvailable() { // 检查关键文件是否存在 const templatePath = join(this.kitPath, 'sce-template-for-ai'); const wasiCorePath = join(this.kitPath, 'WasiCoreSDK'); const docsPath = join(this.kitPath, 'docs'); return existsSync(templatePath) && existsSync(wasiCorePath) && existsSync(docsPath); } /** * 初始化 sce-openhands-kit * 如果不存在则自动下载和解压 */ async initialize() { if (this.isKitAvailable()) { console.error(`✅ SCE OpenHands Kit already available at: ${this.kitPath}`); return; } console.error('🚀 Initializing SCE OpenHands Kit...'); console.error(`📦 Kit will be installed to: ${this.kitPath}`); try { // 创建目标目录 await this.ensureDirectory(dirname(this.kitPath)); // 下载 ZIP 文件 console.error('📥 Downloading SCE OpenHands Kit...'); const zipPath = await this.downloadKit(); // 解压文件 console.error('📂 Extracting SCE OpenHands Kit...'); await this.extractKit(zipPath); // 清理临时文件 await this.cleanup(zipPath); // 验证安装 if (this.isKitAvailable()) { console.error('🎉 SCE OpenHands Kit initialized successfully!'); this.setEnvironmentVariables(); } else { throw new Error('Kit initialization failed: Required components not found after extraction'); } } catch (error) { console.error(`❌ Failed to initialize SCE OpenHands Kit: ${error}`); throw error; } } /** * 下载 sce-openhands-kit ZIP 文件 */ async downloadKit() { const tempDir = join(homedir(), '.sce', 'temp'); await this.ensureDirectory(tempDir); const zipPath = join(tempDir, `sce-openhands-kit-${SCE_KIT_CONFIG.version}.zip`); try { const response = await axios({ method: 'GET', url: SCE_KIT_CONFIG.downloadUrl, responseType: 'stream', timeout: 300000, // 5 分钟超时 proxy: false, headers: { 'User-Agent': 'SCE-MCP-Server/1.0.0' } }); const writer = createWriteStream(zipPath); // 显示下载进度 if (response.headers['content-length']) { const totalSize = parseInt(response.headers['content-length'], 10); let downloadedSize = 0; response.data.on('data', (chunk) => { downloadedSize += chunk.length; const progress = ((downloadedSize / totalSize) * 100).toFixed(1); process.stderr.write(`\r📥 Downloading: ${progress}% (${this.formatBytes(downloadedSize)}/${this.formatBytes(totalSize)})`); }); } await pipeline(response.data, writer); console.error('\n✅ Download completed'); return zipPath; } catch (error) { throw new Error(`Download failed: ${error}`); } } /** * 解压 ZIP 文件到目标目录 */ async extractKit(zipPath) { try { const tempExtractPath = join(dirname(zipPath), 'extract'); await this.ensureDirectory(tempExtractPath); // 解压 ZIP 文件 await new Promise((resolve, reject) => { createReadStream(zipPath) .pipe(unzipper.Extract({ path: tempExtractPath })) .on('close', () => { console.error('✅ Extraction completed'); resolve(); }) .on('error', (error) => { console.error(`❌ Extraction error: ${error}`); reject(error); }); }); // 移动解压后的内容到最终位置 const extractedPath = join(tempExtractPath, SCE_KIT_CONFIG.extractedFolderName); if (!existsSync(extractedPath)) { throw new Error(`Extracted folder not found: ${extractedPath}`); } // 确保目标目录存在 await this.ensureDirectory(this.kitPath); // 移动文件(递归复制) await this.moveDirectory(extractedPath, this.kitPath); // 清理临时解压目录 await this.removeDirectory(tempExtractPath); } catch (error) { throw new Error(`Extraction failed: ${error}`); } } /** * 确保目录存在 */ async ensureDirectory(dirPath) { if (!existsSync(dirPath)) { mkdirSync(dirPath, { recursive: true }); } } /** * 递归复制目录内容到目标目录 */ async moveDirectory(src, dest) { try { // 确保目标目录存在 await this.ensureDirectory(dest); // 递归复制源目录的所有内容到目标目录 await this.copyDirectoryContents(src, dest); // 删除源目录 rmSync(src, { recursive: true, force: true }); } catch (error) { throw new Error(`Failed to move directory contents: ${error}`); } } /** * 递归复制目录内容 */ async copyDirectoryContents(src, dest) { const entries = readdirSync(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = join(src, entry.name); const destPath = join(dest, entry.name); if (entry.isDirectory()) { // 创建目标目录 mkdirSync(destPath, { recursive: true }); // 递归复制子目录 await this.copyDirectoryContents(srcPath, destPath); } else { // 复制文件 copyFileSync(srcPath, destPath); } } } /** * 删除目录 */ async removeDirectory(dirPath) { try { if (existsSync(dirPath)) { rmSync(dirPath, { recursive: true, force: true }); } } catch (error) { // 忽略删除临时目录的错误 console.error(`⚠️ Warning: Failed to clean up temporary directory: ${dirPath}`); } } /** * 清理临时文件 */ async cleanup(zipPath) { try { if (existsSync(zipPath)) { rmSync(zipPath, { force: true }); } } catch (error) { console.error(`⚠️ Warning: Failed to clean up downloaded file: ${zipPath}`); } } /** * 设置环境变量 */ setEnvironmentVariables() { // 设置运行时环境变量 process.env.SCE_OPENHANDS_KIT_PATH = this.kitPath; console.error(`🔧 Environment variable set: SCE_OPENHANDS_KIT_PATH=${this.kitPath}`); console.error('💡 To make this persistent, add the following to your shell profile:'); if (process.platform === 'win32') { console.error(` setx SCE_OPENHANDS_KIT_PATH "${this.kitPath}"`); } else { console.error(` export SCE_OPENHANDS_KIT_PATH="${this.kitPath}"`); } } /** * 格式化字节数 */ formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } } //# sourceMappingURL=kit-initializer.js.map