MEMORY: Penpot API directe port 9003, gotchas pixel-perfect

This commit is contained in:
Nox
2026-02-28 15:14:00 +00:00
parent 935f31dbb6
commit 45224642ff
+77
View File
@@ -165,6 +165,83 @@ create_text { fileId, pageId, text, x, y, textAlign, ... } ← PAS de parentId
- Uploader sur CopyParty : `curl -X PUT http://192.168.1.150:3923/anytype/image.png --data-binary @image.png` - Uploader sur CopyParty : `curl -X PUT http://192.168.1.150:3923/anytype/image.png --data-binary @image.png`
- Puis : `upload_file_media_from_url { fileId, url: "http://192.168.1.150:3923/anytype/image.png" }` - Puis : `upload_file_media_from_url { fileId, url: "http://192.168.1.150:3923/anytype/image.png" }`
## Penpot API Directe — Pixel-perfect (⭐ À UTILISER EN PRIORITÉ)
### Pourquoi directe > MCP
- MCP : auto-calcule les dims texte, pas de `parentId` pour les textes → pas de contrôle pixel-perfect
- API directe : contrôle total (x, y, width, height, parentId, contenu riche)
### Accès
- **URL backend** : `http://192.168.1.150:9003/api/rpc/command/<cmd>` (port 9003 exposé = penpot-backend:6060)
- **Auth** : `Authorization: Token <access_token>` (dans l'en-tête HTTP)
- **Token** : récupérable dans Penpot UI → Profile → Access Tokens
- **Format corps** : JSON **kebab-case** (ex: `page-id`, `frame-id`, `fill-color`)
- **Réponse** : kebab-case aussi (`default-team-id`, `default-project-id`)
- ⚠️ L'accès via nginx port 9001 retourne un user vide même avec le bon token — **toujours utiliser le port 9003**
### Workflow création poster (Node.js)
```js
// 1. Auth + IDs
const prof = await rpc('get-profile');
const teamId = prof['default-team-id'];
const projectId = prof['default-project-id'];
// 2. Créer fichier
const file = await rpc('create-file', { 'project-id': projectId, name: 'Mon affiche', 'is-shared': false });
const FILE_ID = file.id;
const PAGE_ID = Object.keys(file.data?.['pages-index'] || {})[0] || file.data?.pages?.[0];
// 3. Ajouter shapes une par une (revn incrémental !)
for (const change of changes) {
const result = await rpc('update-file', {
id: FILE_ID, 'session-id': randomUUID(), revn: currentRevn, vern: 0,
changes: [change]
});
currentRevn = result.revn;
}
```
### Format d'une change `add-obj`
```js
{
type: 'add-obj',
id: '<uuid>',
'page-id': PAGE_ID,
'frame-id': ROOT, // '00000000-0000-0000-0000-000000000000' = root
'parent-id': ROOT,
obj: {
id, type: 'rect'|'text'|'frame', name,
x, y, width, height,
'parent-id': ROOT, 'frame-id': ROOT,
fills: [{ 'fill-color': '#C0392B', 'fill-opacity': 1 }],
selrect: { x, y, width, height, x1, y1, x2, y2 },
points: [{x,y},{x:x+w,y},{x:x+w,y:y+h},{x,y:y+h}],
transform: {a:1,b:0,c:0,d:1,e:0,f:0},
'transform-inverse': {a:1,b:0,c:0,d:1,e:0,f:0},
// Pour un text :
content: { type:'root', children:[{ type:'paragraph-set', children:[{
type:'paragraph', 'text-align':'center', children:[{
text: 'Mon texte',
'font-id':'gfont-work-sans', 'font-family':'Work Sans',
'font-size':'48', 'font-weight':'700', 'font-style':'normal',
'letter-spacing':'2', 'line-height': 1.2, 'text-decoration':'none',
fills:[{'fill-color':'#FFFFFF', 'fill-opacity':1}],
}]
}]}]}
}
}
```
### ⚠️ Gotchas critiques
- **Envoyer 1 change à la fois** (pas toutes en même temps) + incrémenter `revn`
- **`type: 'frame'`** échoue avec erreur `:shapes nil` → utiliser `type: 'rect'` comme fond
- **toKebab()** : tous les paramètres JS en camelCase → convertir en kebab-case avant envoi
- **`text` content** : bien mettre le champ `text:` dans le nœud enfant (sinon validation error)
- **Texte centré** : mettre `x:0, width:W` (toute la largeur) + `text-align: 'center'` dans le paragraphe
### Script de référence
`/home/node/.openclaw/workspace/penpot-api-direct.mjs` — poster 600×900 complet, 16 shapes
## Podcasts & Vidéos — Transcription ## Podcasts & Vidéos — Transcription
- Je peux **récupérer et transcrire** des podcasts/vidéos en ligne - Je peux **récupérer et transcrire** des podcasts/vidéos en ligne
- **Méthode :** - **Méthode :**