UNPKG

hatch-slidev-builder-mcp

Version:

A comprehensive MCP server for creating Slidev presentations with component library, interactive elements, and team collaboration features

197 lines (196 loc) 7.68 kB
import * as fs from 'fs-extra'; import * as path from 'path'; import { spawn } from 'child_process'; export async function exportDeck(args) { const { deckPath, format, outputPath, options = {} } = args; try { // Verify deck exists const slidesPath = path.join(deckPath, 'slides.md'); if (!await fs.pathExists(slidesPath)) { throw new Error(`Slides file not found at ${slidesPath}`); } // Ensure output directory exists await fs.ensureDir(path.dirname(outputPath)); // Execute export based on format let exportResult; switch (format) { case 'pdf': exportResult = await exportToPDF(deckPath, outputPath, options); break; case 'html': exportResult = await exportToHTML(deckPath, outputPath, options); break; case 'spa': exportResult = await exportToSPA(deckPath, outputPath, options); break; case 'pptx': exportResult = await exportToPPTX(deckPath, outputPath, options); break; default: throw new Error(`Unsupported export format: ${format}`); } return { content: [ { type: 'text', text: `✅ Successfully exported presentation to ${format.toUpperCase()}\n\n` + `📁 Source: ${deckPath}\n` + `📄 Output: ${outputPath}\n` + `📊 Format: ${format}\n` + `⚙️ Options: ${JSON.stringify(options, null, 2)}\n\n` + `${exportResult.details || ''}` } ] }; } catch (error) { throw new Error(`Failed to export deck: ${error instanceof Error ? error.message : String(error)}`); } } async function exportToPDF(deckPath, outputPath, options) { const args = ['export', '--format', 'pdf']; if (options.withClicks) args.push('--with-clicks'); if (options.range) args.push('--range', options.range); if (options.dark) args.push('--dark'); args.push('--output', outputPath); try { await executeSlidevCommand(deckPath, args); const stats = await fs.stat(outputPath); return { details: `📊 PDF Details:\n` + `📁 File Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB\n` + `🕐 Created: ${stats.mtime.toLocaleString()}\n` + `${options.withClicks ? '🖱️ Includes click animations\n' : ''}` + `${options.range ? `📄 Range: ${options.range}\n` : ''}` + `${options.dark ? '🌙 Dark mode enabled\n' : ''}` }; } catch (error) { throw new Error(`PDF export failed: ${error}`); } } async function exportToHTML(deckPath, outputPath, options) { const args = ['export', '--format', 'html']; if (options.withClicks) args.push('--with-clicks'); if (options.range) args.push('--range', options.range); if (options.dark) args.push('--dark'); args.push('--output', outputPath); try { await executeSlidevCommand(deckPath, args); const stats = await fs.stat(outputPath); return { details: `🌐 HTML Details:\n` + `📁 File Size: ${(stats.size / 1024).toFixed(2)} KB\n` + `🕐 Created: ${stats.mtime.toLocaleString()}\n` + `📱 Self-contained HTML file\n` + `${options.withClicks ? '🖱️ Includes click animations\n' : ''}` + `${options.range ? `📄 Range: ${options.range}\n` : ''}` }; } catch (error) { throw new Error(`HTML export failed: ${error}`); } } async function exportToSPA(deckPath, outputPath, options) { const args = ['build']; if (options.range) args.push('--range', options.range); args.push('--out', outputPath); try { await executeSlidevCommand(deckPath, args); // Count files in output directory const files = await fs.readdir(outputPath, { recursive: true }); const totalSize = await calculateDirectorySize(outputPath); return { details: `📦 SPA Details:\n` + `📁 Directory: ${outputPath}\n` + `📄 Files: ${files.length}\n` + `💾 Total Size: ${(totalSize / 1024 / 1024).toFixed(2)} MB\n` + `🚀 Ready for web deployment\n` + `📱 Progressive Web App enabled\n` + `${options.range ? `📄 Range: ${options.range}\n` : ''}` }; } catch (error) { throw new Error(`SPA export failed: ${error}`); } } async function exportToPPTX(deckPath, outputPath, options) { // Note: PPTX export requires additional setup and may not be available in all Slidev versions // This is a placeholder implementation try { // First try with slidev export if it supports pptx const args = ['export', '--format', 'pptx', '--output', outputPath]; await executeSlidevCommand(deckPath, args); const stats = await fs.stat(outputPath); return { details: `📊 PowerPoint Details:\n` + `📁 File Size: ${(stats.size / 1024 / 1024).toFixed(2)} MB\n` + `🕐 Created: ${stats.mtime.toLocaleString()}\n` + `📱 Compatible with Microsoft PowerPoint\n` + `⚠️ Some interactive features may be lost` }; } catch (error) { // Fallback: Export to PDF first, then provide conversion guidance const pdfPath = outputPath.replace('.pptx', '.pdf'); await exportToPDF(deckPath, pdfPath, options); return { details: `⚠️ Direct PPTX export not available\n` + `📄 Exported to PDF instead: ${pdfPath}\n\n` + `To convert to PowerPoint:\n` + `1. Use online converters (PDF to PPTX)\n` + `2. Use Adobe Acrobat Pro\n` + `3. Import PDF into PowerPoint manually\n\n` + `Note: Interactive features will be lost in conversion.` }; } } async function executeSlidevCommand(deckPath, args) { return new Promise((resolve, reject) => { const slidev = spawn('npx', ['slidev', ...args], { cwd: deckPath, stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; slidev.stdout.on('data', (data) => { stdout += data.toString(); }); slidev.stderr.on('data', (data) => { stderr += data.toString(); }); slidev.on('close', (code) => { if (code === 0) { resolve(); } else { reject(`Slidev command failed with code ${code}: ${stderr || stdout}`); } }); slidev.on('error', (error) => { reject(`Failed to execute Slidev command: ${error.message}`); }); }); } async function calculateDirectorySize(dirPath) { let totalSize = 0; const files = await fs.readdir(dirPath, { withFileTypes: true }); for (const file of files) { const fullPath = path.join(dirPath, file.name); if (file.isDirectory()) { totalSize += await calculateDirectorySize(fullPath); } else { const stats = await fs.stat(fullPath); totalSize += stats.size; } } return totalSize; }