fix: paramètres create_text (text/textAlign), template Penpot corrigé
This commit is contained in:
@@ -135,16 +135,25 @@
|
|||||||
|
|
||||||
**5. `list_teams` retourne du texte** ("Found 1 teams"), pas du JSON
|
**5. `list_teams` retourne du texte** ("Found 1 teams"), pas du JSON
|
||||||
|
|
||||||
**6. Workflow création fichier** :
|
**6. `create_text` — paramètres corrects** :
|
||||||
|
- `text` (PAS `content`) → le contenu du texte
|
||||||
|
- `textAlign` (PAS `align`) → "left", "center", "right", "justify"
|
||||||
|
- `fontWeight` → string "100"–"900" ou "bold"
|
||||||
|
- `fillColor` → couleur HEX
|
||||||
|
- ⚠️ `parentId` **NON SUPPORTÉ** pour create_text — les textes vont toujours au root frame
|
||||||
|
- Width/height auto-calculés (text.length × fontSize × 0.6) — ne pas les spécifier
|
||||||
|
|
||||||
|
**7. Workflow création fichier** :
|
||||||
```
|
```
|
||||||
get_profile → defaultProjectId
|
get_profile → defaultProjectId
|
||||||
create_file { projectId, name } → fileId
|
create_file { projectId, name } → fileId
|
||||||
list_pages { fileId } → pageId (array[0].id)
|
list_pages { fileId } → pageId (array[0].id)
|
||||||
create_frame { fileId, pageId, name, x, y, width, height, fillColor } → frameId
|
create_frame { fileId, pageId, name, x, y, width, height, fillColor } → frameId
|
||||||
create_rectangle/text { fileId, pageId, parentId: frameId, ... }
|
create_rectangle { fileId, pageId, parentId: frameId, ... } ← parentId OK pour rect
|
||||||
|
create_text { fileId, pageId, text, x, y, textAlign, ... } ← PAS de parentId
|
||||||
```
|
```
|
||||||
|
|
||||||
**7. Coordonnées shapes dans un frame** : absolues (pas relatives au frame)
|
**8. Coordonnées** : absolues sur la page. Les textes se placent dans le root frame automatiquement. Pour un poster : mettre frame à (0,0) et textes aux bonnes coords absolues.
|
||||||
|
|
||||||
### Pour générer une image finale
|
### Pour générer une image finale
|
||||||
- `export_shape` non dispo → utiliser **HTML/CSS → Playwright screenshot**
|
- `export_shape` non dispo → utiliser **HTML/CSS → Playwright screenshot**
|
||||||
|
|||||||
+75
-119
@@ -1,6 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Script tout-en-un : redémarre le MCP + crée l'affiche SOLDES
|
* Affiche SOLDES -50% — version corrigée
|
||||||
* Paramètres MCP = camelCase !
|
* Leçons apprises :
|
||||||
|
* - create_text : paramètre `text` (pas `content`), `textAlign` (pas `align`)
|
||||||
|
* - create_text : parentId NON supporté → textes à coords absolues
|
||||||
|
* - create_rectangle : parentId OK
|
||||||
|
* - Frame à (0,0) → coordonnées absolues = relatives au frame
|
||||||
*/
|
*/
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
@@ -17,8 +21,7 @@ function portainerReq(path, method = 'GET') {
|
|||||||
let d = ''; res.on('data', c => d += c);
|
let d = ''; res.on('data', c => d += c);
|
||||||
res.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve(res.statusCode); } });
|
res.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve(res.statusCode); } });
|
||||||
});
|
});
|
||||||
req.on('error', reject);
|
req.on('error', reject); req.end();
|
||||||
req.end();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,21 +59,14 @@ function postMCP(body) {
|
|||||||
if (line.startsWith('data:')) {
|
if (line.startsWith('data:')) {
|
||||||
const raw = line.slice(5).trim();
|
const raw = line.slice(5).trim();
|
||||||
if (!raw || raw === '[DONE]') continue;
|
if (!raw || raw === '[DONE]') continue;
|
||||||
try {
|
try { const j = JSON.parse(raw); if (j.result !== undefined) result = j.result; if (j.error) error = j.error; } catch {}
|
||||||
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)));
|
if (error) reject(new Error(JSON.stringify(error)));
|
||||||
else resolve(result);
|
else resolve(result);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try { const j = JSON.parse(data); if (j.error) reject(new Error(JSON.stringify(j.error))); else resolve(j.result ?? j); }
|
||||||
const j = JSON.parse(data);
|
catch { resolve(data || null); }
|
||||||
if (j.error) reject(new Error(JSON.stringify(j.error)));
|
|
||||||
else resolve(j.result ?? j);
|
|
||||||
} catch { resolve(data || null); }
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -81,156 +77,116 @@ function postMCP(body) {
|
|||||||
async function initMCP() {
|
async function initMCP() {
|
||||||
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', 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(() => {});
|
await postMCP({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }).catch(() => {});
|
||||||
console.log('✅ MCP Session:', sessionId);
|
console.log('✅ Session:', sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callTool(name, args = {}) {
|
async function callTool(name, args = {}) {
|
||||||
const result = await postMCP({ jsonrpc: '2.0', id: msgId++, method: 'tools/call', params: { name, arguments: args } });
|
const result = await postMCP({ jsonrpc: '2.0', id: msgId++, method: 'tools/call', params: { name, arguments: args } });
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
// Le tool retourne content[0] (summary) et content[1] (JSON)
|
|
||||||
if (result.content) {
|
if (result.content) {
|
||||||
const jsonContent = result.content.find(c => { try { JSON.parse(c.text); return true; } catch { return false; } });
|
for (const c of result.content) { try { return JSON.parse(c.text); } catch {} }
|
||||||
if (jsonContent) { try { return JSON.parse(jsonContent.text); } catch {} }
|
|
||||||
return result.content[0]?.text;
|
return result.content[0]?.text;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shape(type, args) {
|
async function rect(args) {
|
||||||
const r = await callTool(`create_${type}`, args);
|
const r = await callTool('create_rectangle', args);
|
||||||
const id = typeof r === 'object' ? r?.id : null;
|
console.log(` ▪ rect "${args.name}" ${r?.id?.slice(0,8) || JSON.stringify(r).slice(0,40)}`);
|
||||||
console.log(` ✓ ${type} "${args.name || ''}" ${id ? '→ ' + id.slice(0,8) : JSON.stringify(r).slice(0,60)}`);
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function txt(args) {
|
||||||
|
const r = await callTool('create_text', args);
|
||||||
|
console.log(` ▪ text "${args.name}" ${r?.id?.slice(0,8) || JSON.stringify(r).slice(0,40)}`);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── MAIN ────────────────────────────────────────────────────────────────────
|
// ─── MAIN ────────────────────────────────────────────────────────────────────
|
||||||
console.log('🚀 Démarrage création affiche SOLDES...');
|
console.log('🚀 Création affiche SOLDES (version corrigée)...');
|
||||||
await restartMCPContainer();
|
await restartMCPContainer();
|
||||||
await sleep(10000);
|
await sleep(10000);
|
||||||
await initMCP();
|
await initMCP();
|
||||||
|
|
||||||
// Profil
|
// Profil + IDs
|
||||||
const profile = await callTool('get_profile', {});
|
const profile = await callTool('get_profile', {});
|
||||||
const projectId = profile?.defaultProjectId;
|
const projectId = profile?.defaultProjectId;
|
||||||
console.log('ProjectId:', projectId);
|
console.log('ProjectId:', projectId);
|
||||||
|
|
||||||
// Créer le fichier (camelCase!)
|
// Supprimer l'ancien fichier si présent
|
||||||
console.log('\n📄 Création du fichier...');
|
const existingFiles = await callTool('list_files', { projectId });
|
||||||
const file = await callTool('create_file', { projectId, name: 'Affiche SOLDES -50%' });
|
if (Array.isArray(existingFiles)) {
|
||||||
console.log('File:', JSON.stringify(file).slice(0, 150));
|
for (const f of existingFiles.filter(f => f.name?.includes('SOLDES'))) {
|
||||||
|
await callTool('delete_file', { fileId: f.id });
|
||||||
|
console.log('🗑 Supprimé ancien fichier:', f.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le fichier
|
||||||
|
const file = await callTool('create_file', { projectId, name: 'Affiche SOLDES -50%' });
|
||||||
const fileId = file?.id;
|
const fileId = file?.id;
|
||||||
if (!fileId) { console.error('❌ Pas de fileId'); process.exit(1); }
|
|
||||||
console.log('FileId:', fileId);
|
console.log('FileId:', fileId);
|
||||||
|
|
||||||
// Lister les pages (camelCase)
|
const pages = await callTool('list_pages', { fileId });
|
||||||
const pagesResult = await callTool('list_pages', { fileId });
|
const pageId = Array.isArray(pages) ? pages[0]?.id : null;
|
||||||
console.log('Pages raw:', JSON.stringify(pagesResult).slice(0, 200));
|
|
||||||
let pageId = null;
|
|
||||||
if (Array.isArray(pagesResult)) pageId = pagesResult[0]?.id;
|
|
||||||
else if (pagesResult?.pages) pageId = pagesResult.pages[0]?.id;
|
|
||||||
else {
|
|
||||||
// Extraire UUID depuis texte
|
|
||||||
const uuids = JSON.stringify(pagesResult).match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g);
|
|
||||||
pageId = uuids?.find(u => u !== fileId);
|
|
||||||
}
|
|
||||||
console.log('PageId:', pageId);
|
console.log('PageId:', pageId);
|
||||||
if (!pageId) { console.error('❌ Pas de pageId'); process.exit(1); }
|
if (!pageId) { console.error('❌ Pas de pageId'); process.exit(1); }
|
||||||
|
|
||||||
// ─── DESIGN (600×900) ────────────────────────────────────────────────────────
|
// ─── DESIGN 600×900 — frame à (0,0) ─────────────────────────────────────────
|
||||||
console.log('\n🎨 Construction du design...');
|
console.log('\n🎨 Construction...');
|
||||||
|
|
||||||
// Frame principale - fond rouge
|
// Frame principale = fond rouge (à 0,0 → coords absolues = relatives)
|
||||||
const mainFrame = await shape('frame', {
|
const frame = await callTool('create_frame', {
|
||||||
fileId, pageId,
|
fileId, pageId,
|
||||||
name: 'Affiche SOLDES',
|
name: 'Affiche SOLDES', x: 0, y: 0, width: 600, height: 900,
|
||||||
x: 100, y: 100,
|
|
||||||
width: 600, height: 900,
|
|
||||||
fillColor: '#C0392B',
|
fillColor: '#C0392B',
|
||||||
});
|
});
|
||||||
const frameId = mainFrame?.id;
|
const frameId = frame?.id;
|
||||||
|
|
||||||
// Bandes jaunes déco haut/bas
|
// ── Bandes jaunes haut / bas (parentId OK pour rect)
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Bande haut', x: 0, y: 0, width: 600, height: 14, fillColor: '#F1C40F' });
|
await rect({ fileId, pageId, parentId: frameId, name: 'Bande haut', x: 0, y: 0, width: 600, height: 14, fillColor: '#F1C40F' });
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Bande bas', x: 0, y: 886, width: 600, height: 14, fillColor: '#F1C40F' });
|
await rect({ fileId, pageId, parentId: frameId, name: 'Bande bas', x: 0, y: 886, width: 600, height: 14, fillColor: '#F1C40F' });
|
||||||
|
|
||||||
// Header sombre
|
// ── Header sombre
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Header bg', x: 0, y: 14, width: 600, height: 88, fillColor: '#922B21' });
|
await rect({ fileId, pageId, parentId: frameId, name: 'Header bg', x: 0, y: 14, width: 600, height: 88, fillColor: '#7B241C' });
|
||||||
|
|
||||||
// MEGA SOLDES
|
// ── Séparateur milieu
|
||||||
await shape('text', {
|
await rect({ fileId, pageId, parentId: frameId, name: 'Sep1', x: 80, y: 430, width: 440, height: 3, fillColor: '#F1C40F' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'MEGA SOLDES', content: 'MEGA SOLDES',
|
// ── Badge OFFRE LIMITÉE
|
||||||
x: 0, y: 28, width: 600, height: 65,
|
await rect({ fileId, pageId, parentId: frameId, name: 'Badge bg', x: 115, y: 450, width: 370, height: 68, fillColor: '#F1C40F', r1: 34, r2: 34, r3: 34, r4: 34 });
|
||||||
fontSize: 36, fontWeight: '800', fillColor: '#F1C40F', align: 'center',
|
|
||||||
});
|
// ── Séparateur bas
|
||||||
|
await rect({ fileId, pageId, parentId: frameId, name: 'Sep2', x: 80, y: 760, width: 440, height: 3, fillColor: '#F8C471' });
|
||||||
|
|
||||||
|
// ── TEXTES (coords absolues, pas de parentId)
|
||||||
|
|
||||||
|
// Collection header (petit texte dans le header sombre)
|
||||||
|
await txt({ fileId, pageId, name: 'Collection', text: '✦ Collection Printemps 2026 ✦', x: 0, y: 32, fontSize: 14, fontWeight: '700', fillColor: '#F1C40F', textAlign: 'center', letterSpacing: 4 });
|
||||||
|
|
||||||
|
// SOLDES (grand titre)
|
||||||
|
await txt({ fileId, pageId, name: 'Soldes', text: 'SOLDES', x: 0, y: 115, fontSize: 90, fontWeight: '900', fillColor: '#F1C40F', textAlign: 'center', letterSpacing: 10 });
|
||||||
|
|
||||||
// -50%
|
// -50%
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Pct', text: '-50%', x: 0, y: 205, fontSize: 190, fontWeight: '900', fillColor: '#FFFFFF', textAlign: 'center' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: '-50%', content: '-50%',
|
|
||||||
x: 0, y: 110, width: 600, height: 250,
|
|
||||||
fontSize: 180, fontWeight: '900', fillColor: '#FFFFFF', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sous-titre chaussures
|
// Sous-titre
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Sous-titre', text: 'SUR TOUTES NOS CHAUSSURES', x: 0, y: 380, fontSize: 22, fontWeight: '700', fillColor: '#F1C40F', textAlign: 'center', letterSpacing: 3 });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Sous-titre', content: 'SUR TOUTES NOS CHAUSSURES',
|
|
||||||
x: 0, y: 368, width: 600, height: 55,
|
|
||||||
fontSize: 24, fontWeight: '700', fillColor: '#F1C40F', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Séparateur
|
// Offre limitée (sur le badge jaune)
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Sep', x: 80, y: 432, width: 440, height: 3, fillColor: '#F1C40F' });
|
await txt({ fileId, pageId, name: 'Offre', text: '⚡ OFFRE LIMITÉE ⚡', x: 115, y: 468, fontSize: 22, fontWeight: '900', fillColor: '#7B241C', textAlign: 'center' });
|
||||||
|
|
||||||
// Badge offre
|
|
||||||
await shape('rectangle', {
|
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Badge bg', x: 125, y: 450, width: 350, height: 70,
|
|
||||||
fillColor: '#F1C40F', r1: 12, r2: 12, r3: 12, r4: 12,
|
|
||||||
});
|
|
||||||
await shape('text', {
|
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Badge text', content: '⚡ OFFRE LIMITÉE ⚡',
|
|
||||||
x: 125, y: 465, width: 350, height: 42,
|
|
||||||
fontSize: 22, fontWeight: '800', fillColor: '#922B21', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dates
|
// Dates
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Dates', text: 'du 1er au 31 mars 2026', x: 0, y: 542, fontSize: 16, fontWeight: '400', fillColor: '#FADBD8', textAlign: 'center', letterSpacing: 2 });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Dates', content: 'du 1er au 31 mars 2026',
|
|
||||||
x: 0, y: 540, width: 600, height: 40,
|
|
||||||
fontSize: 20, fontWeight: '400', fillColor: '#FADBD8', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emojis chaussures
|
// Emojis
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Chaussures', text: '👟 👠 👞', x: 0, y: 580, fontSize: 80, textAlign: 'center' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Chaussures', content: '👟 👠 👞',
|
|
||||||
x: 0, y: 590, width: 600, height: 160,
|
|
||||||
fontSize: 90, align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ligne déco
|
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Sep2', x: 80, y: 760, width: 440, height: 3, fillColor: '#F8C471' });
|
|
||||||
|
|
||||||
// Site web
|
// Site web
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Site web', text: 'www.votre-boutique.fr', x: 0, y: 780, fontSize: 14, fontWeight: '400', fillColor: '#F8C471', textAlign: 'center', letterSpacing: 3 });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Site web', content: 'www.votre-boutique.fr',
|
|
||||||
x: 0, y: 775, width: 600, height: 35,
|
|
||||||
fontSize: 16, fontWeight: '400', fillColor: '#F8C471', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Étoiles déco bas
|
// Étoiles
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Etoiles', text: '★ ★ ★ ★ ★', x: 0, y: 836, fontSize: 20, fillColor: '#F1C40F', textAlign: 'center' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Etoiles', content: '★ ★ ★ ★ ★',
|
|
||||||
x: 0, y: 830, width: 600, height: 40,
|
|
||||||
fontSize: 22, fillColor: '#F1C40F', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n✅ Affiche SOLDES créée dans Penpot !');
|
console.log('\n✅ Affiche recréée dans Penpot !');
|
||||||
console.log('🔗 http://192.168.1.150:9001 → dossier "Drafts" → "Affiche SOLDES -50%"');
|
console.log('🔗 http://192.168.1.150:9001 → Drafts → "Affiche SOLDES -50%"');
|
||||||
|
|||||||
+75
-119
@@ -1,6 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Script tout-en-un : redémarre le MCP + crée l'affiche SOLDES
|
* Affiche SOLDES -50% — version corrigée
|
||||||
* Paramètres MCP = camelCase !
|
* Leçons apprises :
|
||||||
|
* - create_text : paramètre `text` (pas `content`), `textAlign` (pas `align`)
|
||||||
|
* - create_text : parentId NON supporté → textes à coords absolues
|
||||||
|
* - create_rectangle : parentId OK
|
||||||
|
* - Frame à (0,0) → coordonnées absolues = relatives au frame
|
||||||
*/
|
*/
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
@@ -17,8 +21,7 @@ function portainerReq(path, method = 'GET') {
|
|||||||
let d = ''; res.on('data', c => d += c);
|
let d = ''; res.on('data', c => d += c);
|
||||||
res.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve(res.statusCode); } });
|
res.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve(res.statusCode); } });
|
||||||
});
|
});
|
||||||
req.on('error', reject);
|
req.on('error', reject); req.end();
|
||||||
req.end();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,21 +59,14 @@ function postMCP(body) {
|
|||||||
if (line.startsWith('data:')) {
|
if (line.startsWith('data:')) {
|
||||||
const raw = line.slice(5).trim();
|
const raw = line.slice(5).trim();
|
||||||
if (!raw || raw === '[DONE]') continue;
|
if (!raw || raw === '[DONE]') continue;
|
||||||
try {
|
try { const j = JSON.parse(raw); if (j.result !== undefined) result = j.result; if (j.error) error = j.error; } catch {}
|
||||||
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)));
|
if (error) reject(new Error(JSON.stringify(error)));
|
||||||
else resolve(result);
|
else resolve(result);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try { const j = JSON.parse(data); if (j.error) reject(new Error(JSON.stringify(j.error))); else resolve(j.result ?? j); }
|
||||||
const j = JSON.parse(data);
|
catch { resolve(data || null); }
|
||||||
if (j.error) reject(new Error(JSON.stringify(j.error)));
|
|
||||||
else resolve(j.result ?? j);
|
|
||||||
} catch { resolve(data || null); }
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -81,156 +77,116 @@ function postMCP(body) {
|
|||||||
async function initMCP() {
|
async function initMCP() {
|
||||||
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', 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(() => {});
|
await postMCP({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }).catch(() => {});
|
||||||
console.log('✅ MCP Session:', sessionId);
|
console.log('✅ Session:', sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callTool(name, args = {}) {
|
async function callTool(name, args = {}) {
|
||||||
const result = await postMCP({ jsonrpc: '2.0', id: msgId++, method: 'tools/call', params: { name, arguments: args } });
|
const result = await postMCP({ jsonrpc: '2.0', id: msgId++, method: 'tools/call', params: { name, arguments: args } });
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
// Le tool retourne content[0] (summary) et content[1] (JSON)
|
|
||||||
if (result.content) {
|
if (result.content) {
|
||||||
const jsonContent = result.content.find(c => { try { JSON.parse(c.text); return true; } catch { return false; } });
|
for (const c of result.content) { try { return JSON.parse(c.text); } catch {} }
|
||||||
if (jsonContent) { try { return JSON.parse(jsonContent.text); } catch {} }
|
|
||||||
return result.content[0]?.text;
|
return result.content[0]?.text;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shape(type, args) {
|
async function rect(args) {
|
||||||
const r = await callTool(`create_${type}`, args);
|
const r = await callTool('create_rectangle', args);
|
||||||
const id = typeof r === 'object' ? r?.id : null;
|
console.log(` ▪ rect "${args.name}" ${r?.id?.slice(0,8) || JSON.stringify(r).slice(0,40)}`);
|
||||||
console.log(` ✓ ${type} "${args.name || ''}" ${id ? '→ ' + id.slice(0,8) : JSON.stringify(r).slice(0,60)}`);
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function txt(args) {
|
||||||
|
const r = await callTool('create_text', args);
|
||||||
|
console.log(` ▪ text "${args.name}" ${r?.id?.slice(0,8) || JSON.stringify(r).slice(0,40)}`);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── MAIN ────────────────────────────────────────────────────────────────────
|
// ─── MAIN ────────────────────────────────────────────────────────────────────
|
||||||
console.log('🚀 Démarrage création affiche SOLDES...');
|
console.log('🚀 Création affiche SOLDES (version corrigée)...');
|
||||||
await restartMCPContainer();
|
await restartMCPContainer();
|
||||||
await sleep(10000);
|
await sleep(10000);
|
||||||
await initMCP();
|
await initMCP();
|
||||||
|
|
||||||
// Profil
|
// Profil + IDs
|
||||||
const profile = await callTool('get_profile', {});
|
const profile = await callTool('get_profile', {});
|
||||||
const projectId = profile?.defaultProjectId;
|
const projectId = profile?.defaultProjectId;
|
||||||
console.log('ProjectId:', projectId);
|
console.log('ProjectId:', projectId);
|
||||||
|
|
||||||
// Créer le fichier (camelCase!)
|
// Supprimer l'ancien fichier si présent
|
||||||
console.log('\n📄 Création du fichier...');
|
const existingFiles = await callTool('list_files', { projectId });
|
||||||
const file = await callTool('create_file', { projectId, name: 'Affiche SOLDES -50%' });
|
if (Array.isArray(existingFiles)) {
|
||||||
console.log('File:', JSON.stringify(file).slice(0, 150));
|
for (const f of existingFiles.filter(f => f.name?.includes('SOLDES'))) {
|
||||||
|
await callTool('delete_file', { fileId: f.id });
|
||||||
|
console.log('🗑 Supprimé ancien fichier:', f.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le fichier
|
||||||
|
const file = await callTool('create_file', { projectId, name: 'Affiche SOLDES -50%' });
|
||||||
const fileId = file?.id;
|
const fileId = file?.id;
|
||||||
if (!fileId) { console.error('❌ Pas de fileId'); process.exit(1); }
|
|
||||||
console.log('FileId:', fileId);
|
console.log('FileId:', fileId);
|
||||||
|
|
||||||
// Lister les pages (camelCase)
|
const pages = await callTool('list_pages', { fileId });
|
||||||
const pagesResult = await callTool('list_pages', { fileId });
|
const pageId = Array.isArray(pages) ? pages[0]?.id : null;
|
||||||
console.log('Pages raw:', JSON.stringify(pagesResult).slice(0, 200));
|
|
||||||
let pageId = null;
|
|
||||||
if (Array.isArray(pagesResult)) pageId = pagesResult[0]?.id;
|
|
||||||
else if (pagesResult?.pages) pageId = pagesResult.pages[0]?.id;
|
|
||||||
else {
|
|
||||||
// Extraire UUID depuis texte
|
|
||||||
const uuids = JSON.stringify(pagesResult).match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g);
|
|
||||||
pageId = uuids?.find(u => u !== fileId);
|
|
||||||
}
|
|
||||||
console.log('PageId:', pageId);
|
console.log('PageId:', pageId);
|
||||||
if (!pageId) { console.error('❌ Pas de pageId'); process.exit(1); }
|
if (!pageId) { console.error('❌ Pas de pageId'); process.exit(1); }
|
||||||
|
|
||||||
// ─── DESIGN (600×900) ────────────────────────────────────────────────────────
|
// ─── DESIGN 600×900 — frame à (0,0) ─────────────────────────────────────────
|
||||||
console.log('\n🎨 Construction du design...');
|
console.log('\n🎨 Construction...');
|
||||||
|
|
||||||
// Frame principale - fond rouge
|
// Frame principale = fond rouge (à 0,0 → coords absolues = relatives)
|
||||||
const mainFrame = await shape('frame', {
|
const frame = await callTool('create_frame', {
|
||||||
fileId, pageId,
|
fileId, pageId,
|
||||||
name: 'Affiche SOLDES',
|
name: 'Affiche SOLDES', x: 0, y: 0, width: 600, height: 900,
|
||||||
x: 100, y: 100,
|
|
||||||
width: 600, height: 900,
|
|
||||||
fillColor: '#C0392B',
|
fillColor: '#C0392B',
|
||||||
});
|
});
|
||||||
const frameId = mainFrame?.id;
|
const frameId = frame?.id;
|
||||||
|
|
||||||
// Bandes jaunes déco haut/bas
|
// ── Bandes jaunes haut / bas (parentId OK pour rect)
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Bande haut', x: 0, y: 0, width: 600, height: 14, fillColor: '#F1C40F' });
|
await rect({ fileId, pageId, parentId: frameId, name: 'Bande haut', x: 0, y: 0, width: 600, height: 14, fillColor: '#F1C40F' });
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Bande bas', x: 0, y: 886, width: 600, height: 14, fillColor: '#F1C40F' });
|
await rect({ fileId, pageId, parentId: frameId, name: 'Bande bas', x: 0, y: 886, width: 600, height: 14, fillColor: '#F1C40F' });
|
||||||
|
|
||||||
// Header sombre
|
// ── Header sombre
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Header bg', x: 0, y: 14, width: 600, height: 88, fillColor: '#922B21' });
|
await rect({ fileId, pageId, parentId: frameId, name: 'Header bg', x: 0, y: 14, width: 600, height: 88, fillColor: '#7B241C' });
|
||||||
|
|
||||||
// MEGA SOLDES
|
// ── Séparateur milieu
|
||||||
await shape('text', {
|
await rect({ fileId, pageId, parentId: frameId, name: 'Sep1', x: 80, y: 430, width: 440, height: 3, fillColor: '#F1C40F' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'MEGA SOLDES', content: 'MEGA SOLDES',
|
// ── Badge OFFRE LIMITÉE
|
||||||
x: 0, y: 28, width: 600, height: 65,
|
await rect({ fileId, pageId, parentId: frameId, name: 'Badge bg', x: 115, y: 450, width: 370, height: 68, fillColor: '#F1C40F', r1: 34, r2: 34, r3: 34, r4: 34 });
|
||||||
fontSize: 36, fontWeight: '800', fillColor: '#F1C40F', align: 'center',
|
|
||||||
});
|
// ── Séparateur bas
|
||||||
|
await rect({ fileId, pageId, parentId: frameId, name: 'Sep2', x: 80, y: 760, width: 440, height: 3, fillColor: '#F8C471' });
|
||||||
|
|
||||||
|
// ── TEXTES (coords absolues, pas de parentId)
|
||||||
|
|
||||||
|
// Collection header (petit texte dans le header sombre)
|
||||||
|
await txt({ fileId, pageId, name: 'Collection', text: '✦ Collection Printemps 2026 ✦', x: 0, y: 32, fontSize: 14, fontWeight: '700', fillColor: '#F1C40F', textAlign: 'center', letterSpacing: 4 });
|
||||||
|
|
||||||
|
// SOLDES (grand titre)
|
||||||
|
await txt({ fileId, pageId, name: 'Soldes', text: 'SOLDES', x: 0, y: 115, fontSize: 90, fontWeight: '900', fillColor: '#F1C40F', textAlign: 'center', letterSpacing: 10 });
|
||||||
|
|
||||||
// -50%
|
// -50%
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Pct', text: '-50%', x: 0, y: 205, fontSize: 190, fontWeight: '900', fillColor: '#FFFFFF', textAlign: 'center' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: '-50%', content: '-50%',
|
|
||||||
x: 0, y: 110, width: 600, height: 250,
|
|
||||||
fontSize: 180, fontWeight: '900', fillColor: '#FFFFFF', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sous-titre chaussures
|
// Sous-titre
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Sous-titre', text: 'SUR TOUTES NOS CHAUSSURES', x: 0, y: 380, fontSize: 22, fontWeight: '700', fillColor: '#F1C40F', textAlign: 'center', letterSpacing: 3 });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Sous-titre', content: 'SUR TOUTES NOS CHAUSSURES',
|
|
||||||
x: 0, y: 368, width: 600, height: 55,
|
|
||||||
fontSize: 24, fontWeight: '700', fillColor: '#F1C40F', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Séparateur
|
// Offre limitée (sur le badge jaune)
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Sep', x: 80, y: 432, width: 440, height: 3, fillColor: '#F1C40F' });
|
await txt({ fileId, pageId, name: 'Offre', text: '⚡ OFFRE LIMITÉE ⚡', x: 115, y: 468, fontSize: 22, fontWeight: '900', fillColor: '#7B241C', textAlign: 'center' });
|
||||||
|
|
||||||
// Badge offre
|
|
||||||
await shape('rectangle', {
|
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Badge bg', x: 125, y: 450, width: 350, height: 70,
|
|
||||||
fillColor: '#F1C40F', r1: 12, r2: 12, r3: 12, r4: 12,
|
|
||||||
});
|
|
||||||
await shape('text', {
|
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Badge text', content: '⚡ OFFRE LIMITÉE ⚡',
|
|
||||||
x: 125, y: 465, width: 350, height: 42,
|
|
||||||
fontSize: 22, fontWeight: '800', fillColor: '#922B21', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dates
|
// Dates
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Dates', text: 'du 1er au 31 mars 2026', x: 0, y: 542, fontSize: 16, fontWeight: '400', fillColor: '#FADBD8', textAlign: 'center', letterSpacing: 2 });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Dates', content: 'du 1er au 31 mars 2026',
|
|
||||||
x: 0, y: 540, width: 600, height: 40,
|
|
||||||
fontSize: 20, fontWeight: '400', fillColor: '#FADBD8', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emojis chaussures
|
// Emojis
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Chaussures', text: '👟 👠 👞', x: 0, y: 580, fontSize: 80, textAlign: 'center' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Chaussures', content: '👟 👠 👞',
|
|
||||||
x: 0, y: 590, width: 600, height: 160,
|
|
||||||
fontSize: 90, align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ligne déco
|
|
||||||
await shape('rectangle', { fileId, pageId, parentId: frameId, name: 'Sep2', x: 80, y: 760, width: 440, height: 3, fillColor: '#F8C471' });
|
|
||||||
|
|
||||||
// Site web
|
// Site web
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Site web', text: 'www.votre-boutique.fr', x: 0, y: 780, fontSize: 14, fontWeight: '400', fillColor: '#F8C471', textAlign: 'center', letterSpacing: 3 });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Site web', content: 'www.votre-boutique.fr',
|
|
||||||
x: 0, y: 775, width: 600, height: 35,
|
|
||||||
fontSize: 16, fontWeight: '400', fillColor: '#F8C471', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Étoiles déco bas
|
// Étoiles
|
||||||
await shape('text', {
|
await txt({ fileId, pageId, name: 'Etoiles', text: '★ ★ ★ ★ ★', x: 0, y: 836, fontSize: 20, fillColor: '#F1C40F', textAlign: 'center' });
|
||||||
fileId, pageId, parentId: frameId,
|
|
||||||
name: 'Etoiles', content: '★ ★ ★ ★ ★',
|
|
||||||
x: 0, y: 830, width: 600, height: 40,
|
|
||||||
fontSize: 22, fillColor: '#F1C40F', align: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('\n✅ Affiche SOLDES créée dans Penpot !');
|
console.log('\n✅ Affiche recréée dans Penpot !');
|
||||||
console.log('🔗 http://192.168.1.150:9001 → dossier "Drafts" → "Affiche SOLDES -50%"');
|
console.log('🔗 http://192.168.1.150:9001 → Drafts → "Affiche SOLDES -50%"');
|
||||||
|
|||||||
Reference in New Issue
Block a user