Spaces:
Paused
Paused
| 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}`); | |
| }); | |