diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 242f863..ccfcec2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -16,6 +16,12 @@ "source": "./plugins/dead-code", "description": "Find and remove unused code added in the current branch", "version": "1.0.0" + }, + { + "name": "figma-icon-sync", + "source": "./plugins/figma-icon-sync", + "description": "Sync icons from Figma design system to your codebase using Figma MCP", + "version": "1.0.0" } ] } diff --git a/README.md b/README.md index 5b9b146..4dba5b8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ Claude Code plugins for code quality and development workflows. # Install the dead-code plugin /plugin install dead-code@fsai + +# Install the figma-icon-sync plugin +/plugin install figma-icon-sync@fsai ``` ## Plugins @@ -37,3 +40,21 @@ Remove dead code from a branch. Claude will diff against master, identify dead code, and remove it. +### figma-icon-sync + +Sync icons from Figma design system to your codebase using Figma MCP. + +**Prerequisites:** Requires Figma MCP to be connected. Add it with: +```bash +claude mcp add --transport http figma https://mcp.figma.com/mcp +``` +Then authenticate via `/mcp` > figma > Authenticate. + +**Usage:** Just ask Claude Code: +- "Sync icons from Figma" +- "Download home and settings icons from the design system" +- "Update icons from Figma" +- "Check available icons in Figma" + +Claude will browse your Figma file, extract SVG icons with proper naming conventions (PascalCase, Outlined suffix), and add them to your icons package. + diff --git a/plugins/figma-icon-sync/.claude-plugin/plugin.json b/plugins/figma-icon-sync/.claude-plugin/plugin.json new file mode 100644 index 0000000..bcff55d --- /dev/null +++ b/plugins/figma-icon-sync/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "figma-icon-sync", + "version": "1.0.0", + "description": "Sync icons from Figma design system to your codebase using Figma MCP", + "author": { + "name": "bill0x2a", + "email": "bill@franchisesystems.ai" + }, + "license": "MIT" +} diff --git a/plugins/figma-icon-sync/references/figma-mcp-icons.md b/plugins/figma-icon-sync/references/figma-mcp-icons.md new file mode 100644 index 0000000..c3b04bc --- /dev/null +++ b/plugins/figma-icon-sync/references/figma-mcp-icons.md @@ -0,0 +1,156 @@ +# Figma MCP Icon Extraction Reference + +## Available MCP Tools for Icon Work + +### get_metadata +Returns sparse XML representation of layers. Best for navigating large files. + +**Use for:** +- Initial file structure exploration +- Finding icon sections and categories +- Getting node IDs for specific icons + +**Example prompt:** +``` +Use get_metadata to explore the structure of this Figma file: +https://www.figma.com/design/xe1fVdPmJnvHPMKoE4Jasm/central-icon-system +``` + +### get_design_context +Returns full design context including code generation. Use for actual icon extraction. + +**Use for:** +- Extracting SVG code for specific icons +- Getting exact styling and paths + +**Example prompt:** +``` +Use get_design_context to extract the icon at node-id 123:456 as SVG code. +Output clean SVG with viewBox="0 0 24 24", no transforms or metadata. +``` + +### get_screenshot +Takes a screenshot of selection. Useful for visual verification. + +**Use for:** +- Verifying correct icon selection +- Visual comparison during sync + +## File Key Extraction + +The file key is the alphanumeric string in the Figma URL between `/design/` and the next `/`: + +``` +https://www.figma.com/design/xe1fVdPmJnvHPMKoE4Jasm/central-icon-system + ^^^^^^^^^^^^^^^^^^^^^^^^ + This is the file key +``` + +## Node ID Format + +Figma node IDs follow the pattern: `123:456` or `123-456` + +When referencing in URLs, they appear as query parameters: +``` +https://www.figma.com/design/FILE_KEY/name?node-id=123-456 +``` + +## Icon Extraction Strategy + +### For Small Icon Libraries (< 50 icons) + +1. Use `get_metadata` once to get full structure +2. Identify all icon nodes +3. Extract each with `get_design_context` + +### For Large Icon Libraries (> 50 icons) + +1. Use `get_metadata` to get page/section structure +2. Identify relevant sections based on user needs +3. Drill down into specific sections +4. Extract only needed icons to avoid rate limits + +### For Icon Updates + +1. Identify icons currently used in codebase +2. Map to Figma node IDs +3. Extract only changed or missing icons +4. Compare SVG content for actual changes + +## SVG Cleanup Requirements + +Icons from Figma typically need cleanup: + +| Issue | Solution | +|-------|----------| +| Non-24x24 dimensions | Normalize viewBox and size | +| Figma metadata | Remove data-* attributes | +| XML declarations | Remove | +| Comments | Remove all HTML comments | +| Empty groups | Remove elements | +| IDs | Remove id attributes | + +## Common Icon Naming Patterns in Design Systems + +| Pattern | Example | Normalized | +|---------|---------|------------| +| kebab-case | `arrow-left` | `ArrowLeft.svg` | +| With size | `24/arrow-left` | `ArrowLeft.svg` | +| With variant | `arrow-left-outlined` | `ArrowLeftOutlined.svg` | +| With prefix | `icon-arrow-left` | `ArrowLeft.svg` | +| Numbered | `01-arrow-left` | `ArrowLeft.svg` | + +## Rate Limits + +Figma API rate limits apply to MCP tool calls: + +- **Free accounts:** 6 tool calls per month +- **Paid accounts:** Per-minute limits (Tier 1 REST API limits) + +### Strategies to Minimize API Calls + +1. Use `get_metadata` once, save locally +2. Batch icon extractions when possible +3. Cache node IDs for repeat syncs +4. Only sync changed icons + +## Error Handling + +### "Too many requests" +Wait and retry, or reduce batch size + +### "Node not found" +Verify node ID format, check if icon was moved/deleted + +### "File access denied" +Ensure Figma MCP is authenticated with proper permissions + +### Large response truncation +Use `get_metadata` first to get structure, then extract individual nodes + +## Integration with Project Build + +After syncing icons, typical build steps: + +1. **Verify icons added:** + ```bash + git diff --name-only packages/icons/ + ``` + +2. **Build icons package:** + ```bash + # Check package.json for exact command + pnpm build:icons + # or + pnpm --filter @company/icons build + ``` + +3. **Verify build output:** + ```bash + ls packages/icons/dist/ + ``` + +4. **Test imports:** + ```typescript + import { ArrowLeft, ArrowLeftOutlined } from '@company/icons'; + ``` diff --git a/plugins/figma-icon-sync/scripts/browse_icons.py b/plugins/figma-icon-sync/scripts/browse_icons.py new file mode 100644 index 0000000..1f71b39 --- /dev/null +++ b/plugins/figma-icon-sync/scripts/browse_icons.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +""" +Figma Icon Browser + +Parses Figma metadata output to help navigate and search for icons. +Useful for processing large icon design systems. + +Usage: + python browse_icons.py --list + python browse_icons.py --search "home" + python browse_icons.py --section "Navigation" + +Requires Python 3.7+ +""" + +import re +import sys +import json +import argparse +from pathlib import Path +from typing import List, Optional, Dict, Any +from dataclasses import dataclass + + +@dataclass +class IconInfo: + """Information about a single icon.""" + name: str + node_id: str + section: str + is_outlined: bool + path: str # Full path in the Figma structure + + +def parse_figma_metadata(content: str) -> List[IconInfo]: + """ + Parse Figma metadata to extract icon information. + + Supports both XML and JSON formats from get_metadata tool. + + Args: + content: Raw metadata string from Figma MCP + + Returns: + List of IconInfo objects + """ + icons = [] # type: List[IconInfo] + + # Try parsing as JSON first + try: + data = json.loads(content) + icons = parse_json_metadata(data) + except json.JSONDecodeError: + # Fall back to XML parsing + icons = parse_xml_metadata(content) + + return icons + + +def parse_json_metadata( + data: Any, + path: str = "", + section: str = "" +) -> List[IconInfo]: + """Parse JSON format metadata.""" + icons = [] # type: List[IconInfo] + + if isinstance(data, dict): + name = data.get('name', '') + node_type = data.get('type', '') + node_id = data.get('id', '') + + # Update section based on frame/page names + current_section = section + if node_type in ['PAGE', 'FRAME', 'SECTION']: + current_section = name + + current_path = f"{path}/{name}" if path else name + + # Check if this looks like an icon + if is_icon_node(name, node_type): + is_outlined = check_outlined_variant(name) + icons.append(IconInfo( + name=name, + node_id=node_id, + section=current_section, + is_outlined=is_outlined, + path=current_path + )) + + # Recurse into children + children = data.get('children', []) + for child in children: + icons.extend(parse_json_metadata(child, current_path, current_section)) + + return icons + + +def parse_xml_metadata(content: str) -> List[IconInfo]: + """Parse XML format metadata.""" + icons = [] # type: List[IconInfo] + + # Simple regex-based parsing for XML + # Pattern to match icon-like elements + node_pattern = re.compile( + r'<(\w+)[^>]*\s+name="([^"]+)"[^>]*\s+id="([^"]+)"', + re.IGNORECASE + ) + + current_section = "" + + for match in node_pattern.finditer(content): + node_type = match.group(1) + name = match.group(2) + node_id = match.group(3) + + # Track sections + if node_type.upper() in ['FRAME', 'PAGE', 'SECTION']: + current_section = name + continue + + # Check if this looks like an icon + if is_icon_node(name, node_type): + is_outlined = check_outlined_variant(name) + icons.append(IconInfo( + name=name, + node_id=node_id, + section=current_section, + is_outlined=is_outlined, + path=f"{current_section}/{name}" + )) + + return icons + + +def is_icon_node(name: str, node_type: str) -> bool: + """Determine if a node is likely an icon.""" + # Skip container types + if node_type.upper() in ['PAGE', 'DOCUMENT', 'SECTION']: + return False + + # Common icon patterns + icon_patterns = [ + r'^icon[-_]', # Prefix: icon-name + r'[-_]icon$', # Suffix: name-icon + r'^ic[-_]', # Prefix: ic-name + r'[-_]ic$', # Suffix: name-ic + r'^\d+[-_]', # Numbered: 24-home + ] + + # Check against patterns + lower_name = name.lower() + for pattern in icon_patterns: + if re.search(pattern, lower_name): + return True + + # If the node is a COMPONENT or COMPONENT_SET, it's likely an icon + if node_type.upper() in ['COMPONENT', 'COMPONENT_SET', 'INSTANCE']: + # Additional check: exclude obviously non-icon components + exclude_patterns = ['button', 'input', 'card', 'modal', 'dialog', 'form'] + if not any(p in lower_name for p in exclude_patterns): + return True + + return False + + +def check_outlined_variant(name: str) -> bool: + """Check if an icon name indicates an outlined variant.""" + outlined_patterns = [ + r'[-_\s]outlined?$', + r'[-_\s]outline$', + r'[-_\s]line$', + r'[-_\s]stroke$', + r'^outline[-_\s]', + ] + + lower_name = name.lower() + return any(re.search(p, lower_name) for p in outlined_patterns) + + +def search_icons(icons: List[IconInfo], query: str) -> List[IconInfo]: + """Search icons by name.""" + query_lower = query.lower() + return [ + icon for icon in icons + if query_lower in icon.name.lower() + ] + + +def filter_by_section(icons: List[IconInfo], section: str) -> List[IconInfo]: + """Filter icons by section name.""" + section_lower = section.lower() + return [ + icon for icon in icons + if section_lower in icon.section.lower() + ] + + +def list_sections(icons: List[IconInfo]) -> List[str]: + """Get unique sections from icons.""" + return sorted(set(icon.section for icon in icons if icon.section)) + + +def format_icon_list(icons: List[IconInfo], verbose: bool = False) -> str: + """Format icon list for display.""" + lines = [] # type: List[str] + + if verbose: + for icon in icons: + variant = "outlined" if icon.is_outlined else "filled" + lines.append(f" {icon.name}") + lines.append(f" Node ID: {icon.node_id}") + lines.append(f" Section: {icon.section}") + lines.append(f" Variant: {variant}") + lines.append("") + else: + # Group by section + by_section = {} # type: Dict[str, List[IconInfo]] + for icon in icons: + section = icon.section or "Uncategorized" + if section not in by_section: + by_section[section] = [] + by_section[section].append(icon) + + for section, section_icons in sorted(by_section.items()): + lines.append(f"\n## {section}") + for icon in sorted(section_icons, key=lambda x: x.name): + variant = " (outlined)" if icon.is_outlined else "" + lines.append(f" - {icon.name}{variant}") + + return "\n".join(lines) + + +def main() -> int: + """Main entry point.""" + parser = argparse.ArgumentParser( + description='Browse and search Figma icon metadata' + ) + + parser.add_argument( + 'metadata_file', + help='File containing Figma metadata output' + ) + parser.add_argument( + '--list', '-l', + action='store_true', + help='List all icons' + ) + parser.add_argument( + '--search', '-s', + type=str, + help='Search for icons by name' + ) + parser.add_argument( + '--section', + type=str, + help='Filter by section name' + ) + parser.add_argument( + '--sections', + action='store_true', + help='List all sections' + ) + parser.add_argument( + '--verbose', '-v', + action='store_true', + help='Show detailed information' + ) + parser.add_argument( + '--json', + action='store_true', + help='Output as JSON' + ) + + args = parser.parse_args() + + # Validate metadata file exists + metadata_path = Path(args.metadata_file) + if not metadata_path.exists(): + print(f"Error: Metadata file not found: {args.metadata_file}") + return 1 + + if not metadata_path.is_file(): + print(f"Error: Path is not a file: {args.metadata_file}") + return 1 + + # Read metadata file with error handling + try: + content = metadata_path.read_text(encoding='utf-8') + except UnicodeDecodeError: + print(f"Error: Failed to read file (encoding error): {args.metadata_file}") + return 1 + except OSError as e: + print(f"Error: Failed to read file: {e}") + return 1 + + if not content.strip(): + print("Error: Metadata file is empty") + return 1 + + icons = parse_figma_metadata(content) + + print(f"Found {len(icons)} icons in metadata\n") + + # Apply filters + if args.search: + icons = search_icons(icons, args.search) + print(f"Search results for '{args.search}': {len(icons)} icons\n") + + if args.section: + icons = filter_by_section(icons, args.section) + print(f"Icons in section '{args.section}': {len(icons)}\n") + + # Output + if args.sections: + sections = list_sections(icons) + print("Sections:") + for section in sections: + print(f" - {section}") + elif args.json: + output = [ + { + 'name': icon.name, + 'node_id': icon.node_id, + 'section': icon.section, + 'is_outlined': icon.is_outlined, + 'path': icon.path + } + for icon in icons + ] + print(json.dumps(output, indent=2)) + elif args.list or args.search or args.section: + print(format_icon_list(icons, args.verbose)) + else: + # Default: show summary + sections = list_sections(icons) + print("Summary:") + print(f" Total icons: {len(icons)}") + print(f" Filled: {len([i for i in icons if not i.is_outlined])}") + print(f" Outlined: {len([i for i in icons if i.is_outlined])}") + print(f" Sections: {len(sections)}") + print("\nUse --list to see all icons, --search to find specific icons") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/plugins/figma-icon-sync/scripts/optimize_svg.py b/plugins/figma-icon-sync/scripts/optimize_svg.py new file mode 100644 index 0000000..9e49a3c --- /dev/null +++ b/plugins/figma-icon-sync/scripts/optimize_svg.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +""" +SVG Icon Optimizer + +Optimizes SVG icons downloaded from Figma to ensure: +- Consistent 24x24 dimensions +- Clean, minimal SVG code +- Proper naming conventions + +Usage: + python optimize_svg.py [--outlined] + python optimize_svg.py --batch + +Requires Python 3.7+ +""" + +import re +import sys +import argparse +from pathlib import Path +from typing import List, Optional, Tuple + + +def optimize_svg(svg_content: str) -> str: + """ + Clean and optimize SVG content for icon usage. + + Args: + svg_content: Raw SVG string from Figma + + Returns: + Optimized SVG string with 24x24 dimensions + """ + # Ensure viewBox is 0 0 24 24 + if 'viewBox' in svg_content: + svg_content = re.sub( + r'viewBox="[^"]*"', + 'viewBox="0 0 24 24"', + svg_content + ) + else: + svg_content = svg_content.replace(']*\?>\s*', '', svg_content) + + # Remove DOCTYPE if present + svg_content = re.sub(r']*>\s*', '', svg_content) + + # Remove comments + svg_content = re.sub(r'', '', svg_content, flags=re.DOTALL) + + # Remove empty groups + svg_content = re.sub(r']*>\s*', '', svg_content) + + # Remove Figma namespace declarations + svg_content = re.sub(r'\s*xmlns:figma="[^"]*"', '', svg_content) + + # Remove id attributes (often Figma-generated) + svg_content = re.sub(r'\s*id="[^"]*"', '', svg_content) + + # Clean up excessive whitespace + svg_content = re.sub(r'\n\s*\n', '\n', svg_content) + svg_content = re.sub(r'>\s+<', '><', svg_content) + + # Add proper formatting + svg_content = svg_content.strip() + + # Ensure fill="currentColor" for icon flexibility (if no fill specified) + if 'fill=' not in svg_content: + svg_content = svg_content.replace(' str: + """ + Convert any naming convention to PascalCase. + + Args: + name: Icon name in any format (kebab-case, snake_case, etc.) + + Returns: + PascalCase version of the name + """ + # Remove file extension if present + name = re.sub(r'\.(svg|png|jpg)$', '', name, flags=re.IGNORECASE) + + # Handle kebab-case, snake_case, and space-separated + words = re.split(r'[-_\s]+', name) + + # Handle camelCase - split on uppercase letters + expanded_words = [] + for word in words: + # Split camelCase while preserving acronyms + split_word = re.sub(r'([a-z])([A-Z])', r'\1 \2', word) + expanded_words.extend(split_word.split()) + + return ''.join(word.capitalize() for word in expanded_words if word) + + +def determine_variant(name: str) -> Tuple[str, bool]: + """ + Determine if an icon is outlined and extract base name. + + Args: + name: Full icon name + + Returns: + Tuple of (base_name, is_outlined) + """ + # Common patterns for outlined icons + outlined_patterns = [ + r'[-_\s]?outlined?$', + r'[-_\s]?outline$', + r'[-_\s]?line$', + r'[-_\s]?stroke$', + ] + + lower_name = name.lower() + is_outlined = False + base_name = name + + for pattern in outlined_patterns: + if re.search(pattern, lower_name, re.IGNORECASE): + base_name = re.sub(pattern, '', name, flags=re.IGNORECASE) + is_outlined = True + break + + return base_name.strip('-_'), is_outlined + + +def process_icon_file( + input_path: str, + output_dir: str, + force_outlined: Optional[bool] = None +) -> str: + """ + Process a single icon file. + + Args: + input_path: Path to input SVG file + output_dir: Directory for output file + force_outlined: Override variant detection (None = auto-detect) + + Returns: + Path to created file + + Raises: + FileNotFoundError: If input file doesn't exist + ValueError: If file is not a valid SVG + IOError: If file cannot be read or written + """ + path = Path(input_path) + + # Validate input file exists + if not path.exists(): + raise FileNotFoundError(f"Input file not found: {input_path}") + + if not path.is_file(): + raise ValueError(f"Input path is not a file: {input_path}") + + # Read file with error handling + try: + content = path.read_text(encoding='utf-8') + except UnicodeDecodeError as e: + raise IOError(f"Failed to read file (encoding error): {input_path}") from e + + # Basic SVG validation + if ' List[str]: + """ + Process all SVG files in a directory. + + Args: + input_dir: Directory containing SVG files + output_dir: Directory for output files + + Returns: + List of created file paths + """ + input_path = Path(input_dir) + + # Validate input directory + if not input_path.exists(): + print(f"Error: Input directory not found: {input_dir}") + return [] + + if not input_path.is_dir(): + print(f"Error: Input path is not a directory: {input_dir}") + return [] + + created_files = [] + error_count = 0 + + for svg_file in input_path.glob('**/*.svg'): + try: + result = process_icon_file(str(svg_file), output_dir) + created_files.append(result) + print(f"Processed: {svg_file.name} -> {Path(result).name}") + except FileNotFoundError as e: + print(f"Error (not found): {svg_file.name}: {e}") + error_count += 1 + except ValueError as e: + print(f"Error (invalid): {svg_file.name}: {e}") + error_count += 1 + except IOError as e: + print(f"Error (IO): {svg_file.name}: {e}") + error_count += 1 + + if error_count > 0: + print(f"\nCompleted with {error_count} error(s)") + + return created_files + + +def main() -> int: + """Main entry point.""" + parser = argparse.ArgumentParser( + description='Optimize SVG icons from Figma for codebase usage' + ) + + parser.add_argument( + 'input', + help='Input SVG file or directory (with --batch)' + ) + parser.add_argument( + 'output_dir', + help='Output directory for processed icons' + ) + parser.add_argument( + '--outlined', + action='store_true', + help='Force icon to be treated as outlined variant' + ) + parser.add_argument( + '--filled', + action='store_true', + help='Force icon to be treated as filled variant' + ) + parser.add_argument( + '--batch', + action='store_true', + help='Process all SVG files in input directory' + ) + + args = parser.parse_args() + + # Determine variant override + force_outlined = None + if args.outlined: + force_outlined = True + elif args.filled: + force_outlined = False + + try: + if args.batch: + results = process_batch(args.input, args.output_dir) + print(f"\nProcessed {len(results)} icons") + else: + result = process_icon_file(args.input, args.output_dir, force_outlined) + print(f"Created: {result}") + return 0 + except (FileNotFoundError, ValueError, IOError) as e: + print(f"Error: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/plugins/figma-icon-sync/skills/figma-icon-sync/SKILL.md b/plugins/figma-icon-sync/skills/figma-icon-sync/SKILL.md new file mode 100644 index 0000000..5294d38 --- /dev/null +++ b/plugins/figma-icon-sync/skills/figma-icon-sync/SKILL.md @@ -0,0 +1,122 @@ +--- +name: figma-icon-sync +description: Sync icons from Figma design system to your codebase. Use when asked to sync, download, or update icons from Figma, or to browse a Figma icon library. Triggers on phrases like "sync icons from Figma", "download icons from design system", "get icons from Figma", or "update icons from Figma". +--- + +# Figma Icon Sync + +Sync icons from a Figma design system to your codebase. Connects to Figma MCP to browse, search, and download icons as optimized SVG files. + +## Prerequisites + +Ensure Figma MCP is connected. Check with `/mcp`. If not connected: +```bash +claude mcp add --transport http figma https://mcp.figma.com/mcp +``` +Then authenticate via `/mcp` > figma > Authenticate. + +## Example Figma File + +Default reference file: https://www.figma.com/design/xe1fVdPmJnvHPMKoE4Jasm/central-icon-system +File Key: `xe1fVdPmJnvHPMKoE4Jasm` + +Users should provide their own Figma file URL for their design system. + +## Workflow + +1. **Verify MCP connection** via `/mcp` - figma server must be connected +2. **Browse file structure** using `get_metadata` to see pages/sections +3. **Find required icons** in codebase: + ```bash + grep -r "Icon" --include="*.tsx" --include="*.ts" . + ``` +4. **Extract icons** using `get_design_context` with SVG output +5. **Optimize and save** with correct naming conventions +6. **Build icons package** using project build commands +7. **Report** synced icons summary + +## Naming Conventions + +| Type | Naming | Example | +|------|--------|---------| +| Filled (default) | `IconName.svg` | `Home.svg` | +| Outlined | `IconNameOutlined.svg` | `HomeOutlined.svg` | + +**Transformations:** +- `arrow-left` → `ArrowLeft.svg` +- `arrow_left` → `ArrowLeft.svg` +- `arrow-left-outlined` → `ArrowLeftOutlined.svg` + +## SVG Requirements + +When extracting via `get_design_context`: +- viewBox="0 0 24 24" +- width="24" height="24" +- Clean paths, no transforms +- No Figma metadata or comments + +## MCP Tools + +| Tool | Use For | +|------|---------| +| `get_metadata` | Structure exploration, finding node IDs | +| `get_design_context` | Extracting SVG code | +| `get_screenshot` | Visual verification | + +## Icon Locations + +Common project locations: +- `packages/icons/` +- `libs/icons/` +- `src/icons/` + +Check project structure and follow existing conventions. + +## Large File Navigation + +For files with many icons: +1. Get structure first with `get_metadata` +2. Note section names containing icons +3. Request specific sections +4. Download only needed icons to minimize API calls + +## Error Handling + +| Error | Solution | +|-------|----------| +| MCP not connected | Authenticate via `/mcp` | +| Rate limits | Batch requests, add delays | +| Large files | Use `get_metadata` first | +| Missing icons | Check naming, search alternatives | + +## Example Session + +``` +User: "Sync home and settings icons from Figma" + +Steps: +1. Verify MCP connection +2. get_metadata → browse file structure +3. Find "home" and "settings" icons +4. For each: get filled + outlined variants +5. Download as SVG via get_design_context +6. Optimize to 24x24 +7. Save as Home.svg, HomeOutlined.svg, etc. +8. Add to icons package +9. Run build command +10. Report synced icons +``` + +## Helper Scripts + +This plugin includes Python scripts for batch processing: + +- `scripts/optimize_svg.py` - Optimize SVGs (dimensions, cleanup) +- `scripts/browse_icons.py` - Parse and search Figma metadata + +Usage: +```bash +python scripts/optimize_svg.py input.svg output_dir/ +python scripts/optimize_svg.py --batch input_dir/ output_dir/ +python scripts/browse_icons.py metadata.json --search "home" +```