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
JavaScript
/**
* 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