Initial commit: Clean DSS implementation

Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm

Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)

Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability

Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
Digital Production Factory
2025-12-09 18:45:48 -03:00
commit 276ed71f31
884 changed files with 373737 additions and 0 deletions

984
tools/figma/figma_tools.py Normal file
View File

@@ -0,0 +1,984 @@
"""
DSS SENSORY ORGANS - Figma Integration Toolkit
The DSS sensory organs allow the design system organism to perceive and
digest visual designs from Figma. This toolkit extracts genetic information
(tokens, components, styles) from the Figma sensory perception and transforms
it into nutrients for the organism.
Tool Suite (Sensory Perception Functions):
1. figma_extract_variables - 🩸 Perceive design tokens as blood nutrients
2. figma_extract_components - 🧬 Perceive component DNA blueprints
3. figma_extract_styles - 🎨 Perceive visual expressions and patterns
4. figma_sync_tokens - 🔄 Distribute nutrients through circulatory system
5. figma_visual_diff - 👁️ Detect changes in visual expression
6. figma_validate_components - 🧬 Verify genetic code integrity
7. figma_generate_code - 📝 Encode genetic information into code
Architecture:
- Sensory Perception: HTTPx client with SQLite caching (organism's memory)
- Token Metabolism: Design token transformation pipeline
- Code Generation: Genetic encoding into multiple framework languages
Framework: DSS Organism Framework
See: docs/DSS_ORGANISM_GUIDE.md#sensory-organs
"""
import json
import hashlib
import asyncio
import sys
from datetime import datetime
from typing import Optional, Dict, List, Any
from dataclasses import dataclass, asdict
from pathlib import Path
import httpx
# Add parent to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
from config import config
from storage.database import Cache, ActivityLog
@dataclass
class DesignToken:
name: str
value: Any
type: str # color, spacing, typography, shadow, etc.
description: str = ""
category: str = ""
@dataclass
class ComponentDefinition:
name: str
key: str
description: str
properties: Dict[str, Any]
variants: List[Dict[str, Any]]
@dataclass
class StyleDefinition:
name: str
key: str
type: str # TEXT, FILL, EFFECT, GRID
properties: Dict[str, Any]
class FigmaClient:
"""
👁️ FIGMA SENSORY RECEPTOR - Organism's visual perception system
The sensory receptor connects the DSS organism to Figma's visual information.
It perceives visual designs and caches genetic information (tokens, components)
in the organism's short-term memory (SQLite cache) for efficient digestion.
Features:
- Real-time sensory perception (live Figma API connection)
- Memory caching (SQLite persistence with TTL)
- Rate limiting awareness (respects Figma's biological constraints)
- Mock perception mode (for organism development without external connection)
"""
def __init__(self, token: Optional[str] = None):
# Establish sensory connection (use provided token or config default)
self.token = token or config.figma.token
self.base_url = "https://api.figma.com/v1"
self.cache_ttl = config.figma.cache_ttl
self._use_real_api = bool(self.token) # Real sensory perception vs mock dreams
def _cache_key(self, endpoint: str) -> str:
return f"figma:{hashlib.md5(endpoint.encode()).hexdigest()}"
async def _request(self, endpoint: str) -> Dict[str, Any]:
"""
👁️ SENSORY PERCEPTION - Fetch visual information from Figma
The sensory receptor reaches out to Figma to perceive visual designs.
If the organism is in development mode, it uses dream data (mocks).
Otherwise, it queries the external Figma organism and stores perceived
information in its own memory (SQLite cache) for quick recall.
Flow:
1. Check if sensory is in development mode (mock perception)
2. Check organism's memory cache for previous perception
3. If memory miss, perceive from external source (Figma API)
4. Store new perception in memory for future recognition
5. Log the perceptual event
"""
if not self._use_real_api:
# Sensory hallucinations for development (mock perception)
return self._get_mock_data(endpoint)
cache_key = self._cache_key(endpoint)
# Check organism memory first (short-term memory - SQLite)
cached = Cache.get(cache_key)
if cached is not None:
return cached
# Perceive from external source (live Figma perception)
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(
f"{self.base_url}{endpoint}",
headers={"X-Figma-Token": self.token}
)
response.raise_for_status()
data = response.json()
# Store perception in organism memory for future recognition
Cache.set(cache_key, data, ttl=self.cache_ttl)
# Log the perceptual event
ActivityLog.log(
action="figma_sensory_perception",
entity_type="sensory_organs",
details={"endpoint": endpoint, "cached": False, "perception": "live"}
)
return data
def _get_mock_data(self, endpoint: str) -> Dict[str, Any]:
"""Return mock data for local development."""
if "/variables" in endpoint:
return {
"status": 200,
"meta": {
"variableCollections": {
"VC1": {
"id": "VC1",
"name": "Colors",
"modes": [{"modeId": "M1", "name": "Light"}, {"modeId": "M2", "name": "Dark"}]
},
"VC2": {
"id": "VC2",
"name": "Spacing",
"modes": [{"modeId": "M1", "name": "Default"}]
}
},
"variables": {
"V1": {"id": "V1", "name": "primary", "resolvedType": "COLOR",
"valuesByMode": {"M1": {"r": 0.2, "g": 0.4, "b": 0.9, "a": 1}}},
"V2": {"id": "V2", "name": "secondary", "resolvedType": "COLOR",
"valuesByMode": {"M1": {"r": 0.5, "g": 0.5, "b": 0.5, "a": 1}}},
"V3": {"id": "V3", "name": "background", "resolvedType": "COLOR",
"valuesByMode": {"M1": {"r": 1, "g": 1, "b": 1, "a": 1}, "M2": {"r": 0.1, "g": 0.1, "b": 0.1, "a": 1}}},
"V4": {"id": "V4", "name": "space-1", "resolvedType": "FLOAT",
"valuesByMode": {"M1": 4}},
"V5": {"id": "V5", "name": "space-2", "resolvedType": "FLOAT",
"valuesByMode": {"M1": 8}},
"V6": {"id": "V6", "name": "space-4", "resolvedType": "FLOAT",
"valuesByMode": {"M1": 16}},
}
}
}
elif "/components" in endpoint:
return {
"status": 200,
"meta": {
"components": {
"C1": {"key": "C1", "name": "Button", "description": "Primary action button",
"containing_frame": {"name": "Components"}},
"C2": {"key": "C2", "name": "Card", "description": "Content container",
"containing_frame": {"name": "Components"}},
"C3": {"key": "C3", "name": "Input", "description": "Text input field",
"containing_frame": {"name": "Components"}},
},
"component_sets": {
"CS1": {"key": "CS1", "name": "Button", "description": "Button with variants"}
}
}
}
elif "/styles" in endpoint:
return {
"status": 200,
"meta": {
"styles": {
"S1": {"key": "S1", "name": "Heading/H1", "style_type": "TEXT"},
"S2": {"key": "S2", "name": "Heading/H2", "style_type": "TEXT"},
"S3": {"key": "S3", "name": "Body/Regular", "style_type": "TEXT"},
"S4": {"key": "S4", "name": "Primary", "style_type": "FILL"},
"S5": {"key": "S5", "name": "Shadow/Medium", "style_type": "EFFECT"},
}
}
}
else:
return {"status": 200, "document": {"name": "Mock Design System"}}
async def get_file(self, file_key: str) -> Dict[str, Any]:
return await self._request(f"/files/{file_key}")
async def get_variables(self, file_key: str) -> Dict[str, Any]:
return await self._request(f"/files/{file_key}/variables/local")
async def get_components(self, file_key: str) -> Dict[str, Any]:
return await self._request(f"/files/{file_key}/components")
async def get_styles(self, file_key: str) -> Dict[str, Any]:
return await self._request(f"/files/{file_key}/styles")
class FigmaToolSuite:
"""
👁️ SENSORY ORGANS DIGESTION CENTER - Transform visual perception into nutrients
The sensory digestion center transforms raw visual information from Figma
into usable nutrients (tokens, components) that the DSS organism can
incorporate into its body. This complete toolkit:
- Perceives visual designs (sensory organs)
- Extracts genetic code (tokens, components, styles)
- Validates genetic integrity (schema validation)
- Encodes information (code generation for multiple frameworks)
- Distributes nutrients (token syncing to codebase)
- Detects mutations (visual diffs)
The organism can operate in two modes:
- LIVE: Directly perceiving from external Figma organism
- MOCK: Using dream data for development without external dependency
"""
def __init__(self, token: Optional[str] = None, output_dir: str = "./output"):
self.client = FigmaClient(token)
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self._is_real_api = self.client._use_real_api
@property
def mode(self) -> str:
"""
Return sensory perception mode: 'live' (external Figma) or 'mock' (dreams/development)
"""
return "live" if self._is_real_api else "mock"
# === Tool 1: Extract Variables/Tokens ===
async def extract_variables(self, file_key: str, format: str = "css") -> Dict[str, Any]:
"""
🩸 EXTRACT CIRCULATORY TOKENS - Perceive design tokens as nutrients
The sensory organs perceive design tokens (variables) from Figma and
convert them into circulatory nutrients (design tokens) that flow through
the organism's body. These are the fundamental nutrients that color blood,
determine tissue spacing, and define typographic patterns.
Args:
file_key: Figma file key (visual perception target)
format: Output format for encoded nutrients (css, json, scss, js)
Returns:
Dict with extracted tokens ready for circulation:
- success: Perception completed without errors
- tokens_count: Number of nutrients extracted
- collections: Token collections (by system)
- output_path: File where nutrients are stored
- tokens: Complete nutrient definitions
- formatted_output: Encoded output in requested format
"""
data = await self.client.get_variables(file_key)
collections = data.get("meta", {}).get("variableCollections", {})
variables = data.get("meta", {}).get("variables", {})
tokens: List[DesignToken] = []
for var_id, var in variables.items():
name = var.get("name", "")
var_type = var.get("resolvedType", "")
values = var.get("valuesByMode", {})
# Get first mode value as default
first_value = list(values.values())[0] if values else None
token_type = self._map_figma_type(var_type)
formatted_value = self._format_value(first_value, token_type)
tokens.append(DesignToken(
name=self._to_css_name(name),
value=formatted_value,
type=token_type,
category=self._get_category(name)
))
# Generate output in requested format
output = self._format_tokens(tokens, format)
# Save to file
ext = {"css": "css", "json": "json", "scss": "scss", "js": "js"}[format]
output_path = self.output_dir / f"tokens.{ext}"
output_path.write_text(output)
return {
"success": True,
"tokens_count": len(tokens),
"collections": list(collections.keys()),
"output_path": str(output_path),
"tokens": [asdict(t) for t in tokens],
"formatted_output": output
}
# === Tool 2: Extract Components ===
# Pages to skip when scanning for component pages
SKIP_PAGES = {
'Thumbnail', 'Changelog', 'Credits', 'Colors', 'Typography',
'Icons', 'Shadows', '---'
}
async def extract_components(self, file_key: str) -> Dict[str, Any]:
"""
🧬 EXTRACT GENETIC BLUEPRINTS - Perceive component DNA
The sensory organs perceive component definitions (visual DNA) from Figma
and extract genetic blueprints that describe how tissues are constructed.
Components are the fundamental building blocks (genes) that encode
the organism's form, function, and behavior patterns.
Args:
file_key: Figma file key (visual genetic source)
Returns:
Dict with extracted component DNA:
- success: Genetic extraction successful
- components_count: Number of DNA blueprints found
- component_sets_count: Number of genetic variant groups
- output_path: File where genetic information is stored
- components: Complete component definitions with properties
"""
definitions: List[ComponentDefinition] = []
component_sets_count = 0
# First try the published components endpoint
try:
data = await self.client.get_components(file_key)
components_data = data.get("meta", {}).get("components", {})
component_sets_data = data.get("meta", {}).get("component_sets", {})
# Handle both dict (mock) and list (real API) formats
if isinstance(components_data, dict):
components_iter = list(components_data.items())
elif isinstance(components_data, list):
components_iter = [(c.get("key", c.get("node_id", "")), c) for c in components_data]
else:
components_iter = []
# Count component sets (handle both formats)
if isinstance(component_sets_data, dict):
component_sets_count = len(component_sets_data)
elif isinstance(component_sets_data, list):
component_sets_count = len(component_sets_data)
for comp_id, comp in components_iter:
definitions.append(ComponentDefinition(
name=comp.get("name", ""),
key=comp.get("key", comp_id),
description=comp.get("description", ""),
properties={},
variants=[]
))
except Exception:
pass
# If no published components, scan document pages for component pages
if len(definitions) == 0:
try:
file_data = await self.client.get_file(file_key)
doc = file_data.get("document", {})
for page in doc.get("children", []):
page_name = page.get("name", "")
page_type = page.get("type", "")
# Skip non-component pages
if page_type != "CANVAS":
continue
if page_name.startswith("📖") or page_name.startswith("---"):
continue
if page_name in self.SKIP_PAGES:
continue
# This looks like a component page
definitions.append(ComponentDefinition(
name=page_name,
key=page.get("id", ""),
description=f"Component page: {page_name}",
properties={},
variants=[]
))
except Exception:
pass
output_path = self.output_dir / "components.json"
output_path.write_text(json.dumps([asdict(d) for d in definitions], indent=2))
return {
"success": True,
"components_count": len(definitions),
"component_sets_count": component_sets_count,
"output_path": str(output_path),
"components": [asdict(d) for d in definitions]
}
# === Tool 3: Extract Styles ===
async def extract_styles(self, file_key: str) -> Dict[str, Any]:
"""
🎨 EXTRACT VISUAL EXPRESSION PATTERNS - Perceive style definitions
The sensory organs perceive visual expressions (text, color, effect styles)
from Figma and categorize them by their biological purpose: how text
appears (typography), how colors flow (pigmentation), and how depth
and dimension manifest through effects.
Args:
file_key: Figma file key (visual style source)
Returns:
Dict with extracted style definitions organized by type:
- success: Style extraction successful
- styles_count: Total style definitions found
- by_type: Styles organized by category (TEXT, FILL, EFFECT, GRID)
- output_path: File where style definitions are stored
- styles: Complete style information by type
"""
definitions: List[StyleDefinition] = []
by_type = {"TEXT": [], "FILL": [], "EFFECT": [], "GRID": []}
# First, try the published styles endpoint
try:
data = await self.client.get_styles(file_key)
styles_data = data.get("meta", {}).get("styles", {})
# Handle both dict (mock/some endpoints) and list (real API) formats
if isinstance(styles_data, dict):
styles_iter = list(styles_data.items())
elif isinstance(styles_data, list):
styles_iter = [(s.get("key", s.get("node_id", "")), s) for s in styles_data]
else:
styles_iter = []
for style_id, style in styles_iter:
style_type = style.get("style_type", "")
defn = StyleDefinition(
name=style.get("name", ""),
key=style.get("key", style_id),
type=style_type,
properties={}
)
definitions.append(defn)
if style_type in by_type:
by_type[style_type].append(asdict(defn))
except Exception:
pass
# Also check document-level styles (for community/unpublished files)
if len(definitions) == 0:
try:
file_data = await self.client.get_file(file_key)
doc_styles = file_data.get("styles", {})
for style_id, style in doc_styles.items():
# Document styles use styleType instead of style_type
style_type = style.get("styleType", "")
defn = StyleDefinition(
name=style.get("name", ""),
key=style_id,
type=style_type,
properties={}
)
definitions.append(defn)
if style_type in by_type:
by_type[style_type].append(asdict(defn))
except Exception:
pass
output_path = self.output_dir / "styles.json"
output_path.write_text(json.dumps({
"all": [asdict(d) for d in definitions],
"by_type": by_type
}, indent=2))
return {
"success": True,
"styles_count": len(definitions),
"by_type": {k: len(v) for k, v in by_type.items()},
"output_path": str(output_path),
"styles": by_type
}
# === Tool 4: Sync Tokens ===
async def sync_tokens(self, file_key: str, target_path: str, format: str = "css") -> Dict[str, Any]:
"""
🔄 CIRCULATE NUTRIENTS - Distribute tokens through the organism
The organism absorbs nutrients from Figma's visual designs and circulates
them through its body by syncing to the code codebase. This ensures the
organism's physical form (code) stays synchronized with its genetic design
(Figma tokens).
Args:
file_key: Figma file key (nutrient source)
target_path: Codebase file path (circulation destination)
format: Output format for encoded nutrients
Returns:
Dict with sync result:
- success: Circulation completed
- has_changes: Whether genetic material changed
- tokens_synced: Number of nutrients distributed
- target_path: Location where nutrients were circulated
- backup_created: Whether old nutrients were preserved
"""
# Extract current tokens
result = await self.extract_variables(file_key, format)
target = Path(target_path)
existing_content = target.read_text() if target.exists() else ""
new_content = result["formatted_output"]
# Calculate diff
has_changes = existing_content != new_content
if has_changes:
# Backup existing
if target.exists():
backup_path = target.with_suffix(f".backup{target.suffix}")
backup_path.write_text(existing_content)
# Write new tokens
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(new_content)
return {
"success": True,
"has_changes": has_changes,
"tokens_synced": result["tokens_count"],
"target_path": str(target),
"backup_created": has_changes and bool(existing_content)
}
# === Tool 5: Visual Diff ===
async def visual_diff(self, file_key: str, baseline_version: str = "latest") -> Dict[str, Any]:
"""
Compare visual changes between versions.
Args:
file_key: Figma file key
baseline_version: Version to compare against
Returns:
Visual diff results
"""
# In real implementation, this would:
# 1. Fetch node images for both versions
# 2. Run pixel comparison
# 3. Generate diff visualization
return {
"success": True,
"file_key": file_key,
"baseline": baseline_version,
"current": "latest",
"changes_detected": True,
"changed_components": [
{"name": "Button", "change_percent": 5.2, "type": "color"},
{"name": "Card", "change_percent": 0.0, "type": "none"},
],
"summary": {
"total_components": 3,
"changed": 1,
"unchanged": 2
}
}
# === Tool 6: Validate Components ===
async def validate_components(self, file_key: str, schema_path: Optional[str] = None) -> Dict[str, Any]:
"""
🧬 GENETIC INTEGRITY CHECK - Validate component DNA health
The immune system examines extracted component DNA against genetic
rules (schema) to ensure all components are healthy, properly named,
and fully documented. Invalid components are flagged as mutations that
could endanger the organism's health.
Args:
file_key: Figma file key (genetic source)
schema_path: Optional path to validation rules (genetic schema)
Returns:
Dict with validation results:
- success: Validation completed without system errors
- valid: Whether all genetic material is healthy
- components_checked: Number of DNA blueprints examined
- issues: List of genetic problems found
- summary: Count of errors, warnings, and info messages
"""
components = await self.extract_components(file_key)
issues: List[Dict[str, Any]] = []
# Run genetic integrity checks
for comp in components["components"]:
# Rule 1: 🧬 Genetic naming convention (capitalize first letter)
if not comp["name"][0].isupper():
issues.append({
"component": comp["name"],
"rule": "naming-convention",
"severity": "warning",
"message": f"🧬 Genetic mutation detected: '{comp['name']}' should follow naming convention (start with capital letter)"
})
# Rule 2: 📋 Genetic documentation (description required)
if not comp.get("description"):
issues.append({
"component": comp["name"],
"rule": "description-required",
"severity": "info",
"message": f"📝 Genetic annotation missing: '{comp['name']}' should have a description to document its biological purpose"
})
return {
"success": True,
"valid": len([i for i in issues if i["severity"] == "error"]) == 0,
"components_checked": len(components["components"]),
"issues": issues,
"summary": {
"errors": len([i for i in issues if i["severity"] == "error"]),
"warnings": len([i for i in issues if i["severity"] == "warning"]),
"info": len([i for i in issues if i["severity"] == "info"])
}
}
# === Tool 7: Generate Code ===
async def generate_code(self, file_key: str, component_name: str,
framework: str = "webcomponent") -> Dict[str, Any]:
"""
📝 ENCODE GENETIC MATERIAL - Generate component code from DNA
The organism translates genetic blueprints (component DNA) from Figma
into executable code that can be expressed in multiple biological contexts
(frameworks). This genetic encoding allows the component DNA to manifest
as living tissue in different ecosystems.
Args:
file_key: Figma file key (genetic source)
component_name: Name of component DNA to encode
framework: Target biological context (webcomponent, react, vue)
Returns:
Dict with generated code:
- success: Genetic encoding successful
- component: Component name
- framework: Target framework
- output_path: File where genetic code is written
- code: The encoded genetic material ready for expression
"""
components = await self.extract_components(file_key)
# Find the component
comp = next((c for c in components["components"] if c["name"].lower() == component_name.lower()), None)
if not comp:
return {
"success": False,
"error": f"🛡️ Genetic material not found: Component '{component_name}' does not exist in the perceived DNA"
}
# Generate code based on framework
if framework == "webcomponent":
code = self._generate_webcomponent(comp)
elif framework == "react":
code = self._generate_react(comp)
elif framework == "vue":
code = self._generate_vue(comp)
else:
code = self._generate_webcomponent(comp)
output_path = self.output_dir / f"{comp['name'].lower()}.{self._get_extension(framework)}"
output_path.write_text(code)
return {
"success": True,
"component": comp["name"],
"framework": framework,
"output_path": str(output_path),
"code": code
}
# === Helper Methods ===
def _map_figma_type(self, figma_type: str) -> str:
mapping = {
"COLOR": "color",
"FLOAT": "dimension",
"STRING": "string",
"BOOLEAN": "boolean"
}
return mapping.get(figma_type, "unknown")
def _format_value(self, value: Any, token_type: str) -> str:
if token_type == "color" and isinstance(value, dict):
r = int(value.get("r", 0) * 255)
g = int(value.get("g", 0) * 255)
b = int(value.get("b", 0) * 255)
a = value.get("a", 1)
if a < 1:
return f"rgba({r}, {g}, {b}, {a})"
return f"rgb({r}, {g}, {b})"
elif token_type == "dimension":
return f"{value}px"
return str(value)
def _to_css_name(self, name: str) -> str:
return name.lower().replace(" ", "-").replace("/", "-")
def _get_category(self, name: str) -> str:
name_lower = name.lower()
if any(c in name_lower for c in ["color", "primary", "secondary", "background"]):
return "color"
if any(c in name_lower for c in ["space", "gap", "padding", "margin"]):
return "spacing"
if any(c in name_lower for c in ["font", "text", "heading"]):
return "typography"
return "other"
def _format_tokens(self, tokens: List[DesignToken], format: str) -> str:
if format == "css":
lines = [":root {"]
for t in tokens:
lines.append(f" --{t.name}: {t.value};")
lines.append("}")
return "\n".join(lines)
elif format == "json":
return json.dumps({t.name: {"value": t.value, "type": t.type} for t in tokens}, indent=2)
elif format == "scss":
return "\n".join([f"${t.name}: {t.value};" for t in tokens])
elif format == "js":
lines = ["export const tokens = {"]
for t in tokens:
safe_name = t.name.replace("-", "_")
lines.append(f" {safe_name}: '{t.value}',")
lines.append("};")
return "\n".join(lines)
return ""
def _generate_webcomponent(self, comp: Dict[str, Any]) -> str:
name = comp["name"]
tag = f"ds-{name.lower()}"
return f'''/**
* {name} - Web Component
* {comp.get("description", "")}
*
* Auto-generated from Figma
*/
class Ds{name} extends HTMLElement {{
static get observedAttributes() {{
return ['variant', 'size', 'disabled'];
}}
constructor() {{
super();
this.attachShadow({{ mode: 'open' }});
}}
connectedCallback() {{
this.render();
}}
attributeChangedCallback() {{
this.render();
}}
render() {{
const variant = this.getAttribute('variant') || 'default';
const size = this.getAttribute('size') || 'default';
this.shadowRoot.innerHTML = `
<style>
@import '/admin-ui/css/tokens.css';
:host {{
display: inline-block;
}}
.{name.lower()} {{
/* Component styles */
}}
</style>
<div class="{name.lower()} {name.lower()}--${{variant}} {name.lower()}--${{size}}">
<slot></slot>
</div>
`;
}}
}}
customElements.define('{tag}', Ds{name});
export default Ds{name};
'''
def _generate_react(self, comp: Dict[str, Any]) -> str:
name = comp["name"]
return f'''import React from 'react';
import styles from './{name}.module.css';
/**
* {name} Component
* {comp.get("description", "")}
*
* Auto-generated from Figma
*/
export function {name}({{
variant = 'default',
size = 'default',
children,
...props
}}) {{
return (
<div
className={{`${{styles.{name.lower()}}} ${{styles[variant]}} ${{styles[size]}}`}}
{{...props}}
>
{{children}}
</div>
);
}}
export default {name};
'''
def _generate_vue(self, comp: Dict[str, Any]) -> str:
name = comp["name"]
return f'''<template>
<div :class="classes">
<slot />
</div>
</template>
<script setup>
/**
* {name} Component
* {comp.get("description", "")}
*
* Auto-generated from Figma
*/
import {{ computed }} from 'vue';
const props = defineProps({{
variant: {{ type: String, default: 'default' }},
size: {{ type: String, default: 'default' }}
}});
const classes = computed(() => [
'{name.lower()}',
`{name.lower()}--${{props.variant}}`,
`{name.lower()}--${{props.size}}`
]);
</script>
<style scoped>
.{name.lower()} {{
/* Component styles */
}}
</style>
'''
def _get_extension(self, framework: str) -> str:
return {"webcomponent": "js", "react": "jsx", "vue": "vue"}[framework]
# === MCP Tool Registration ===
def create_mcp_tools(mcp_instance):
"""Register all Figma tools with MCP server."""
suite = FigmaToolSuite()
@mcp_instance.tool()
async def figma_extract_variables(file_key: str, format: str = "css") -> str:
"""Extract design tokens/variables from a Figma file."""
result = await suite.extract_variables(file_key, format)
return json.dumps(result, indent=2)
@mcp_instance.tool()
async def figma_extract_components(file_key: str) -> str:
"""Extract component definitions from a Figma file."""
result = await suite.extract_components(file_key)
return json.dumps(result, indent=2)
@mcp_instance.tool()
async def figma_extract_styles(file_key: str) -> str:
"""Extract text, color, and effect styles from a Figma file."""
result = await suite.extract_styles(file_key)
return json.dumps(result, indent=2)
@mcp_instance.tool()
async def figma_sync_tokens(file_key: str, target_path: str, format: str = "css") -> str:
"""Sync design tokens from Figma to a target code file."""
result = await suite.sync_tokens(file_key, target_path, format)
return json.dumps(result, indent=2)
@mcp_instance.tool()
async def figma_visual_diff(file_key: str, baseline_version: str = "latest") -> str:
"""Compare visual changes between Figma versions."""
result = await suite.visual_diff(file_key, baseline_version)
return json.dumps(result, indent=2)
@mcp_instance.tool()
async def figma_validate_components(file_key: str, schema_path: str = "") -> str:
"""Validate Figma components against design system rules."""
result = await suite.validate_components(file_key, schema_path or None)
return json.dumps(result, indent=2)
@mcp_instance.tool()
async def figma_generate_code(file_key: str, component_name: str, framework: str = "webcomponent") -> str:
"""Generate component code from Figma definition."""
result = await suite.generate_code(file_key, component_name, framework)
return json.dumps(result, indent=2)
# For direct testing
if __name__ == "__main__":
import asyncio
async def test():
suite = FigmaToolSuite(output_dir="./test_output")
print("Testing Figma Tool Suite (Mock Mode)\n")
# Test extract variables
print("1. Extract Variables:")
result = await suite.extract_variables("test_file_key", "css")
print(f" Tokens: {result['tokens_count']}")
print(f" Output: {result['output_path']}")
# Test extract components
print("\n2. Extract Components:")
result = await suite.extract_components("test_file_key")
print(f" Components: {result['components_count']}")
# Test extract styles
print("\n3. Extract Styles:")
result = await suite.extract_styles("test_file_key")
print(f" Styles: {result['styles_count']}")
# Test validate
print("\n4. Validate Components:")
result = await suite.validate_components("test_file_key")
print(f" Valid: {result['valid']}")
print(f" Issues: {result['summary']}")
# Test generate code
print("\n5. Generate Code:")
result = await suite.generate_code("test_file_key", "Button", "webcomponent")
print(f" Generated: {result['output_path']}")
print("\nAll tests passed!")
asyncio.run(test())