Aktraiser commited on
Commit
a4d9764
·
1 Parent(s): 0eed2ad

refacto code

Browse files
Files changed (7) hide show
  1. app.py +111 -633
  2. tools/__init__.py +86 -0
  3. tools/config.py +102 -0
  4. tools/figjam.py +361 -0
  5. tools/figma_design.py +92 -0
  6. tools/navigation.py +67 -0
  7. tools/user_account.py +237 -0
app.py CHANGED
@@ -7,647 +7,17 @@ import gradio as gr
7
  import asyncio
8
  import json
9
  import logging
10
- import requests
11
- from typing import Dict, Any, Optional, List
12
  from PIL import Image
13
  import base64
14
  import io
15
 
 
 
 
16
  # Configuration du logging
17
  logging.basicConfig(level=logging.INFO)
18
  logger = logging.getLogger(__name__)
19
 
20
- # Configuration Figma API
21
- FIGMA_API_BASE = "https://api.figma.com/v1"
22
-
23
- # === CONFIGURATION ET ÉTAT ===
24
-
25
- # Variables globales pour stocker la configuration
26
- figma_config = {
27
- "token": None,
28
- "file_id": None,
29
- "team_id": None
30
- }
31
-
32
- def configure_figma_token(token: str) -> str:
33
- """Configure le token d'accès Figma"""
34
- global figma_config
35
-
36
- if not token or not token.startswith(('figd_', 'figc_')):
37
- return "❌ Token invalide. Le token doit commencer par 'figd_' ou 'figc_'"
38
-
39
- figma_config["token"] = token
40
-
41
- # Test de connexion
42
- try:
43
- headers = {"X-Figma-Token": token}
44
- response = requests.get(f"{FIGMA_API_BASE}/me", headers=headers, timeout=10)
45
-
46
- if response.status_code == 200:
47
- user_data = response.json()
48
- username = user_data.get("handle", "Utilisateur inconnu")
49
- return f"✅ Token configuré avec succès ! Connecté en tant que : {username}"
50
- else:
51
- return f"❌ Erreur lors de la vérification du token : {response.status_code}"
52
-
53
- except Exception as e:
54
- return f"❌ Erreur de connexion à l'API Figma : {str(e)}"
55
-
56
- def configure_figma_file_id(file_id: str) -> str:
57
- """Configure l'ID du fichier Figma à utiliser"""
58
- global figma_config
59
-
60
- if not file_id:
61
- return "❌ L'ID du fichier est requis"
62
-
63
- figma_config["file_id"] = file_id
64
-
65
- # Test d'accès au fichier
66
- if figma_config["token"]:
67
- try:
68
- headers = {"X-Figma-Token": figma_config["token"]}
69
- response = requests.get(f"{FIGMA_API_BASE}/files/{file_id}", headers=headers, timeout=10)
70
-
71
- if response.status_code == 200:
72
- file_data = response.json()
73
- file_name = file_data.get("name", "Fichier inconnu")
74
- return f"✅ Fichier configuré avec succès : {file_name}"
75
- else:
76
- return f"❌ Impossible d'accéder au fichier : {response.status_code}"
77
-
78
- except Exception as e:
79
- return f"❌ Erreur lors de l'accès au fichier : {str(e)}"
80
- else:
81
- return "⚠️ ID du fichier configuré, mais token manquant"
82
-
83
- # === FONCTIONS API FIGMA ===
84
-
85
- def make_figma_request(endpoint: str, method: str = "GET", data: Dict = None) -> Dict[str, Any]:
86
- """Effectue une requête à l'API Figma"""
87
- if not figma_config["token"]:
88
- return {"error": "Token Figma non configuré"}
89
-
90
- headers = {
91
- "X-Figma-Token": figma_config["token"],
92
- "Content-Type": "application/json"
93
- }
94
-
95
- url = f"{FIGMA_API_BASE}/{endpoint}"
96
-
97
- try:
98
- if method == "GET":
99
- response = requests.get(url, headers=headers, timeout=30)
100
- elif method == "POST":
101
- response = requests.post(url, headers=headers, json=data, timeout=30)
102
- elif method == "PUT":
103
- response = requests.put(url, headers=headers, json=data, timeout=30)
104
- elif method == "DELETE":
105
- response = requests.delete(url, headers=headers, timeout=30)
106
- else:
107
- return {"error": f"Méthode HTTP non supportée : {method}"}
108
-
109
- if response.status_code in [200, 201]:
110
- return response.json()
111
- else:
112
- return {"error": f"Erreur API {response.status_code}: {response.text}"}
113
-
114
- except Exception as e:
115
- return {"error": f"Erreur de requête : {str(e)}"}
116
-
117
- # === OUTILS MCP FIGMA ===
118
-
119
- def get_figma_file_info(file_id: str = "") -> str:
120
- """Récupère les informations d'un fichier Figma"""
121
- file_id = file_id or figma_config["file_id"]
122
-
123
- if not file_id:
124
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
125
-
126
- result = make_figma_request(f"files/{file_id}")
127
-
128
- if "error" in result:
129
- return f"❌ Erreur : {result['error']}"
130
-
131
- file_info = {
132
- "nom": result.get("name", ""),
133
- "derniere_modification": result.get("lastModified", ""),
134
- "version": result.get("version", ""),
135
- "pages": [page.get("name", "") for page in result.get("document", {}).get("children", [])]
136
- }
137
-
138
- return f"📄 **Fichier Figma :**\n{json.dumps(file_info, indent=2, ensure_ascii=False)}"
139
-
140
- def create_figma_rectangle(x: str, y: str, width: str, height: str, name: str = "Rectangle", color: str = "#FF0000") -> str:
141
- """Crée un rectangle dans Figma (via commentaire pour notification)"""
142
- if not figma_config["file_id"]:
143
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
144
-
145
- try:
146
- x_pos, y_pos = float(x), float(y)
147
- w, h = float(width), float(height)
148
-
149
- # Créer un commentaire avec les instructions de création
150
- comment_text = f"🟦 **Rectangle à créer :**\n- Nom: {name}\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}\n- Couleur: {color}"
151
-
152
- comment_data = {
153
- "message": comment_text,
154
- "client_meta": {
155
- "x": x_pos,
156
- "y": y_pos
157
- }
158
- }
159
-
160
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
161
-
162
- if "error" in result:
163
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
164
-
165
- return f"✅ Rectangle {name} créé (via commentaire) à ({x_pos}, {y_pos}) avec la taille {w}x{h}"
166
-
167
- except ValueError:
168
- return "❌ Les coordonnées et dimensions doivent être des nombres"
169
-
170
- def create_figma_frame(x: str, y: str, width: str, height: str, name: str = "Frame") -> str:
171
- """Crée un frame dans Figma (via commentaire pour notification)"""
172
- if not figma_config["file_id"]:
173
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
174
-
175
- try:
176
- x_pos, y_pos = float(x), float(y)
177
- w, h = float(width), float(height)
178
-
179
- comment_text = f"🖼️ **Frame à créer :**\n- Nom: {name}\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}"
180
-
181
- comment_data = {
182
- "message": comment_text,
183
- "client_meta": {
184
- "x": x_pos,
185
- "y": y_pos
186
- }
187
- }
188
-
189
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
190
-
191
- if "error" in result:
192
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
193
-
194
- return f"✅ Frame {name} créé (via commentaire) à ({x_pos}, {y_pos}) avec la taille {w}x{h}"
195
-
196
- except ValueError:
197
- return "❌ Les coordonnées et dimensions doivent être des nombres"
198
-
199
- def create_figma_text(x: str, y: str, text: str, name: str = "Text", font_size: str = "16") -> str:
200
- """Crée un élément texte dans Figma (via commentaire pour notification)"""
201
- if not figma_config["file_id"]:
202
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
203
-
204
- try:
205
- x_pos, y_pos = float(x), float(y)
206
- size = float(font_size)
207
-
208
- comment_text = f"📝 **Texte à créer :**\n- Nom: {name}\n- Position: ({x_pos}, {y_pos})\n- Texte: \"{text}\"\n- Taille: {size}px"
209
-
210
- comment_data = {
211
- "message": comment_text,
212
- "client_meta": {
213
- "x": x_pos,
214
- "y": y_pos
215
- }
216
- }
217
-
218
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
219
-
220
- if "error" in result:
221
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
222
-
223
- return f"✅ Texte \"{text}\" créé (via commentaire) à ({x_pos}, {y_pos})"
224
-
225
- except ValueError:
226
- return "❌ Les coordonnées et la taille doivent être des nombres"
227
-
228
- def get_figma_comments(file_id: str = "") -> str:
229
- """Récupère tous les commentaires d'un fichier Figma"""
230
- file_id = file_id or figma_config["file_id"]
231
-
232
- if not file_id:
233
- return "❌ ID du fichier requis"
234
-
235
- result = make_figma_request(f"files/{file_id}/comments")
236
-
237
- if "error" in result:
238
- return f"❌ Erreur : {result['error']}"
239
-
240
- comments = result.get("comments", [])
241
-
242
- if not comments:
243
- return "📝 Aucun commentaire trouvé dans ce fichier"
244
-
245
- comment_list = []
246
- for comment in comments[:10]: # Limiter à 10 commentaires
247
- user = comment.get("user", {}).get("handle", "Anonyme")
248
- message = comment.get("message", "")
249
- created_at = comment.get("created_at", "")
250
- comment_list.append(f"👤 {user} ({created_at}): {message}")
251
-
252
- return f"📝 **Commentaires récents :**\n" + "\n\n".join(comment_list)
253
-
254
- def get_figma_user_info() -> str:
255
- """Récupère les informations de l'utilisateur connecté"""
256
- result = make_figma_request("me")
257
-
258
- if "error" in result:
259
- return f"❌ Erreur : {result['error']}"
260
-
261
- user_info = {
262
- "nom": result.get("handle", ""),
263
- "email": result.get("email", ""),
264
- "id": result.get("id", "")
265
- }
266
-
267
- return f"👤 **Utilisateur connecté :**\n{json.dumps(user_info, indent=2, ensure_ascii=False)}"
268
-
269
- def list_figma_team_projects(team_id: str = "") -> str:
270
- """Liste les projets d'une équipe Figma"""
271
- team_id = team_id or figma_config["team_id"]
272
-
273
- if not team_id:
274
- return "❌ ID de l'équipe requis. Configurez-le avec figma_config['team_id'] = 'VOTRE_TEAM_ID'"
275
-
276
- result = make_figma_request(f"teams/{team_id}/projects")
277
-
278
- if "error" in result:
279
- return f"❌ Erreur : {result['error']}"
280
-
281
- projects = result.get("projects", [])
282
-
283
- if not projects:
284
- return "📁 Aucun projet trouvé dans cette équipe"
285
-
286
- project_list = []
287
- for project in projects[:10]: # Limiter à 10 projets
288
- name = project.get("name", "Sans nom")
289
- project_id = project.get("id", "")
290
- project_list.append(f"📁 {name} (ID: {project_id})")
291
-
292
- return f"📁 **Projets de l'équipe :**\n" + "\n".join(project_list)
293
-
294
- def create_figjam_sticky_note(x: str, y: str, text: str, width: str = "240", height: str = "240") -> str:
295
- """Crée un post-it (sticky note) dans FigJam avec les dimensions officielles"""
296
- if not figma_config["file_id"]:
297
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
298
-
299
- try:
300
- x_pos, y_pos = float(x), float(y)
301
- w, h = float(width), float(height)
302
-
303
- comment_text = f"🟡 **Sticky Note à créer (API FigJam) :**\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px (défaut: 240x240)\n- Texte: \"{text}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst sticky = figma.createSticky()\nsticky.x = {x_pos}\nsticky.y = {y_pos}\nsticky.resize({w}, {h})\nawait figma.loadFontAsync(sticky.text.fontName)\nsticky.text.characters = '{text}'\n```"
304
-
305
- comment_data = {
306
- "message": comment_text,
307
- "client_meta": {
308
- "x": x_pos,
309
- "y": y_pos
310
- }
311
- }
312
-
313
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
314
-
315
- if "error" in result:
316
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
317
-
318
- return f"✅ Instructions Sticky Note créées à ({x_pos}, {y_pos}) - Taille: {w}x{h}px"
319
-
320
- except ValueError:
321
- return "❌ Les coordonnées et dimensions doivent être des nombres"
322
-
323
- def create_figjam_connector_between_elements(element1_name: str, element2_name: str, style: str = "solid") -> str:
324
- """Crée un connecteur entre deux éléments FigJam (officiel)"""
325
- if not figma_config["file_id"]:
326
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
327
-
328
- comment_text = f"🔗 **Connecteur FigJam à créer (API officielle) :**\n- Entre: {element1_name}\n- Et: {element2_name}\n- Style: {style}\n\n📋 **Code Plugin Figma :**\n```javascript\n// Trouver les éléments par nom\nconst element1 = figma.currentPage.findOne(n => n.name === '{element1_name}')\nconst element2 = figma.currentPage.findOne(n => n.name === '{element2_name}')\n\n// Créer le connecteur\nconst connector = figma.createConnector()\nconnector.connectorStart = {{\n endpointNodeId: element1.id,\n magnet: 'AUTO'\n}}\nconnector.connectorEnd = {{\n endpointNodeId: element2.id,\n magnet: 'AUTO'\n}}\n```"
329
-
330
- comment_data = {
331
- "message": comment_text,
332
- "client_meta": {
333
- "x": 0,
334
- "y": 0
335
- }
336
- }
337
-
338
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
339
-
340
- if "error" in result:
341
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
342
-
343
- return f"✅ Instructions connecteur créées entre {element1_name} et {element2_name}"
344
-
345
- def create_figjam_shape_with_text(x: str, y: str, shape_type: str, text: str, width: str = "208", height: str = "208") -> str:
346
- """Crée une forme avec texte intégré dans FigJam (API officielle)"""
347
- if not figma_config["file_id"]:
348
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
349
-
350
- try:
351
- x_pos, y_pos = float(x), float(y)
352
- w, h = float(width), float(height)
353
-
354
- # Types de formes supportés par l'API Figma
355
- supported_shapes = {
356
- "rectangle": "ROUNDED_RECTANGLE",
357
- "circle": "ELLIPSE",
358
- "triangle": "TRIANGLE",
359
- "diamond": "DIAMOND",
360
- "star": "STAR",
361
- "hexagon": "HEXAGON"
362
- }
363
-
364
- figma_shape = supported_shapes.get(shape_type.lower(), "ROUNDED_RECTANGLE")
365
-
366
- comment_text = f"🔶 **Forme avec texte FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Type: {shape_type} → {figma_shape}\n- Taille: {w}x{h}px (défaut: 208x208)\n- Texte: \"{text}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst shape = figma.createShapeWithText()\nshape.x = {x_pos}\nshape.y = {y_pos}\nshape.resize({w}, {h})\nshape.shapeType = '{figma_shape}'\nawait figma.loadFontAsync(shape.text.fontName)\nshape.text.characters = '{text}'\n```"
367
-
368
- comment_data = {
369
- "message": comment_text,
370
- "client_meta": {
371
- "x": x_pos,
372
- "y": y_pos
373
- }
374
- }
375
-
376
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
377
-
378
- if "error" in result:
379
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
380
-
381
- return f"✅ Instructions forme {shape_type} avec texte créées à ({x_pos}, {y_pos})"
382
-
383
- except ValueError:
384
- return "❌ Les coordonnées et dimensions doivent être des nombres"
385
-
386
- def create_figjam_table(rows: str, columns: str, x: str = "0", y: str = "0") -> str:
387
- """Crée un tableau dans FigJam (API officielle)"""
388
- if not figma_config["file_id"]:
389
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
390
-
391
- try:
392
- num_rows, num_cols = int(rows), int(columns)
393
- x_pos, y_pos = float(x), float(y)
394
-
395
- comment_text = f"📊 **Tableau FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Dimensions: {num_rows} lignes × {num_cols} colonnes\n\n📋 **Code Plugin Figma :**\n```javascript\nconst table = figma.createTable({num_rows}, {num_cols})\ntable.x = {x_pos}\ntable.y = {y_pos}\n\n// Exemple: ajouter du texte dans les cellules\nawait figma.loadFontAsync(table.cellAt(0, 0).text.fontName)\ntable.cellAt(0, 0).text.characters = 'Titre 1'\ntable.cellAt(0, 1).text.characters = 'Titre 2'\n```"
396
-
397
- comment_data = {
398
- "message": comment_text,
399
- "client_meta": {
400
- "x": x_pos,
401
- "y": y_pos
402
- }
403
- }
404
-
405
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
406
-
407
- if "error" in result:
408
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
409
-
410
- return f"✅ Instructions tableau {num_rows}×{num_cols} créées à ({x_pos}, {y_pos})"
411
-
412
- except ValueError:
413
- return "❌ Les dimensions et coordonnées doivent être des nombres"
414
-
415
- def create_figjam_code_block(x: str, y: str, code: str, language: str = "javascript") -> str:
416
- """Crée un bloc de code dans FigJam (API officielle)"""
417
- if not figma_config["file_id"]:
418
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
419
-
420
- try:
421
- x_pos, y_pos = float(x), float(y)
422
-
423
- # Échapper les caractères spéciaux pour l'affichage
424
- escaped_code = code.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
425
-
426
- comment_text = f"💻 **Bloc de code FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Langage: {language}\n- Code: \"{code[:100]}{'...' if len(code) > 100 else ''}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst codeBlock = figma.createCodeBlock()\ncodeBlock.x = {x_pos}\ncodeBlock.y = {y_pos}\nawait figma.loadFontAsync(codeBlock.codeBlockText.fontName)\ncodeBlock.codeBlockText.characters = \"{escaped_code}\"\n```"
427
-
428
- comment_data = {
429
- "message": comment_text,
430
- "client_meta": {
431
- "x": x_pos,
432
- "y": y_pos
433
- }
434
- }
435
-
436
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
437
-
438
- if "error" in result:
439
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
440
-
441
- return f"✅ Instructions bloc de code {language} créées à ({x_pos}, {y_pos})"
442
-
443
- except ValueError:
444
- return "❌ Les coordonnées doivent être des nombres"
445
-
446
- def create_figjam_background_shape(x: str, y: str, width: str, height: str, color: str = "#F3F4F6", title: str = "", corner_radius: str = "8") -> str:
447
- """Crée une forme de fond rectangulaire pour organiser le contenu FigJam"""
448
- if not figma_config["file_id"]:
449
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
450
-
451
- try:
452
- x_pos, y_pos = float(x), float(y)
453
- w, h = float(width), float(height)
454
- radius = float(corner_radius)
455
-
456
- comment_text = f"📐 **Forme de fond FigJam (zone de travail) :**\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px\n- Couleur: {color}\n- Titre: \"{title}\"\n- Rayon coins: {radius}px\n\n📋 **Code Plugin Figma :**\n```javascript\n// Créer la forme de fond\nconst background = figma.createRectangle()\nbackground.x = {x_pos}\nbackground.y = {y_pos}\nbackground.resize({w}, {h})\nbackground.cornerRadius = {radius}\nbackground.fills = [{{\n type: 'SOLID',\n color: {{ r: 0.95, g: 0.96, b: 0.97 }} // {color}\n}}]\nbackground.name = 'Zone - {title}'\n\n// Optionnel: ajouter un titre\nif ('{title}') {{\n const titleText = figma.createText()\n titleText.x = {x_pos + 16}\n titleText.y = {y_pos + 16}\n await figma.loadFontAsync(titleText.fontName)\n titleText.characters = '{title}'\n titleText.fontSize = 18\n}}\n```"
457
-
458
- comment_data = {
459
- "message": comment_text,
460
- "client_meta": {
461
- "x": x_pos,
462
- "y": y_pos
463
- }
464
- }
465
-
466
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
467
-
468
- if "error" in result:
469
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
470
-
471
- return f"✅ Zone de travail \"{title}\" créée à ({x_pos}, {y_pos}) - {w}x{h}px"
472
-
473
- except ValueError:
474
- return "❌ Les coordonnées et dimensions doivent être des nombres"
475
-
476
- def create_figjam_sticker(x: str, y: str, sticker_type: str, size: str = "40") -> str:
477
- """Crée un sticker/emoji dans FigJam pour les réactions et annotations"""
478
- if not figma_config["file_id"]:
479
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
480
-
481
- try:
482
- x_pos, y_pos = float(x), float(y)
483
- sticker_size = float(size)
484
-
485
- # Mapping des stickers populaires
486
- stickers = {
487
- "thumbs_up": "👍",
488
- "thumbs_down": "👎",
489
- "heart": "❤️",
490
- "star": "⭐",
491
- "fire": "🔥",
492
- "rocket": "🚀",
493
- "bulb": "💡",
494
- "warning": "⚠️",
495
- "check": "✅",
496
- "cross": "❌",
497
- "question": "❓",
498
- "idea": "💭",
499
- "clock": "⏰",
500
- "money": "💰",
501
- "target": "🎯",
502
- "celebrate": "🎉",
503
- "thinking": "🤔",
504
- "confused": "😕"
505
- }
506
-
507
- emoji = stickers.get(sticker_type.lower(), sticker_type)
508
-
509
- comment_text = f"🎭 **Sticker FigJam :**\n- Position: ({x_pos}, {y_pos})\n- Type: {sticker_type} → {emoji}\n- Taille: {sticker_size}px\n\n📋 **Code Plugin Figma :**\n```javascript\n// Créer un sticker comme texte stylisé\nconst sticker = figma.createText()\nsticker.x = {x_pos}\nsticker.y = {y_pos}\nsticker.resize({sticker_size}, {sticker_size})\nawait figma.loadFontAsync({{ family: 'Inter', style: 'Regular' }})\nsticker.characters = '{emoji}'\nsticker.fontSize = {sticker_size}\nsticker.textAlignHorizontal = 'CENTER'\nsticker.textAlignVertical = 'CENTER'\nsticker.name = 'Sticker - {sticker_type}'\n```"
510
-
511
- comment_data = {
512
- "message": comment_text,
513
- "client_meta": {
514
- "x": x_pos,
515
- "y": y_pos
516
- }
517
- }
518
-
519
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
520
-
521
- if "error" in result:
522
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
523
-
524
- return f"✅ Sticker {emoji} créé à ({x_pos}, {y_pos}) - Taille: {sticker_size}px"
525
-
526
- except ValueError:
527
- return "❌ Les coordonnées et la taille doivent être des nombres"
528
-
529
- def create_figjam_workshop_template(template_type: str, x: str = "0", y: str = "0") -> str:
530
- """Crée des templates d'atelier FigJam prêts à utiliser"""
531
- if not figma_config["file_id"]:
532
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
533
-
534
- try:
535
- x_pos, y_pos = float(x), float(y)
536
-
537
- templates = {
538
- "retrospective": {
539
- "title": "Rétrospective Sprint",
540
- "zones": [
541
- {"title": "😊 Ce qui s'est bien passé", "x": 0, "y": 100, "width": 300, "height": 400, "color": "#D1FAE5"},
542
- {"title": "😐 Ce qui peut être amélioré", "x": 320, "y": 100, "width": 300, "height": 400, "color": "#FEF3C7"},
543
- {"title": "🚀 Actions à prendre", "x": 640, "y": 100, "width": 300, "height": 400, "color": "#DBEAFE"}
544
- ]
545
- },
546
- "brainstorm": {
547
- "title": "Session Brainstorming",
548
- "zones": [
549
- {"title": "🎯 Objectif", "x": 0, "y": 100, "width": 960, "height": 120, "color": "#F3E8FF"},
550
- {"title": "💡 Idées", "x": 0, "y": 240, "width": 480, "height": 400, "color": "#FEF3C7"},
551
- {"title": "⭐ Meilleures idées", "x": 500, "y": 240, "width": 460, "height": 400, "color": "#D1FAE5"}
552
- ]
553
- },
554
- "user_journey": {
555
- "title": "Parcours Utilisateur",
556
- "zones": [
557
- {"title": "👤 Persona", "x": 0, "y": 100, "width": 200, "height": 300, "color": "#F3E8FF"},
558
- {"title": "🎯 Découverte", "x": 220, "y": 100, "width": 180, "height": 300, "color": "#DBEAFE"},
559
- {"title": "🔍 Exploration", "x": 420, "y": 100, "width": 180, "height": 300, "color": "#FEF3C7"},
560
- {"title": "✅ Conversion", "x": 620, "y": 100, "width": 180, "height": 300, "color": "#D1FAE5"},
561
- {"title": "💝 Fidélisation", "x": 820, "y": 100, "width": 180, "height": 300, "color": "#FECACA"}
562
- ]
563
- }
564
- }
565
-
566
- template = templates.get(template_type.lower())
567
-
568
- if not template:
569
- available = ", ".join(templates.keys())
570
- return f"❌ Template non trouvé. Disponibles: {available}"
571
-
572
- # Générer le code pour créer le template complet
573
- zones_code = []
574
- for i, zone in enumerate(template["zones"]):
575
- zone_x = x_pos + zone["x"]
576
- zone_y = y_pos + zone["y"]
577
- zones_code.append(f"""
578
- // Zone {i+1}: {zone['title']}
579
- const zone{i+1} = figma.createRectangle()
580
- zone{i+1}.x = {zone_x}
581
- zone{i+1}.y = {zone_y}
582
- zone{i+1}.resize({zone['width']}, {zone['height']})
583
- zone{i+1}.cornerRadius = 8
584
- zone{i+1}.fills = [{{ type: 'SOLID', color: {{ r: 0.95, g: 0.96, b: 0.97 }} }}]
585
- zone{i+1}.name = '{zone['title']}'
586
-
587
- const title{i+1} = figma.createText()
588
- title{i+1}.x = {zone_x + 16}
589
- title{i+1}.y = {zone_y + 16}
590
- await figma.loadFontAsync(title{i+1}.fontName)
591
- title{i+1}.characters = '{zone['title']}'
592
- title{i+1}.fontSize = 16""")
593
-
594
- comment_text = f"🏗️ **Template FigJam: {template['title']} :**\n- Position: ({x_pos}, {y_pos})\n- {len(template['zones'])} zones de travail\n\n📋 **Code Plugin Figma :**\n```javascript\n// Template: {template['title']}{''.join(zones_code)}\n```"
595
-
596
- comment_data = {
597
- "message": comment_text,
598
- "client_meta": {
599
- "x": x_pos,
600
- "y": y_pos
601
- }
602
- }
603
-
604
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
605
-
606
- if "error" in result:
607
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
608
-
609
- return f"✅ Template \"{template['title']}\" créé avec {len(template['zones'])} zones à ({x_pos}, {y_pos})"
610
-
611
- except ValueError:
612
- return "❌ Les coordonnées doivent être des nombres"
613
-
614
- def create_figjam_organized_zone(title: str, x: str, y: str, width: str = "400", height: str = "500", max_stickies: str = "12") -> str:
615
- """Crée une zone organisée avec grille pour post-its dans FigJam"""
616
- if not figma_config["file_id"]:
617
- return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
618
-
619
- try:
620
- x_pos, y_pos = float(x), float(y)
621
- w, h = float(width), float(height)
622
- max_notes = int(max_stickies)
623
-
624
- # Calculer la grille optimale
625
- cols = 3 if max_notes <= 9 else 4
626
- rows = (max_notes + cols - 1) // cols # Division avec arrondi vers le haut
627
-
628
- sticky_width = (w - 80) // cols # 80px de marge totale
629
- sticky_height = 120 # Hauteur standard d'un post-it
630
-
631
- comment_text = f"📋 **Zone organisée FigJam :**\n- Titre: \"{title}\"\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px\n- Grille: {cols}×{rows} ({max_notes} post-its max)\n\n📋 **Code Plugin Figma :**\n```javascript\n// Zone de fond\nconst zone = figma.createRectangle()\nzone.x = {x_pos}\nzone.y = {y_pos}\nzone.resize({w}, {h})\nzone.cornerRadius = 12\nzone.fills = [{{ type: 'SOLID', color: {{ r: 0.98, g: 0.98, b: 0.99 }} }}]\nzone.strokes = [{{ type: 'SOLID', color: {{ r: 0.9, g: 0.9, b: 0.92 }} }}]\nzone.strokeWeight = 2\nzone.name = 'Zone - {title}'\n\n// Titre\nconst titleText = figma.createText()\ntitleText.x = {x_pos + 20}\ntitleText.y = {y_pos + 20}\nawait figma.loadFontAsync(titleText.fontName)\ntitleText.characters = '{title}'\ntitleText.fontSize = 20\ntitleText.fontName = {{ family: 'Inter', style: 'Bold' }}\n\n// Grille de post-its exemple\nfor (let row = 0; row < {rows}; row++) {{\n for (let col = 0; col < {cols}; col++) {{\n const index = row * {cols} + col\n if (index >= {max_notes}) break\n \n const sticky = figma.createSticky()\n sticky.x = {x_pos + 20} + col * ({sticky_width} + 10)\n sticky.y = {y_pos + 70} + row * ({sticky_height} + 10)\n sticky.resize({sticky_width}, {sticky_height})\n await figma.loadFontAsync(sticky.text.fontName)\n sticky.text.characters = `Idée ${{index + 1}}`\n sticky.name = `Sticky ${{index + 1}}`\n }}\n}}\n```"
632
-
633
- comment_data = {
634
- "message": comment_text,
635
- "client_meta": {
636
- "x": x_pos,
637
- "y": y_pos
638
- }
639
- }
640
-
641
- result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
642
-
643
- if "error" in result:
644
- return f"❌ Erreur lors de la création du commentaire : {result['error']}"
645
-
646
- return f"✅ Zone organisée \"{title}\" créée avec grille {cols}×{rows} pour {max_notes} post-its"
647
-
648
- except ValueError:
649
- return "❌ Les coordonnées et dimensions doivent être des nombres"
650
-
651
  # === CONFIGURATION DE L'APPLICATION GRADIO ===
652
 
653
  def setup_demo():
@@ -663,6 +33,21 @@ def setup_demo():
663
  def test_user():
664
  return get_figma_user_info()
665
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  with gr.Blocks(
667
  title="🎨 Figma MCP Server",
668
  theme=gr.themes.Soft(),
@@ -721,6 +106,18 @@ def setup_demo():
721
  test_comments_btn = gr.Button("📝 Commentaires")
722
  test_user_btn = gr.Button("👤 Info Utilisateur")
723
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  # Connexions des événements
725
  token_btn.click(
726
  configure_figma_token,
@@ -748,6 +145,31 @@ def setup_demo():
748
  test_user,
749
  outputs=[status_output]
750
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
 
752
  gr.Markdown("""
753
  ---
@@ -762,6 +184,15 @@ def setup_demo():
762
  - `get_figma_comments()` - Récupère les commentaires
763
  - `get_figma_user_info()` - Info utilisateur connecté
764
 
 
 
 
 
 
 
 
 
 
765
  **🎨 Création d'éléments de base (Figma Design) :**
766
  - `create_figma_rectangle(x, y, width, height, name, color)` - Crée un rectangle
767
  - `create_figma_frame(x, y, width, height, name)` - Crée un frame
@@ -794,6 +225,53 @@ def setup_demo():
794
  4. Ajouter des stickers pour les réactions avec `create_figjam_sticker()`
795
 
796
  **✨ Toutes les fonctions génèrent du code JavaScript prêt à utiliser dans un plugin Figma !**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
  """)
798
 
799
  return demo
 
7
  import asyncio
8
  import json
9
  import logging
 
 
10
  from PIL import Image
11
  import base64
12
  import io
13
 
14
+ # Import de tous les outils MCP depuis le dossier tools
15
+ from tools import *
16
+
17
  # Configuration du logging
18
  logging.basicConfig(level=logging.INFO)
19
  logger = logging.getLogger(__name__)
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  # === CONFIGURATION DE L'APPLICATION GRADIO ===
22
 
23
  def setup_demo():
 
33
  def test_user():
34
  return get_figma_user_info()
35
 
36
+ def test_user_detailed():
37
+ return get_figma_user_detailed_info()
38
+
39
+ def test_user_teams():
40
+ return list_figma_user_teams()
41
+
42
+ def test_user_permissions():
43
+ return get_figma_current_user_permissions()
44
+
45
+ def test_workspace_stats():
46
+ return get_figma_workspace_usage_stats()
47
+
48
+ def test_api_limitations():
49
+ return get_figma_api_limitations_info()
50
+
51
  with gr.Blocks(
52
  title="🎨 Figma MCP Server",
53
  theme=gr.themes.Soft(),
 
106
  test_comments_btn = gr.Button("📝 Commentaires")
107
  test_user_btn = gr.Button("👤 Info Utilisateur")
108
 
109
+ # Nouveaux boutons pour les infos utilisateur détaillées
110
+ with gr.Row():
111
+ test_user_detailed_btn = gr.Button("👤 Profil Détaillé")
112
+ test_teams_btn = gr.Button("🏢 Mes Équipes")
113
+ test_permissions_btn = gr.Button("🔐 Permissions")
114
+
115
+ with gr.Row():
116
+ test_stats_btn = gr.Button("📊 Stats Workspace")
117
+
118
+ with gr.Row():
119
+ test_limitations_btn = gr.Button("📚 Limitations API")
120
+
121
  # Connexions des événements
122
  token_btn.click(
123
  configure_figma_token,
 
145
  test_user,
146
  outputs=[status_output]
147
  )
148
+
149
+ test_user_detailed_btn.click(
150
+ test_user_detailed,
151
+ outputs=[status_output]
152
+ )
153
+
154
+ test_teams_btn.click(
155
+ test_user_teams,
156
+ outputs=[status_output]
157
+ )
158
+
159
+ test_permissions_btn.click(
160
+ test_user_permissions,
161
+ outputs=[status_output]
162
+ )
163
+
164
+ test_stats_btn.click(
165
+ test_workspace_stats,
166
+ outputs=[status_output]
167
+ )
168
+
169
+ test_limitations_btn.click(
170
+ test_api_limitations,
171
+ outputs=[status_output]
172
+ )
173
 
174
  gr.Markdown("""
175
  ---
 
184
  - `get_figma_comments()` - Récupère les commentaires
185
  - `get_figma_user_info()` - Info utilisateur connecté
186
 
187
+ **👤 Compte utilisateur et équipe :**
188
+ - `get_figma_user_detailed_info()` - Informations détaillées de l'utilisateur (profil complet)
189
+ - `list_figma_user_teams()` - Liste toutes les équipes de l'utilisateur avec rôles et plans
190
+ - `get_figma_team_info(team_id)` - Infos détaillées d'une équipe (plan, limites, stockage)
191
+ - `get_figma_current_user_permissions()` - Permissions dans le fichier actuel (via Plugin API)
192
+ - `get_figma_workspace_usage_stats()` - Statistiques d'utilisation de l'espace de travail
193
+ - `list_figma_team_projects(team_id)` - Projets d'une équipe
194
+ - `get_figma_api_limitations_info()` - Explique les limitations API Plugin vs REST
195
+
196
  **🎨 Création d'éléments de base (Figma Design) :**
197
  - `create_figma_rectangle(x, y, width, height, name, color)` - Crée un rectangle
198
  - `create_figma_frame(x, y, width, height, name)` - Crée un frame
 
225
  4. Ajouter des stickers pour les réactions avec `create_figjam_sticker()`
226
 
227
  **✨ Toutes les fonctions génèrent du code JavaScript prêt à utiliser dans un plugin Figma !**
228
+
229
+ ---
230
+
231
+ ## 🔍 **Informations du compte utilisateur disponibles :**
232
+
233
+ ### **Via l'API REST Figma :**
234
+ - **Profil utilisateur** : ID, nom d'utilisateur, email, photo de profil
235
+ - **Équipes** : Liste des équipes, rôles, plans d'abonnement
236
+ - **Informations équipe** : Plan (Starter/Pro/Org), limites d'éditeurs, stockage utilisé
237
+ - **Projets** : Accès aux projets de l'équipe, statistiques d'utilisation
238
+
239
+ ### **Via le Plugin API (dans les plugins Figma) :**
240
+ - **Utilisateur actuel** : `figma.currentUser` (nom, email, couleur, session)
241
+ - **Utilisateurs actifs** : `figma.activeUsers` (FigJam uniquement)
242
+ - **Contexte** : Type d'éditeur, mode du plugin, clé du fichier
243
+ - **Permissions** : Accès aux fonctionnalités selon les droits
244
+
245
+ ### **Plans d'abonnement détectables :**
246
+ - **Starter** : 3 pages max, fonctionnalités limitées
247
+ - **Professional** : Pages illimitées, fonctionnalités avancées
248
+ - **Organization** : Gestion d'équipe, contrôles admin
249
+ - **Education** : Fonctionnalités spéciales pour l'éducation
250
+
251
+ **💡 Note :** Certaines informations nécessitent des permissions spéciales ou des plugins privés.
252
+
253
+ ---
254
+
255
+ ## ⚠️ **Limitation importante : Lister les projets**
256
+
257
+ **🚫 L'API Plugin Figma ne peut PAS lister les projets !**
258
+
259
+ Selon la documentation officielle Context7 MCP :
260
+ - L'API Plugin s'exécute uniquement dans le contexte du fichier actuel
261
+ - Aucune fonction `figma.listProjects()` ou équivalent n'existe
262
+ - Pas d'accès aux informations de navigation entre fichiers
263
+
264
+ **✅ Solution : Notre serveur MCP utilise l'API REST**
265
+ - `list_figma_user_teams()` - Via `GET /teams`
266
+ - `list_figma_team_projects(team_id)` - Via `GET /teams/{team_id}/projects`
267
+ - `get_figma_file_info()` - Via `GET /files/{file_id}`
268
+
269
+ **💡 Approche hybride recommandée :**
270
+ 1. **API REST** : Navigation, projets, informations utilisateur
271
+ 2. **Plugin API** : Création/modification dans le fichier actuel
272
+ 3. **Code JavaScript** : Manipulation avancée des objets
273
+
274
+ Utilisez `get_figma_api_limitations_info()` pour plus de détails !
275
  """)
276
 
277
  return demo
tools/__init__.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🎨 Figma MCP Tools - Outils organisés par catégorie
3
+ """
4
+
5
+ # Import de la configuration
6
+ from .config import figma_config, make_figma_request
7
+
8
+ # Import des outils de configuration
9
+ from .config import configure_figma_token, configure_figma_file_id
10
+
11
+ # Import des outils de navigation
12
+ from .navigation import (
13
+ get_figma_file_info,
14
+ get_figma_comments,
15
+ get_figma_user_info
16
+ )
17
+
18
+ # Import des outils de compte utilisateur
19
+ from .user_account import (
20
+ get_figma_user_detailed_info,
21
+ list_figma_user_teams,
22
+ get_figma_team_info,
23
+ get_figma_current_user_permissions,
24
+ get_figma_workspace_usage_stats,
25
+ get_figma_api_limitations_info,
26
+ list_figma_team_projects
27
+ )
28
+
29
+ # Import des outils Figma Design
30
+ from .figma_design import (
31
+ create_figma_rectangle,
32
+ create_figma_frame,
33
+ create_figma_text
34
+ )
35
+
36
+ # Import des outils FigJam
37
+ from .figjam import (
38
+ create_figjam_sticky_note,
39
+ create_figjam_connector_between_elements,
40
+ create_figjam_shape_with_text,
41
+ create_figjam_table,
42
+ create_figjam_code_block,
43
+ create_figjam_background_shape,
44
+ create_figjam_sticker,
45
+ create_figjam_workshop_template,
46
+ create_figjam_organized_zone
47
+ )
48
+
49
+ # Liste de tous les outils disponibles pour l'export
50
+ __all__ = [
51
+ # Configuration
52
+ 'figma_config',
53
+ 'make_figma_request',
54
+ 'configure_figma_token',
55
+ 'configure_figma_file_id',
56
+
57
+ # Navigation
58
+ 'get_figma_file_info',
59
+ 'get_figma_comments',
60
+ 'get_figma_user_info',
61
+
62
+ # Compte utilisateur
63
+ 'get_figma_user_detailed_info',
64
+ 'list_figma_user_teams',
65
+ 'get_figma_team_info',
66
+ 'get_figma_current_user_permissions',
67
+ 'get_figma_workspace_usage_stats',
68
+ 'get_figma_api_limitations_info',
69
+ 'list_figma_team_projects',
70
+
71
+ # Figma Design
72
+ 'create_figma_rectangle',
73
+ 'create_figma_frame',
74
+ 'create_figma_text',
75
+
76
+ # FigJam
77
+ 'create_figjam_sticky_note',
78
+ 'create_figjam_connector_between_elements',
79
+ 'create_figjam_shape_with_text',
80
+ 'create_figjam_table',
81
+ 'create_figjam_code_block',
82
+ 'create_figjam_background_shape',
83
+ 'create_figjam_sticker',
84
+ 'create_figjam_workshop_template',
85
+ 'create_figjam_organized_zone'
86
+ ]
tools/config.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🔧 Configuration et utilitaires pour les outils Figma MCP
3
+ """
4
+ import requests
5
+ import logging
6
+ from typing import Dict, Any
7
+
8
+ # Configuration du logging
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Configuration Figma API
12
+ FIGMA_API_BASE = "https://api.figma.com/v1"
13
+
14
+ # Variables globales pour stocker la configuration
15
+ figma_config = {
16
+ "token": None,
17
+ "file_id": None,
18
+ "team_id": None
19
+ }
20
+
21
+ def make_figma_request(endpoint: str, method: str = "GET", data: Dict = None) -> Dict[str, Any]:
22
+ """Effectue une requête à l'API Figma"""
23
+ if not figma_config["token"]:
24
+ return {"error": "Token Figma non configuré"}
25
+
26
+ headers = {
27
+ "X-Figma-Token": figma_config["token"],
28
+ "Content-Type": "application/json"
29
+ }
30
+
31
+ url = f"{FIGMA_API_BASE}/{endpoint}"
32
+
33
+ try:
34
+ if method == "GET":
35
+ response = requests.get(url, headers=headers, timeout=30)
36
+ elif method == "POST":
37
+ response = requests.post(url, headers=headers, json=data, timeout=30)
38
+ elif method == "PUT":
39
+ response = requests.put(url, headers=headers, json=data, timeout=30)
40
+ elif method == "DELETE":
41
+ response = requests.delete(url, headers=headers, timeout=30)
42
+ else:
43
+ return {"error": f"Méthode HTTP non supportée : {method}"}
44
+
45
+ if response.status_code in [200, 201]:
46
+ return response.json()
47
+ else:
48
+ return {"error": f"Erreur API {response.status_code}: {response.text}"}
49
+
50
+ except Exception as e:
51
+ return {"error": f"Erreur de requête : {str(e)}"}
52
+
53
+ def configure_figma_token(token: str) -> str:
54
+ """Configure le token d'accès Figma"""
55
+ global figma_config
56
+
57
+ if not token or not token.startswith(('figd_', 'figc_')):
58
+ return "❌ Token invalide. Le token doit commencer par 'figd_' ou 'figc_'"
59
+
60
+ figma_config["token"] = token
61
+
62
+ # Test de connexion
63
+ try:
64
+ headers = {"X-Figma-Token": token}
65
+ response = requests.get(f"{FIGMA_API_BASE}/me", headers=headers, timeout=10)
66
+
67
+ if response.status_code == 200:
68
+ user_data = response.json()
69
+ username = user_data.get("handle", "Utilisateur inconnu")
70
+ return f"✅ Token configuré avec succès ! Connecté en tant que : {username}"
71
+ else:
72
+ return f"❌ Erreur lors de la vérification du token : {response.status_code}"
73
+
74
+ except Exception as e:
75
+ return f"❌ Erreur de connexion à l'API Figma : {str(e)}"
76
+
77
+ def configure_figma_file_id(file_id: str) -> str:
78
+ """Configure l'ID du fichier Figma à utiliser"""
79
+ global figma_config
80
+
81
+ if not file_id:
82
+ return "❌ L'ID du fichier est requis"
83
+
84
+ figma_config["file_id"] = file_id
85
+
86
+ # Test d'accès au fichier
87
+ if figma_config["token"]:
88
+ try:
89
+ headers = {"X-Figma-Token": figma_config["token"]}
90
+ response = requests.get(f"{FIGMA_API_BASE}/files/{file_id}", headers=headers, timeout=10)
91
+
92
+ if response.status_code == 200:
93
+ file_data = response.json()
94
+ file_name = file_data.get("name", "Fichier inconnu")
95
+ return f"✅ Fichier configuré avec succès : {file_name}"
96
+ else:
97
+ return f"❌ Impossible d'accéder au fichier : {response.status_code}"
98
+
99
+ except Exception as e:
100
+ return f"❌ Erreur lors de l'accès au fichier : {str(e)}"
101
+ else:
102
+ return "⚠️ ID du fichier configuré, mais token manquant"
tools/figjam.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🟡 Outils de création d'éléments FigJam
3
+ """
4
+ from .config import figma_config, make_figma_request
5
+
6
+ def create_figjam_sticky_note(x: str, y: str, text: str, width: str = "240", height: str = "240") -> str:
7
+ """Crée un post-it (sticky note) dans FigJam avec les dimensions officielles"""
8
+ if not figma_config["file_id"]:
9
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
10
+
11
+ try:
12
+ x_pos, y_pos = float(x), float(y)
13
+ w, h = float(width), float(height)
14
+
15
+ comment_text = f"🟡 **Sticky Note à créer (API FigJam) :**\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px (défaut: 240x240)\n- Texte: \"{text}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst sticky = figma.createSticky()\nsticky.x = {x_pos}\nsticky.y = {y_pos}\nsticky.resize({w}, {h})\nawait figma.loadFontAsync(sticky.text.fontName)\nsticky.text.characters = '{text}'\n```"
16
+
17
+ comment_data = {
18
+ "message": comment_text,
19
+ "client_meta": {
20
+ "x": x_pos,
21
+ "y": y_pos
22
+ }
23
+ }
24
+
25
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
26
+
27
+ if "error" in result:
28
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
29
+
30
+ return f"✅ Instructions Sticky Note créées à ({x_pos}, {y_pos}) - Taille: {w}x{h}px"
31
+
32
+ except ValueError:
33
+ return "❌ Les coordonnées et dimensions doivent être des nombres"
34
+
35
+ def create_figjam_connector_between_elements(element1_name: str, element2_name: str, style: str = "solid") -> str:
36
+ """Crée un connecteur entre deux éléments FigJam (officiel)"""
37
+ if not figma_config["file_id"]:
38
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
39
+
40
+ comment_text = f"🔗 **Connecteur FigJam à créer (API officielle) :**\n- Entre: {element1_name}\n- Et: {element2_name}\n- Style: {style}\n\n📋 **Code Plugin Figma :**\n```javascript\n// Trouver les éléments par nom\nconst element1 = figma.currentPage.findOne(n => n.name === '{element1_name}')\nconst element2 = figma.currentPage.findOne(n => n.name === '{element2_name}')\n\n// Créer le connecteur\nconst connector = figma.createConnector()\nconnector.connectorStart = {{\n endpointNodeId: element1.id,\n magnet: 'AUTO'\n}}\nconnector.connectorEnd = {{\n endpointNodeId: element2.id,\n magnet: 'AUTO'\n}}\n```"
41
+
42
+ comment_data = {
43
+ "message": comment_text,
44
+ "client_meta": {
45
+ "x": 0,
46
+ "y": 0
47
+ }
48
+ }
49
+
50
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
51
+
52
+ if "error" in result:
53
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
54
+
55
+ return f"✅ Instructions connecteur créées entre {element1_name} et {element2_name}"
56
+
57
+ def create_figjam_shape_with_text(x: str, y: str, shape_type: str, text: str, width: str = "208", height: str = "208") -> str:
58
+ """Crée une forme avec texte intégré dans FigJam (API officielle)"""
59
+ if not figma_config["file_id"]:
60
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
61
+
62
+ try:
63
+ x_pos, y_pos = float(x), float(y)
64
+ w, h = float(width), float(height)
65
+
66
+ # Types de formes supportés par l'API Figma
67
+ supported_shapes = {
68
+ "rectangle": "ROUNDED_RECTANGLE",
69
+ "circle": "ELLIPSE",
70
+ "triangle": "TRIANGLE",
71
+ "diamond": "DIAMOND",
72
+ "star": "STAR",
73
+ "hexagon": "HEXAGON"
74
+ }
75
+
76
+ figma_shape = supported_shapes.get(shape_type.lower(), "ROUNDED_RECTANGLE")
77
+
78
+ comment_text = f"🔶 **Forme avec texte FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Type: {shape_type} → {figma_shape}\n- Taille: {w}x{h}px (défaut: 208x208)\n- Texte: \"{text}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst shape = figma.createShapeWithText()\nshape.x = {x_pos}\nshape.y = {y_pos}\nshape.resize({w}, {h})\nshape.shapeType = '{figma_shape}'\nawait figma.loadFontAsync(shape.text.fontName)\nshape.text.characters = '{text}'\n```"
79
+
80
+ comment_data = {
81
+ "message": comment_text,
82
+ "client_meta": {
83
+ "x": x_pos,
84
+ "y": y_pos
85
+ }
86
+ }
87
+
88
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
89
+
90
+ if "error" in result:
91
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
92
+
93
+ return f"✅ Instructions forme {shape_type} avec texte créées à ({x_pos}, {y_pos})"
94
+
95
+ except ValueError:
96
+ return "❌ Les coordonnées et dimensions doivent être des nombres"
97
+
98
+ def create_figjam_table(rows: str, columns: str, x: str = "0", y: str = "0") -> str:
99
+ """Crée un tableau dans FigJam (API officielle)"""
100
+ if not figma_config["file_id"]:
101
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
102
+
103
+ try:
104
+ num_rows, num_cols = int(rows), int(columns)
105
+ x_pos, y_pos = float(x), float(y)
106
+
107
+ comment_text = f"📊 **Tableau FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Dimensions: {num_rows} lignes × {num_cols} colonnes\n\n📋 **Code Plugin Figma :**\n```javascript\nconst table = figma.createTable({num_rows}, {num_cols})\ntable.x = {x_pos}\ntable.y = {y_pos}\n\n// Exemple: ajouter du texte dans les cellules\nawait figma.loadFontAsync(table.cellAt(0, 0).text.fontName)\ntable.cellAt(0, 0).text.characters = 'Titre 1'\ntable.cellAt(0, 1).text.characters = 'Titre 2'\n```"
108
+
109
+ comment_data = {
110
+ "message": comment_text,
111
+ "client_meta": {
112
+ "x": x_pos,
113
+ "y": y_pos
114
+ }
115
+ }
116
+
117
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
118
+
119
+ if "error" in result:
120
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
121
+
122
+ return f"✅ Instructions tableau {num_rows}×{num_cols} créées à ({x_pos}, {y_pos})"
123
+
124
+ except ValueError:
125
+ return "❌ Les dimensions et coordonnées doivent être des nombres"
126
+
127
+ def create_figjam_code_block(x: str, y: str, code: str, language: str = "javascript") -> str:
128
+ """Crée un bloc de code dans FigJam (API officielle)"""
129
+ if not figma_config["file_id"]:
130
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
131
+
132
+ try:
133
+ x_pos, y_pos = float(x), float(y)
134
+
135
+ # Échapper les caractères spéciaux pour l'affichage
136
+ escaped_code = code.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
137
+
138
+ comment_text = f"💻 **Bloc de code FigJam (API officielle) :**\n- Position: ({x_pos}, {y_pos})\n- Langage: {language}\n- Code: \"{code[:100]}{'...' if len(code) > 100 else ''}\"\n\n📋 **Code Plugin Figma :**\n```javascript\nconst codeBlock = figma.createCodeBlock()\ncodeBlock.x = {x_pos}\ncodeBlock.y = {y_pos}\nawait figma.loadFontAsync(codeBlock.codeBlockText.fontName)\ncodeBlock.codeBlockText.characters = \"{escaped_code}\"\n```"
139
+
140
+ comment_data = {
141
+ "message": comment_text,
142
+ "client_meta": {
143
+ "x": x_pos,
144
+ "y": y_pos
145
+ }
146
+ }
147
+
148
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
149
+
150
+ if "error" in result:
151
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
152
+
153
+ return f"✅ Instructions bloc de code {language} créées à ({x_pos}, {y_pos})"
154
+
155
+ except ValueError:
156
+ return "❌ Les coordonnées doivent être des nombres"
157
+
158
+ def create_figjam_background_shape(x: str, y: str, width: str, height: str, color: str = "#F3F4F6", title: str = "", corner_radius: str = "8") -> str:
159
+ """Crée une forme de fond rectangulaire pour organiser le contenu FigJam"""
160
+ if not figma_config["file_id"]:
161
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
162
+
163
+ try:
164
+ x_pos, y_pos = float(x), float(y)
165
+ w, h = float(width), float(height)
166
+ radius = float(corner_radius)
167
+
168
+ comment_text = f"📐 **Forme de fond FigJam (zone de travail) :**\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px\n- Couleur: {color}\n- Titre: \"{title}\"\n- Rayon coins: {radius}px\n\n📋 **Code Plugin Figma :**\n```javascript\n// Créer la forme de fond\nconst background = figma.createRectangle()\nbackground.x = {x_pos}\nbackground.y = {y_pos}\nbackground.resize({w}, {h})\nbackground.cornerRadius = {radius}\nbackground.fills = [{{\n type: 'SOLID',\n color: {{ r: 0.95, g: 0.96, b: 0.97 }} // {color}\n}}]\nbackground.name = 'Zone - {title}'\n\n// Optionnel: ajouter un titre\nif ('{title}') {{\n const titleText = figma.createText()\n titleText.x = {x_pos + 16}\n titleText.y = {y_pos + 16}\n await figma.loadFontAsync(titleText.fontName)\n titleText.characters = '{title}'\n titleText.fontSize = 18\n}}\n```"
169
+
170
+ comment_data = {
171
+ "message": comment_text,
172
+ "client_meta": {
173
+ "x": x_pos,
174
+ "y": y_pos
175
+ }
176
+ }
177
+
178
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
179
+
180
+ if "error" in result:
181
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
182
+
183
+ return f"✅ Zone de travail \"{title}\" créée à ({x_pos}, {y_pos}) - {w}x{h}px"
184
+
185
+ except ValueError:
186
+ return "❌ Les coordonnées et dimensions doivent être des nombres"
187
+
188
+ def create_figjam_sticker(x: str, y: str, sticker_type: str, size: str = "40") -> str:
189
+ """Crée un sticker/emoji dans FigJam pour les réactions et annotations"""
190
+ if not figma_config["file_id"]:
191
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
192
+
193
+ try:
194
+ x_pos, y_pos = float(x), float(y)
195
+ sticker_size = float(size)
196
+
197
+ # Mapping des stickers populaires
198
+ stickers = {
199
+ "thumbs_up": "👍",
200
+ "thumbs_down": "👎",
201
+ "heart": "❤️",
202
+ "star": "⭐",
203
+ "fire": "🔥",
204
+ "rocket": "🚀",
205
+ "bulb": "💡",
206
+ "warning": "⚠️",
207
+ "check": "✅",
208
+ "cross": "❌",
209
+ "question": "❓",
210
+ "idea": "💭",
211
+ "clock": "⏰",
212
+ "money": "💰",
213
+ "target": "🎯",
214
+ "celebrate": "🎉",
215
+ "thinking": "🤔",
216
+ "confused": "😕"
217
+ }
218
+
219
+ emoji = stickers.get(sticker_type.lower(), sticker_type)
220
+
221
+ comment_text = f"🎭 **Sticker FigJam :**\n- Position: ({x_pos}, {y_pos})\n- Type: {sticker_type} → {emoji}\n- Taille: {sticker_size}px\n\n📋 **Code Plugin Figma :**\n```javascript\n// Créer un sticker comme texte stylisé\nconst sticker = figma.createText()\nsticker.x = {x_pos}\nsticker.y = {y_pos}\nsticker.resize({sticker_size}, {sticker_size})\nawait figma.loadFontAsync({{ family: 'Inter', style: 'Regular' }})\nsticker.characters = '{emoji}'\nsticker.fontSize = {sticker_size}\nsticker.textAlignHorizontal = 'CENTER'\nsticker.textAlignVertical = 'CENTER'\nsticker.name = 'Sticker - {sticker_type}'\n```"
222
+
223
+ comment_data = {
224
+ "message": comment_text,
225
+ "client_meta": {
226
+ "x": x_pos,
227
+ "y": y_pos
228
+ }
229
+ }
230
+
231
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
232
+
233
+ if "error" in result:
234
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
235
+
236
+ return f"✅ Sticker {emoji} créé à ({x_pos}, {y_pos}) - Taille: {sticker_size}px"
237
+
238
+ except ValueError:
239
+ return "❌ Les coordonnées et la taille doivent être des nombres"
240
+
241
+ def create_figjam_workshop_template(template_type: str, x: str = "0", y: str = "0") -> str:
242
+ """Crée des templates d'atelier FigJam prêts à utiliser"""
243
+ if not figma_config["file_id"]:
244
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
245
+
246
+ try:
247
+ x_pos, y_pos = float(x), float(y)
248
+
249
+ templates = {
250
+ "retrospective": {
251
+ "title": "Rétrospective Sprint",
252
+ "zones": [
253
+ {"title": "😊 Ce qui s'est bien passé", "x": 0, "y": 100, "width": 300, "height": 400, "color": "#D1FAE5"},
254
+ {"title": "😐 Ce qui peut être amélioré", "x": 320, "y": 100, "width": 300, "height": 400, "color": "#FEF3C7"},
255
+ {"title": "🚀 Actions à prendre", "x": 640, "y": 100, "width": 300, "height": 400, "color": "#DBEAFE"}
256
+ ]
257
+ },
258
+ "brainstorm": {
259
+ "title": "Session Brainstorming",
260
+ "zones": [
261
+ {"title": "🎯 Objectif", "x": 0, "y": 100, "width": 960, "height": 120, "color": "#F3E8FF"},
262
+ {"title": "💡 Idées", "x": 0, "y": 240, "width": 480, "height": 400, "color": "#FEF3C7"},
263
+ {"title": "⭐ Meilleures idées", "x": 500, "y": 240, "width": 460, "height": 400, "color": "#D1FAE5"}
264
+ ]
265
+ },
266
+ "user_journey": {
267
+ "title": "Parcours Utilisateur",
268
+ "zones": [
269
+ {"title": "👤 Persona", "x": 0, "y": 100, "width": 200, "height": 300, "color": "#F3E8FF"},
270
+ {"title": "🎯 Découverte", "x": 220, "y": 100, "width": 180, "height": 300, "color": "#DBEAFE"},
271
+ {"title": "🔍 Exploration", "x": 420, "y": 100, "width": 180, "height": 300, "color": "#FEF3C7"},
272
+ {"title": "✅ Conversion", "x": 620, "y": 100, "width": 180, "height": 300, "color": "#D1FAE5"},
273
+ {"title": "💝 Fidélisation", "x": 820, "y": 100, "width": 180, "height": 300, "color": "#FECACA"}
274
+ ]
275
+ }
276
+ }
277
+
278
+ template = templates.get(template_type.lower())
279
+
280
+ if not template:
281
+ available = ", ".join(templates.keys())
282
+ return f"❌ Template non trouvé. Disponibles: {available}"
283
+
284
+ # Générer le code pour créer le template complet
285
+ zones_code = []
286
+ for i, zone in enumerate(template["zones"]):
287
+ zone_x = x_pos + zone["x"]
288
+ zone_y = y_pos + zone["y"]
289
+ zones_code.append(f"""
290
+ // Zone {i+1}: {zone['title']}
291
+ const zone{i+1} = figma.createRectangle()
292
+ zone{i+1}.x = {zone_x}
293
+ zone{i+1}.y = {zone_y}
294
+ zone{i+1}.resize({zone['width']}, {zone['height']})
295
+ zone{i+1}.cornerRadius = 8
296
+ zone{i+1}.fills = [{{ type: 'SOLID', color: {{ r: 0.95, g: 0.96, b: 0.97 }} }}]
297
+ zone{i+1}.name = '{zone['title']}'
298
+
299
+ const title{i+1} = figma.createText()
300
+ title{i+1}.x = {zone_x + 16}
301
+ title{i+1}.y = {zone_y + 16}
302
+ await figma.loadFontAsync(title{i+1}.fontName)
303
+ title{i+1}.characters = '{zone['title']}'
304
+ title{i+1}.fontSize = 16""")
305
+
306
+ comment_text = f"🏗️ **Template FigJam: {template['title']} :**\n- Position: ({x_pos}, {y_pos})\n- {len(template['zones'])} zones de travail\n\n📋 **Code Plugin Figma :**\n```javascript\n// Template: {template['title']}{''.join(zones_code)}\n```"
307
+
308
+ comment_data = {
309
+ "message": comment_text,
310
+ "client_meta": {
311
+ "x": x_pos,
312
+ "y": y_pos
313
+ }
314
+ }
315
+
316
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
317
+
318
+ if "error" in result:
319
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
320
+
321
+ return f"✅ Template \"{template['title']}\" créé avec {len(template['zones'])} zones à ({x_pos}, {y_pos})"
322
+
323
+ except ValueError:
324
+ return "❌ Les coordonnées doivent être des nombres"
325
+
326
+ def create_figjam_organized_zone(title: str, x: str, y: str, width: str = "400", height: str = "500", max_stickies: str = "12") -> str:
327
+ """Crée une zone organisée avec grille pour post-its dans FigJam"""
328
+ if not figma_config["file_id"]:
329
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
330
+
331
+ try:
332
+ x_pos, y_pos = float(x), float(y)
333
+ w, h = float(width), float(height)
334
+ max_notes = int(max_stickies)
335
+
336
+ # Calculer la grille optimale
337
+ cols = 3 if max_notes <= 9 else 4
338
+ rows = (max_notes + cols - 1) // cols # Division avec arrondi vers le haut
339
+
340
+ sticky_width = (w - 80) // cols # 80px de marge totale
341
+ sticky_height = 120 # Hauteur standard d'un post-it
342
+
343
+ comment_text = f"📋 **Zone organisée FigJam :**\n- Titre: \"{title}\"\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}px\n- Grille: {cols}×{rows} ({max_notes} post-its max)\n\n📋 **Code Plugin Figma :**\n```javascript\n// Zone de fond\nconst zone = figma.createRectangle()\nzone.x = {x_pos}\nzone.y = {y_pos}\nzone.resize({w}, {h})\nzone.cornerRadius = 12\nzone.fills = [{{ type: 'SOLID', color: {{ r: 0.98, g: 0.98, b: 0.99 }} }}]\nzone.strokes = [{{ type: 'SOLID', color: {{ r: 0.9, g: 0.9, b: 0.92 }} }}]\nzone.strokeWeight = 2\nzone.name = 'Zone - {title}'\n\n// Titre\nconst titleText = figma.createText()\ntitleText.x = {x_pos + 20}\ntitleText.y = {y_pos + 20}\nawait figma.loadFontAsync(titleText.fontName)\ntitleText.characters = '{title}'\ntitleText.fontSize = 20\ntitleText.fontName = {{ family: 'Inter', style: 'Bold' }}\n\n// Grille de post-its exemple\nfor (let row = 0; row < {rows}; row++) {{\n for (let col = 0; col < {cols}; col++) {{\n const index = row * {cols} + col\n if (index >= {max_notes}) break\n \n const sticky = figma.createSticky()\n sticky.x = {x_pos + 20} + col * ({sticky_width} + 10)\n sticky.y = {y_pos + 70} + row * ({sticky_height} + 10)\n sticky.resize({sticky_width}, {sticky_height})\n await figma.loadFontAsync(sticky.text.fontName)\n sticky.text.characters = `Idée ${{index + 1}}`\n sticky.name = `Sticky ${{index + 1}}`\n }}\n}}\n```"
344
+
345
+ comment_data = {
346
+ "message": comment_text,
347
+ "client_meta": {
348
+ "x": x_pos,
349
+ "y": y_pos
350
+ }
351
+ }
352
+
353
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
354
+
355
+ if "error" in result:
356
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
357
+
358
+ return f"✅ Zone organisée \"{title}\" créée avec grille {cols}×{rows} pour {max_notes} post-its"
359
+
360
+ except ValueError:
361
+ return "❌ Les coordonnées et dimensions doivent être des nombres"
tools/figma_design.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🎨 Outils de création d'éléments Figma Design
3
+ """
4
+ from .config import figma_config, make_figma_request
5
+
6
+ def create_figma_rectangle(x: str, y: str, width: str, height: str, name: str = "Rectangle", color: str = "#FF0000") -> str:
7
+ """Crée un rectangle dans Figma (via commentaire pour notification)"""
8
+ if not figma_config["file_id"]:
9
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
10
+
11
+ try:
12
+ x_pos, y_pos = float(x), float(y)
13
+ w, h = float(width), float(height)
14
+
15
+ # Créer un commentaire avec les instructions de création
16
+ comment_text = f"🟦 **Rectangle à créer :**\n- Nom: {name}\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}\n- Couleur: {color}"
17
+
18
+ comment_data = {
19
+ "message": comment_text,
20
+ "client_meta": {
21
+ "x": x_pos,
22
+ "y": y_pos
23
+ }
24
+ }
25
+
26
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
27
+
28
+ if "error" in result:
29
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
30
+
31
+ return f"✅ Rectangle {name} créé (via commentaire) à ({x_pos}, {y_pos}) avec la taille {w}x{h}"
32
+
33
+ except ValueError:
34
+ return "❌ Les coordonnées et dimensions doivent être des nombres"
35
+
36
+ def create_figma_frame(x: str, y: str, width: str, height: str, name: str = "Frame") -> str:
37
+ """Crée un frame dans Figma (via commentaire pour notification)"""
38
+ if not figma_config["file_id"]:
39
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
40
+
41
+ try:
42
+ x_pos, y_pos = float(x), float(y)
43
+ w, h = float(width), float(height)
44
+
45
+ comment_text = f"🖼️ **Frame à créer :**\n- Nom: {name}\n- Position: ({x_pos}, {y_pos})\n- Taille: {w}x{h}"
46
+
47
+ comment_data = {
48
+ "message": comment_text,
49
+ "client_meta": {
50
+ "x": x_pos,
51
+ "y": y_pos
52
+ }
53
+ }
54
+
55
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
56
+
57
+ if "error" in result:
58
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
59
+
60
+ return f"✅ Frame {name} créé (via commentaire) à ({x_pos}, {y_pos}) avec la taille {w}x{h}"
61
+
62
+ except ValueError:
63
+ return "❌ Les coordonnées et dimensions doivent être des nombres"
64
+
65
+ def create_figma_text(x: str, y: str, text: str, name: str = "Text", font_size: str = "16") -> str:
66
+ """Crée un élément texte dans Figma (via commentaire pour notification)"""
67
+ if not figma_config["file_id"]:
68
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
69
+
70
+ try:
71
+ x_pos, y_pos = float(x), float(y)
72
+ size = float(font_size)
73
+
74
+ comment_text = f"📝 **Texte à créer :**\n- Nom: {name}\n- Position: ({x_pos}, {y_pos})\n- Texte: \"{text}\"\n- Taille: {size}px"
75
+
76
+ comment_data = {
77
+ "message": comment_text,
78
+ "client_meta": {
79
+ "x": x_pos,
80
+ "y": y_pos
81
+ }
82
+ }
83
+
84
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
85
+
86
+ if "error" in result:
87
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
88
+
89
+ return f"✅ Texte \"{text}\" créé (via commentaire) à ({x_pos}, {y_pos})"
90
+
91
+ except ValueError:
92
+ return "❌ Les coordonnées et la taille doivent être des nombres"
tools/navigation.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🧭 Outils de navigation et d'information sur les fichiers Figma
3
+ """
4
+ import json
5
+ from .config import figma_config, make_figma_request
6
+
7
+ def get_figma_file_info(file_id: str = "") -> str:
8
+ """Récupère les informations d'un fichier Figma"""
9
+ file_id = file_id or figma_config["file_id"]
10
+
11
+ if not file_id:
12
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
13
+
14
+ result = make_figma_request(f"files/{file_id}")
15
+
16
+ if "error" in result:
17
+ return f"❌ Erreur : {result['error']}"
18
+
19
+ file_info = {
20
+ "nom": result.get("name", ""),
21
+ "derniere_modification": result.get("lastModified", ""),
22
+ "version": result.get("version", ""),
23
+ "pages": [page.get("name", "") for page in result.get("document", {}).get("children", [])]
24
+ }
25
+
26
+ return f"📄 **Fichier Figma :**\n{json.dumps(file_info, indent=2, ensure_ascii=False)}"
27
+
28
+ def get_figma_comments(file_id: str = "") -> str:
29
+ """Récupère tous les commentaires d'un fichier Figma"""
30
+ file_id = file_id or figma_config["file_id"]
31
+
32
+ if not file_id:
33
+ return "❌ ID du fichier requis"
34
+
35
+ result = make_figma_request(f"files/{file_id}/comments")
36
+
37
+ if "error" in result:
38
+ return f"❌ Erreur : {result['error']}"
39
+
40
+ comments = result.get("comments", [])
41
+
42
+ if not comments:
43
+ return "📝 Aucun commentaire trouvé dans ce fichier"
44
+
45
+ comment_list = []
46
+ for comment in comments[:10]: # Limiter à 10 commentaires
47
+ user = comment.get("user", {}).get("handle", "Anonyme")
48
+ message = comment.get("message", "")
49
+ created_at = comment.get("created_at", "")
50
+ comment_list.append(f"👤 {user} ({created_at}): {message}")
51
+
52
+ return f"📝 **Commentaires récents :**\n" + "\n\n".join(comment_list)
53
+
54
+ def get_figma_user_info() -> str:
55
+ """Récupère les informations de l'utilisateur connecté"""
56
+ result = make_figma_request("me")
57
+
58
+ if "error" in result:
59
+ return f"❌ Erreur : {result['error']}"
60
+
61
+ user_info = {
62
+ "nom": result.get("handle", ""),
63
+ "email": result.get("email", ""),
64
+ "id": result.get("id", "")
65
+ }
66
+
67
+ return f"👤 **Utilisateur connecté :**\n{json.dumps(user_info, indent=2, ensure_ascii=False)}"
tools/user_account.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 👤 Outils de gestion du compte utilisateur et des équipes Figma
3
+ """
4
+ import json
5
+ from .config import figma_config, make_figma_request
6
+
7
+ def get_figma_user_detailed_info() -> str:
8
+ """Récupère les informations détaillées de l'utilisateur connecté avec plus de données"""
9
+ result = make_figma_request("me")
10
+
11
+ if "error" in result:
12
+ return f"❌ Erreur : {result['error']}"
13
+
14
+ user_info = {
15
+ "id": result.get("id", ""),
16
+ "nom_utilisateur": result.get("handle", ""),
17
+ "email": result.get("email", ""),
18
+ "nom_affichage": result.get("img_url", "").split("/")[-1] if result.get("img_url") else "",
19
+ "url_image": result.get("img_url", ""),
20
+ "date_creation": result.get("created_at", ""),
21
+ "derniere_connexion": result.get("last_activity_at", ""),
22
+ "plan_actuel": "Non spécifié (utiliser get_figma_team_info pour plus de détails)"
23
+ }
24
+
25
+ return f"👤 **Informations détaillées utilisateur :**\n{json.dumps(user_info, indent=2, ensure_ascii=False)}"
26
+
27
+ def get_figma_team_info(team_id: str = "") -> str:
28
+ """Récupère les informations détaillées d'une équipe Figma incluant le plan d'abonnement"""
29
+ team_id = team_id or figma_config.get("team_id", "")
30
+
31
+ if not team_id:
32
+ return "❌ ID de l'équipe requis. Utilisez d'abord list_figma_user_teams() pour trouver votre équipe."
33
+
34
+ result = make_figma_request(f"teams/{team_id}")
35
+
36
+ if "error" in result:
37
+ return f"❌ Erreur : {result['error']}"
38
+
39
+ team_info = {
40
+ "id": result.get("id", ""),
41
+ "nom": result.get("name", ""),
42
+ "plan_abonnement": result.get("billing_plan", "Inconnu"),
43
+ "limite_editeurs": result.get("editor_limit", "Non spécifié"),
44
+ "editeurs_actifs": result.get("active_editors", 0),
45
+ "stockage_utilise": result.get("storage_used", "Non spécifié"),
46
+ "limite_stockage": result.get("storage_limit", "Non spécifié"),
47
+ "fonctionnalites": result.get("features", []),
48
+ "date_creation": result.get("created_at", ""),
49
+ "statut": result.get("status", "")
50
+ }
51
+
52
+ return f"🏢 **Informations équipe :**\n{json.dumps(team_info, indent=2, ensure_ascii=False)}"
53
+
54
+ def list_figma_user_teams() -> str:
55
+ """Liste toutes les équipes auxquelles l'utilisateur appartient"""
56
+ result = make_figma_request("teams")
57
+
58
+ if "error" in result:
59
+ return f"❌ Erreur : {result['error']}"
60
+
61
+ teams = result.get("teams", [])
62
+
63
+ if not teams:
64
+ return "🏢 Aucune équipe trouvée pour cet utilisateur"
65
+
66
+ team_list = []
67
+ for team in teams:
68
+ team_id = team.get("id", "")
69
+ name = team.get("name", "Sans nom")
70
+ role = team.get("role", "Membre")
71
+ plan = team.get("billing_plan", "Plan inconnu")
72
+ team_list.append(f"🏢 **{name}** (ID: {team_id})\n - Rôle: {role}\n - Plan: {plan}")
73
+
74
+ return f"🏢 **Équipes de l'utilisateur :**\n\n" + "\n\n".join(team_list)
75
+
76
+ def get_figma_current_user_permissions() -> str:
77
+ """Récupère les permissions et capacités de l'utilisateur actuel dans un fichier"""
78
+ if not figma_config["file_id"]:
79
+ return "❌ ID du fichier requis. Utilisez configure_figma_file_id() d'abord."
80
+
81
+ comment_text = f"""👥 **Informations utilisateur actuel (Plugin API) :**
82
+
83
+ 📋 **Code Plugin Figma pour récupérer les infos utilisateur :**
84
+ ```javascript
85
+ // Informations utilisateur actuel
86
+ const currentUser = figma.currentUser
87
+ if (currentUser) {{
88
+ console.log('Utilisateur:', {{
89
+ id: currentUser.id,
90
+ nom: currentUser.name,
91
+ email: currentUser.email || 'Non disponible',
92
+ couleur: currentUser.color,
93
+ sessionId: currentUser.sessionId,
94
+ photoUrl: currentUser.photoUrl || 'Pas de photo'
95
+ }})
96
+ }} else {{
97
+ console.log('Aucun utilisateur connecté')
98
+ }}
99
+
100
+ // Type d'éditeur actuel
101
+ console.log('Type éditeur:', figma.editorType) // 'figma' | 'figjam' | 'dev' | 'slides'
102
+
103
+ // Mode du plugin
104
+ console.log('Mode plugin:', figma.mode) // 'default' | 'textreview' | 'inspect' | etc.
105
+
106
+ // Utilisateurs actifs (FigJam uniquement)
107
+ if (figma.editorType === 'figjam' && figma.activeUsers) {{
108
+ console.log('Utilisateurs actifs:', figma.activeUsers.map(user => ({{
109
+ id: user.id,
110
+ nom: user.name,
111
+ couleur: user.color,
112
+ sessionId: user.sessionId
113
+ }})))
114
+ }}
115
+
116
+ // Clé du fichier (plugins privés uniquement)
117
+ if (figma.fileKey) {{
118
+ console.log('Clé fichier:', figma.fileKey)
119
+ }}
120
+ ```"""
121
+
122
+ comment_data = {
123
+ "message": comment_text,
124
+ "client_meta": {"x": 0, "y": 0}
125
+ }
126
+
127
+ result = make_figma_request(f"files/{figma_config['file_id']}/comments", "POST", comment_data)
128
+
129
+ if "error" in result:
130
+ return f"❌ Erreur lors de la création du commentaire : {result['error']}"
131
+
132
+ return f"✅ Code généré pour récupérer les permissions et infos utilisateur"
133
+
134
+ def get_figma_workspace_usage_stats() -> str:
135
+ """Récupère les statistiques d'utilisation de l'espace de travail"""
136
+ if not figma_config.get("team_id"):
137
+ return "❌ ID de l'équipe requis. Utilisez list_figma_user_teams() pour trouver votre équipe puis configurez figma_config['team_id']"
138
+
139
+ # Récupérer les projets de l'équipe
140
+ projects_result = make_figma_request(f"teams/{figma_config['team_id']}/projects")
141
+
142
+ if "error" in projects_result:
143
+ return f"❌ Erreur : {projects_result['error']}"
144
+
145
+ projects = projects_result.get("projects", [])
146
+
147
+ stats = {
148
+ "nombre_projets": len(projects),
149
+ "projets_recents": [],
150
+ "statistiques_utilisation": "Disponible via l'API Team (nécessite permissions admin)"
151
+ }
152
+
153
+ # Récupérer les 5 projets les plus récents
154
+ for project in projects[:5]:
155
+ stats["projets_recents"].append({
156
+ "nom": project.get("name", ""),
157
+ "id": project.get("id", ""),
158
+ "derniere_modification": project.get("modified_at", "")
159
+ })
160
+
161
+ return f"📊 **Statistiques d'utilisation :**\n{json.dumps(stats, indent=2, ensure_ascii=False)}"
162
+
163
+ def get_figma_api_limitations_info() -> str:
164
+ """Explique les limitations de l'API Plugin Figma vs l'API REST pour la gestion des projets"""
165
+
166
+ limitations_info = {
167
+ "api_plugin_limitations": {
168
+ "titre": "🚫 Limitations de l'API Plugin Figma",
169
+ "description": "L'API Plugin Figma ne peut PAS :",
170
+ "limitations": [
171
+ "Lister tous les projets d'un utilisateur",
172
+ "Naviguer entre différents fichiers Figma",
173
+ "Accéder aux informations de facturation/abonnement",
174
+ "Créer ou supprimer des projets",
175
+ "Gérer les permissions d'équipe"
176
+ ],
177
+ "contexte": "Le plugin s'exécute uniquement dans le contexte du fichier actuel"
178
+ },
179
+ "api_rest_capacites": {
180
+ "titre": "✅ Capacités de l'API REST Figma",
181
+ "description": "L'API REST Figma peut :",
182
+ "capacites": [
183
+ "Lister les équipes de l'utilisateur",
184
+ "Lister les projets d'une équipe",
185
+ "Lister les fichiers d'un projet",
186
+ "Accéder aux informations de profil utilisateur",
187
+ "Récupérer les commentaires des fichiers",
188
+ "Exporter des assets (avec limitations)"
189
+ ],
190
+ "note": "Nécessite un token d'accès personnel"
191
+ },
192
+ "recommendation": {
193
+ "titre": "💡 Approche hybride recommandée",
194
+ "description": "Utiliser les deux APIs en complémentarité :",
195
+ "strategies": [
196
+ "API REST : Navigation, gestion des projets, informations utilisateur",
197
+ "API Plugin : Création/modification d'éléments dans le fichier actuel",
198
+ "Plugin avec code JavaScript : Manipulation avancée des objets"
199
+ ]
200
+ },
201
+ "workarounds": {
202
+ "titre": "🔧 Solutions de contournement",
203
+ "alternatives": [
204
+ "Utiliser l'API REST pour lister les projets (comme nous faisons)",
205
+ "Stocker les IDs de fichiers dans le clientStorage du plugin",
206
+ "Utiliser des plugins privés pour accéder à figma.fileKey",
207
+ "Implémenter un système de cache des informations projets"
208
+ ]
209
+ }
210
+ }
211
+
212
+ return f"📚 **Limitations et capacités des APIs Figma :**\n{json.dumps(limitations_info, indent=2, ensure_ascii=False)}"
213
+
214
+ def list_figma_team_projects(team_id: str = "") -> str:
215
+ """Liste les projets d'une équipe Figma"""
216
+ team_id = team_id or figma_config["team_id"]
217
+
218
+ if not team_id:
219
+ return "❌ ID de l'équipe requis. Configurez-le avec figma_config['team_id'] = 'VOTRE_TEAM_ID'"
220
+
221
+ result = make_figma_request(f"teams/{team_id}/projects")
222
+
223
+ if "error" in result:
224
+ return f"❌ Erreur : {result['error']}"
225
+
226
+ projects = result.get("projects", [])
227
+
228
+ if not projects:
229
+ return "📁 Aucun projet trouvé dans cette équipe"
230
+
231
+ project_list = []
232
+ for project in projects[:10]: # Limiter à 10 projets
233
+ name = project.get("name", "Sans nom")
234
+ project_id = project.get("id", "")
235
+ project_list.append(f"📁 {name} (ID: {project_id})")
236
+
237
+ return f"📁 **Projets de l'équipe :**\n" + "\n".join(project_list)