/** * Création d'une affiche "SOLDES -50% Chaussures" dans Penpot */ import http from 'http'; const MCP_HOST = '192.168.1.150'; const MCP_PORT = 9002; let sessionId = null; let msgId = 1; function postMCP(body) { return new Promise((resolve, reject) => { const payload = JSON.stringify(body); const headers = { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload), 'Accept': 'application/json, text/event-stream', }; if (sessionId) headers['mcp-session-id'] = sessionId; const req = http.request({ hostname: MCP_HOST, port: MCP_PORT, path: '/mcp', method: 'POST', headers }, res => { if (res.headers['mcp-session-id']) sessionId = res.headers['mcp-session-id']; let data = ''; res.setEncoding('utf8'); res.on('data', c => data += c); res.on('end', () => { const ct = res.headers['content-type'] || ''; if (ct.includes('text/event-stream')) { let result = null, error = null; for (const line of data.split('\n')) { if (line.startsWith('data:')) { const raw = line.slice(5).trim(); if (!raw || raw === '[DONE]') continue; try { const j = JSON.parse(raw); if (j.result !== undefined) result = j.result; if (j.error) error = j.error; } catch {} } } if (error) reject(new Error(JSON.stringify(error))); else resolve(result); } else { try { const j = JSON.parse(data); if (j.error) reject(new Error(JSON.stringify(j.error))); else resolve(j.result ?? j); } catch { resolve(data || null); } } }); }); req.on('error', reject); req.write(payload); req.end(); }); } async function init() { await postMCP({ jsonrpc: '2.0', id: msgId++, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'nox', version: '1.0' } } }); await postMCP({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }).catch(() => {}); console.log('✅ Session:', sessionId); } async function callTool(name, args = {}) { const result = await postMCP({ jsonrpc: '2.0', id: msgId++, method: 'tools/call', params: { name, arguments: args } }); if (!result) return null; if (result.content && result.content[0]) { const text = result.content[0].text; try { return JSON.parse(text); } catch { return text; } } return result; } // ─── MAIN ─────────────────────────────────────────────────────────────────── await init(); // 1. Profil pour récupérer defaultTeamId et defaultProjectId const profile = await callTool('get_profile', {}); const teamId = profile?.defaultTeamId; const projectId = profile?.defaultProjectId; console.log('TeamId:', teamId); console.log('ProjectId:', projectId); if (!projectId || !teamId) { console.error('❌ Pas de profil/projet', JSON.stringify(profile)); process.exit(1); } // 2. Créer le fichier console.log('\n📄 Création du fichier...'); const file = await callTool('create_file', { project_id: projectId, name: 'Affiche SOLDES -50%' }); console.log('File:', JSON.stringify(file).slice(0, 300)); const fileId = file?.id || file?.file_id; if (!fileId) { console.error('❌ Pas de fileId', file); process.exit(1); } console.log('FileId:', fileId); // 3. Récupérer la première page const pages = await callTool('list_pages', { file_id: fileId }); console.log('Pages:', JSON.stringify(pages).slice(0, 200)); let pageId = null; if (Array.isArray(pages)) pageId = pages[0]?.id; else if (pages?.pages) pageId = pages.pages[0]?.id; console.log('PageId:', pageId); // ─── DESIGN ───────────────────────────────────────────────────────────────── // Format affiche : 600 x 900px // Palette : fond rouge foncé, texte blanc/jaune, accent orange // 4. Frame principale (l'affiche) console.log('\n🎨 Création du frame principal...'); const mainFrame = await callTool('create_frame', { file_id: fileId, page_id: pageId, name: 'Affiche SOLDES', x: 50, y: 50, width: 600, height: 900, fill_color: '#C0392B', }); console.log('MainFrame:', JSON.stringify(mainFrame).slice(0, 200)); const frameId = mainFrame?.id; // 5. Bande décorative haut (jaune) await callTool('create_rectangle', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Bande haut', x: 0, y: 0, width: 600, height: 12, fill_color: '#F1C40F', }); // 6. Bande décorative bas (jaune) await callTool('create_rectangle', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Bande bas', x: 0, y: 888, width: 600, height: 12, fill_color: '#F1C40F', }); // 7. Bloc accroche en haut await callTool('create_rectangle', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Bloc top', x: 0, y: 12, width: 600, height: 80, fill_color: '#922B21', }); // 8. Texte "MEGA SOLDES" (haut) await callTool('create_text', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Label MEGA', content: 'MEGA SOLDES', x: 0, y: 25, width: 600, height: 60, font_size: 32, font_weight: '700', fill_color: '#F1C40F', align: 'center', }); // 9. Gros texte "-50%" await callTool('create_text', { file_id: fileId, page_id: pageId, parent_id: frameId, name: '-50%', content: '-50%', x: 0, y: 120, width: 600, height: 280, font_size: 200, font_weight: '900', fill_color: '#FFFFFF', align: 'center', }); // 10. Texte "SUR TOUTES NOS CHAUSSURES" await callTool('create_text', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Sous-titre', content: 'SUR TOUTES NOS CHAUSSURES', x: 0, y: 415, width: 600, height: 60, font_size: 26, font_weight: '600', fill_color: '#F1C40F', align: 'center', }); // 11. Séparateur await callTool('create_rectangle', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Separateur', x: 80, y: 490, width: 440, height: 4, fill_color: '#F1C40F', }); // 12. Bloc central jaune pour "OFFRE LIMITÉE" await callTool('create_rectangle', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Badge offre', x: 130, y: 520, width: 340, height: 70, fill_color: '#F1C40F', corner_radius: 8, }); // 13. Texte "OFFRE LIMITÉE" await callTool('create_text', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Offre limitee', content: 'OFFRE LIMITÉE', x: 130, y: 534, width: 340, height: 44, font_size: 24, font_weight: '800', fill_color: '#C0392B', align: 'center', }); // 14. Texte "du 1er au 31 janvier" await callTool('create_text', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Dates', content: 'du 1er au 31 mars 2026', x: 0, y: 620, width: 600, height: 40, font_size: 20, font_weight: '400', fill_color: '#FADBD8', align: 'center', }); // 15. Grande icône chaussure (emoji via texte) await callTool('create_text', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Icone chaussure', content: '👟', x: 0, y: 660, width: 600, height: 160, font_size: 130, align: 'center', }); // 16. Texte bas "boutique-example.fr" await callTool('create_text', { file_id: fileId, page_id: pageId, parent_id: frameId, name: 'Site web', content: 'www.votre-boutique.fr', x: 0, y: 845, width: 600, height: 35, font_size: 16, font_weight: '400', fill_color: '#F8C471', align: 'center', }); console.log('\n✅ Affiche créée dans Penpot !'); console.log(`🔗 Ouvrir : http://192.168.1.150:9001 → projet → fichier "Affiche SOLDES -50%"`);