const express = require('express'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const { spawn } = require('child_process'); const { v4: uuidv4 } = require('uuid'); const app = express(); app.use(cors()); app.use(express.json()); // Security Check Middleware app.use((req, res, next) => { const clientToken = req.headers['x-custom-app-token']; const serverToken = process.env.MY_APP_TOKEN || 'ayon_custom_token_1234'; if (clientToken !== serverToken) { return res.status(403).json({ success: false, error: "Access Denied!" }); } next(); }); // ১. আসল স্কিল ফোল্ডারটা খুঁজে বের করার ফাংশন const findTargetFolder = (dir) => { if (!fs.existsSync(dir)) return null; const list = fs.readdirSync(dir); for (let file of list) { const fullPath = path.join(dir, file); const stat = fs.statSync(fullPath); if (stat && stat.isDirectory()) { const ignoreDirs = ['node_modules', '.npm', '.cache']; if (!ignoreDirs.includes(file)) { const found = findTargetFolder(fullPath); if (found) return found; } } else if (file.toLowerCase() === 'skill.md') { return dir; } } return null; }; // ২. পুরো ফোল্ডার স্ক্যান করে JSON Tree বানানোর ফাংশন (মেইন ফোল্ডারের নামসহ) const buildFolderTree = (currentDir, rootDir) => { let folderData = []; if (!fs.existsSync(currentDir)) return folderData; // অয়নের লজিক: মেইন ফোল্ডারের নামটা বের করে নেওয়া (যেমন: stitch-design) const rootFolderName = path.basename(rootDir); const list = fs.readdirSync(currentDir); list.forEach(file => { const fullPath = path.join(currentDir, file); const stat = fs.statSync(fullPath); if (stat && stat.isDirectory()) { const subFolderData = buildFolderTree(fullPath, rootDir); folderData = folderData.concat(subFolderData); } else { const fileName = file.toLowerCase(); if (fileName.endsWith('.md') && fileName !== 'readme.md') { const content = fs.readFileSync(fullPath, 'utf-8'); // রিলেটিভ পাথ বের করে তার আগে মেইন ফোল্ডারের নাম জোড়া দেওয়া হচ্ছে const relativePath = path.relative(rootDir, fullPath); // উইন্ডোজ এবং লিনাক্স সব জায়গায় ঠিকঠাক কাজ করার জন্য স্লাশ (/) ঠিক করা হচ্ছে const finalPath = rootFolderName + '/' + relativePath.replace(/\\/g, '/'); folderData.push({ name: file, path: finalPath, // এখন পাথ হবে: "stitch-design/SKILL.md" content: content }); } } }); return folderData; }; app.post('/run-skill', (req, res) => { let command = req.body.command || ''; command = command.replace(/^npx\s+/ig, '').trim(); command = 'npx ' + command; const sessionID = req.headers['x-session-id'] || uuidv4(); const workDir = path.join(__dirname, 'tmp', sessionID); console.log(`\n========================================`); console.log(`🟢 [API Request] Session: ${sessionID}`); console.log(`💻 [Command] ${command}`); fs.mkdirSync(workDir, { recursive: true }); let isFinished = false; const childProcess = spawn(command, { cwd: workDir, shell: true, env: { ...process.env, HOME: workDir, USERPROFILE: workDir } }); let rawOutput = ""; childProcess.stdout.on('data', (data) => rawOutput += data.toString()); childProcess.stderr.on('data', (data) => rawOutput += data.toString()); const timeoutId = setTimeout(() => { if (!isFinished) { console.log(`⏱️ [Timeout] Killing process...`); childProcess.kill('SIGKILL'); } }, 120000); childProcess.on('close', (code) => { isFinished = true; clearTimeout(timeoutId); if (code === 0) { const targetFolder = findTargetFolder(workDir); if (targetFolder) { console.log(`🎯 [Target Locked] Found Folder: ${targetFolder}`); try { const folderTree = buildFolderTree(targetFolder, targetFolder); console.log(`🚀 [Sending Data] Sending JSON with ${folderTree.length} files...`); res.json({ success: true, type: "folder_tree", total_files: folderTree.length, files: folderTree, session_id: sessionID }); } catch (err) { console.log(`❌ [Read Error] ${err.message}`); res.status(500).json({ success: false, error: "Failed to read folder contents." }); } } else { console.log(`⚠️ [Warning] Valid skill folder not found.`); res.status(404).json({ success: false, error: "Skill Folder not found." }); } } else { console.log(`❌ [Failed] Process ended with code: ${code}`); res.status(500).json({ success: false, error: "Execution failed.", logs: rawOutput }); } if (fs.existsSync(workDir)) { fs.rmSync(workDir, { recursive: true, force: true }); console.log(`🧹 [Cleanup] Cache deleted.`); } console.log(`========================================\n`); }); childProcess.on('error', (err) => { isFinished = true; clearTimeout(timeoutId); res.status(500).json({ success: false, error: err.message }); if (fs.existsSync(workDir)) fs.rmSync(workDir, { recursive: true, force: true }); }); }); const PORT = process.env.PORT || 7860; app.listen(PORT, () => { console.log(`🚀 Modern API Server running on port ${PORT}`); });