minitap-mcp 0.4.3__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/PKG-INFO +2 -2
  2. minitap_mcp-0.4.3/minitap/mcp/core/agents/compare_screenshots.py → minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/agent.py +2 -2
  3. minitap_mcp-0.4.3/minitap/mcp/core/agents/compare_screenshots.md → minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/eval/prompts/prompt_1.md +2 -2
  4. minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/actual.png +0 -0
  5. minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/figma.png +0 -0
  6. minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/human_feedback.txt +18 -0
  7. minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/prompt_1/model_params.json +3 -0
  8. minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/eval/scenario_1_add_cartoon_img_and_move_button/prompt_1/output.md +46 -0
  9. minitap_mcp-0.5.1/minitap/mcp/core/agents/compare_screenshots/prompt.md +62 -0
  10. minitap_mcp-0.5.1/minitap/mcp/core/utils/figma.py +69 -0
  11. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/tools/analyze_screen.py +3 -3
  12. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/tools/compare_screenshot_with_figma.py +1 -1
  13. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/tools/execute_mobile_command.py +11 -5
  14. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/tools/save_figma_assets.py +5 -11
  15. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/pyproject.toml +2 -2
  16. minitap_mcp-0.4.3/minitap/mcp/core/agents/extract_figma_assets.md +0 -64
  17. minitap_mcp-0.4.3/minitap/mcp/core/agents/extract_figma_assets.py +0 -96
  18. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/PYPI_README.md +0 -0
  19. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/__init__.py +0 -0
  20. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/core/config.py +0 -0
  21. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/core/decorators.py +0 -0
  22. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/core/device.py +0 -0
  23. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/core/llm.py +0 -0
  24. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/core/logging_config.py +0 -0
  25. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/core/models.py +0 -0
  26. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/core/sdk_agent.py +0 -0
  27. /minitap_mcp-0.4.3/minitap/mcp/core/utils.py → /minitap_mcp-0.5.1/minitap/mcp/core/utils/images.py +0 -0
  28. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/main.py +0 -0
  29. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/server/middleware.py +0 -0
  30. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/server/poller.py +0 -0
  31. {minitap_mcp-0.4.3 → minitap_mcp-0.5.1}/minitap/mcp/tools/screen_analyzer.md +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: minitap-mcp
3
- Version: 0.4.3
3
+ Version: 0.5.1
4
4
  Summary: Model Context Protocol server for controlling Android & iOS devices with natural language
5
5
  Author: Pierre-Louis Favreau, Jean-Pierre Lo, Clément Guiguet
6
6
  Requires-Dist: fastmcp>=2.12.4
7
7
  Requires-Dist: python-dotenv>=1.1.1
8
8
  Requires-Dist: pydantic>=2.12.0
9
9
  Requires-Dist: pydantic-settings>=2.10.1
10
- Requires-Dist: minitap-mobile-use>=2.8.2
10
+ Requires-Dist: minitap-mobile-use>=2.9.1
11
11
  Requires-Dist: jinja2>=3.1.6
12
12
  Requires-Dist: langchain-core>=0.3.75
13
13
  Requires-Dist: pillow>=11.1.0
@@ -8,7 +8,7 @@ from pydantic import BaseModel
8
8
 
9
9
  from minitap.mcp.core.device import capture_screenshot, find_mobile_device
10
10
  from minitap.mcp.core.llm import get_minitap_llm
11
- from minitap.mcp.core.utils import get_screenshot_message_for_llm
11
+ from minitap.mcp.core.utils.images import get_screenshot_message_for_llm
12
12
 
13
13
 
14
14
  class CompareScreenshotsOutput(BaseModel):
@@ -27,7 +27,7 @@ async def compare_screenshots(
27
27
  CompareScreenshotsOutput
28
28
  """
29
29
  system_message = Template(
30
- Path(__file__).parent.joinpath("compare_screenshots.md").read_text(encoding="utf-8")
30
+ Path(__file__).parent.joinpath("prompt.md").read_text(encoding="utf-8")
31
31
  ).render()
32
32
 
33
33
  device = find_mobile_device()
@@ -55,8 +55,8 @@ Structured list of differences + summary.
55
55
  Please use the following to start the analysis.
56
56
  **Input:**
57
57
  First screen is the Figma screenshot (what is expected)
58
- Second screen is what is expected (taken from the phone, after the implementation)
58
+ Second screen is what is implemented (taken from the phone, after the implementation)
59
59
 
60
60
  You will have this data in the next messages sent by the user.
61
61
 
62
- Go ahead and generate your report.
62
+ Go ahead and generate your report.
@@ -0,0 +1,18 @@
1
+ 6 differences found :
2
+ - Add button moved and restyled
3
+ The “+ Add” button is now at the top instead of the bottom, and its style is very different. This is a big layout change — confirm with design.
4
+
5
+ - Title typo
6
+ “Languges” should be “Languages”.
7
+
8
+ - Subtitle casing
9
+ The subtitle should be sentence case (“You are learning”), not title case.
10
+
11
+ - Extra robot illustration
12
+ A large yellow robot was added but isn’t in the design. Confirm if it should be there.
13
+
14
+ - Extra sub-label in list item
15
+ “Norwegian” has a second line (“from French”), which isn’t part of the design. Check if this is intended.
16
+
17
+ - Missing small “M” icon
18
+ The red “M” in the bottom-right is missing.
@@ -0,0 +1,46 @@
1
+ Based on the visual comparison of the provided screenshots, here is a detailed report of the differences.
2
+
3
+ ---
4
+
5
+ 1. **Location**: Top-right of the header area vs. bottom-center of the screen
6
+ **Change**: The `+ Add` button has been moved from a full-width element at the bottom of the screen to a smaller, pill-shaped button in the top-right header area. Its style has also changed from a button with only a text label and icon to one with a solid yellow background and white text/icon.
7
+ **Type**: Position/Movement, Style Change, Size Change
8
+ **Impact**: This is a major structural change to the page layout. The primary call-to-action is in a completely different location, which fundamentally alters the user's interaction pattern with the screen.
9
+ **Recommendation**: The development team should align with the design team on the intended placement and style of this critical action button. This change is too significant to be accidental and suggests a disconnect between design and implementation requirements.
10
+
11
+ 2. **Location**: Page title area, below the `X` icon.
12
+ **Change**: The page title text is misspelled as "Languges" in the implementation, whereas the design specifies "Languages".
13
+ **Type**: Text Change
14
+ **Impact**: Introduces a spelling error on a primary screen title, which appears unprofessional.
15
+ **Recommendation**: Correct the typo in the title to "Languages".
16
+
17
+ 3. **Location**: Subtitle area, directly below the page title.
18
+ **Change**: The text casing for the subtitle has been changed from sentence case ("You are learning") to title case ("You are Learning").
19
+ **Type**: Text Change / Style Change
20
+ **Impact**: A minor visual inconsistency that deviates from the typographic style defined in the design.
21
+ **Recommendation**: Update the subtitle text to use sentence case ("You are learning") to match the design specification.
22
+
23
+ 4. **Location**: Bottom half of the screen.
24
+ **Change**: The implemented screen includes a large illustration of a yellow robot character that is not present in the expected design.
25
+ **Type**: Added Element
26
+ **Impact**: This new element significantly alters the visual hierarchy and feel of the screen, consuming a large amount of whitespace and adding a strong branding element not specified in the design.
27
+ **Recommendation**: Verify with the product/design team if this illustration is an intended addition. If not, it should be removed to match the figma design.
28
+
29
+ 5. **Location**: Second item in the language list ("Norwegian").
30
+ **Change**: One of the language list items in the implementation includes a gray sub-label ("from French") below the language name. The design for list items shows only a single line of text for the language.
31
+ **Type**: Added Element (within a component)
32
+ **Impact**: The implemented list item component has a different structure than designed, supporting two lines of text instead of one. This affects component height and internal layout.
33
+ **Recommendation**: Clarify if this two-line state is a required feature. If so, the design should be updated to include this variant. If not, the sub-label should be removed.
34
+
35
+ 6. **Location**: Bottom-right corner of the screen.
36
+ **Change**: The expected design includes a small, red "M" character/logo in the bottom-right corner. This element is missing from the implemented screen.
37
+ **Type**: Removed Element
38
+ **Impact**: A subtle branding or UI element has been omitted from the final implementation.
39
+ **Recommendation**: Add the "M" element back to the screen in the position specified by the design.
40
+
41
+ ---
42
+
43
+ ### Summary
44
+
45
+ - **Total differences found**: 6
46
+ - **Overall "match score"**: 50/100
@@ -0,0 +1,62 @@
1
+ You will be given _two screenshots_.
2
+
3
+ 1. "Expected screenshot" — this is the design from Figma.
4
+ 2. "Implemented screenshot" — this is the actual phone screen that has been built.
5
+
6
+ Your task is to **compare the two screenshots** in detail, and generate a structured report that includes:
7
+
8
+ - A comprehensive list of **all visible differences** between the expected design and the implemented screen.
9
+ - For each difference, provide:
10
+ - A clear **description** of what changed (for example: "The 'Submit' button label changed from 'Submit' to 'Send'", "The icon moved 8px to the right", "The background colour of header changed from #FFFFFF to #F6F6F6", etc.).
11
+ - The **type of change** (e.g., text change, color change, position/movement, size change, added element, removed element, style change).
12
+ - The **location** of the change (for example: "bottom-centre of screen", "top header area", "to the right of search bar"). If possible, approximate coordinates or bounding box (e.g., "approx. 240×180 px at screen width 1080").
13
+ - The **impact on implementation** (i.e., reasoning about what this means: "The implemented version uses a different text label – so behaviour may differ", "The icon moved and may overlap another element", etc.).
14
+ - A **recommendation** if relevant (e.g., "Should revert to #FFFFFF to match design", "Check alignment of icon relative to search bar", etc.).
15
+
16
+ **Important**:
17
+
18
+ - Assume the screenshots are aligned (same resolution and scale); if not aligned mention that as a difference.
19
+ - Focus on _visible UI differences_ (layout, text, style, iconography) – you do _not_ need to inspect source code, only what is visually rendered.
20
+ - Do _not_ produce generic comments like "looks like a difference" – aim for _precise, actionable descriptions_.
21
+ - **IGNORE dynamic/personal content** that naturally differs between mockups and real implementations:
22
+ - User profile information (names, usernames, email addresses, profile pictures)
23
+ - Time-based information (current time, dates, timestamps, "2 hours ago", etc.)
24
+ - Dynamic data (notification counts, unread badges, live statistics)
25
+ - Sample/placeholder content that varies (e.g., "John Doe" vs "Jane Smith")
26
+ - System status information (battery level, signal strength, network indicators)
27
+ - Only flag these as differences if the _structure, layout, or styling_ of these elements differs, not the content itself.
28
+ - Output in a structured format, for example:
29
+
30
+ ```
31
+
32
+ 1. Location: [top header – full width]
33
+ Change: Background colour changed from #FFFFFF → #F6F6F6
34
+ Type: Colour change
35
+ Impact: The header will appear darker than design; text contrast may be lower.
36
+ Recommendation: Update header background to #FFFFFF as in design.
37
+
38
+ ```
39
+
40
+ - At the end produce a summary with ONLY:
41
+ - Total number of differences found
42
+ - Overall "match score" out of 100 (your estimation of how closely the implementation matches the design)
43
+ - Do NOT include any recap, overview, or macro-level summary of changes - all details are already captured in the differences list above.
44
+
45
+ ### Input:
46
+
47
+ - Screenshot A: Expected (Figma)
48
+ - Screenshot B: Implemented (Phone)
49
+ Provide both screenshots and then the prompt.
50
+
51
+ ### Output:
52
+
53
+ Structured list of differences + summary.
54
+
55
+ Please use the following to start the analysis.
56
+ **Input:**
57
+ First screen is the Figma screenshot (what is expected)
58
+ Second screen is what is implemented (taken from the phone, after the implementation)
59
+
60
+ You will have this data in the next messages sent by the user.
61
+
62
+ Go ahead and generate your report.
@@ -0,0 +1,69 @@
1
+ """Agent to extract Figma asset URLs from design context code using regex."""
2
+
3
+ import re
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class FigmaAsset(BaseModel):
9
+ """Represents a single Figma asset."""
10
+
11
+ variable_name: str = Field(description="The variable name from the code (e.g., imgSignal)")
12
+ url: str = Field(description="The full URL to the asset")
13
+ extension: str = Field(description="The file extension (e.g., svg, png, jpg)")
14
+
15
+
16
+ class ExtractedAssets(BaseModel):
17
+ """Container for all extracted Figma assets."""
18
+
19
+ assets: list[FigmaAsset] = Field(
20
+ default_factory=list,
21
+ description="List of all extracted assets from the Figma design context",
22
+ )
23
+ code_implementation: str = Field(
24
+ description="The React/TypeScript code with imports instead of const declarations"
25
+ )
26
+
27
+
28
+ def extract_figma_assets(design_context_code: str) -> ExtractedAssets:
29
+ """Extract asset URLs from Figma design context code using regex.
30
+
31
+ Args:
32
+ design_context_code: The React/TypeScript code from get_design_context
33
+
34
+ Returns:
35
+ ExtractedAssets with list of assets and transformed code
36
+ """
37
+ # Regex captures: (1) variable name, (2) full URL, (4) extension
38
+ # Supports http/https, any domain, query strings, optional semicolon
39
+ pattern = r'const\s+(\w+)\s*=\s*["\']((https?://[^"\']+?)\.(\w+)(?:\?[^"\']*)?)["\'];?'
40
+ matches = re.finditer(pattern, design_context_code)
41
+
42
+ assets = []
43
+ asset_lines = []
44
+
45
+ for match in matches:
46
+ var_name = match.group(1)
47
+ url = match.group(2)
48
+ extension = match.group(4)
49
+
50
+ assets.append(FigmaAsset(variable_name=var_name, url=url, extension=extension))
51
+ asset_lines.append(match.group(0))
52
+
53
+ import_statements = []
54
+ for asset in assets:
55
+ import_statements.append(
56
+ f"import {asset.variable_name} from './{asset.variable_name}.{asset.extension}';"
57
+ )
58
+
59
+ transformed_code = design_context_code
60
+ for line in asset_lines:
61
+ transformed_code = transformed_code.replace(line, "")
62
+
63
+ lines = transformed_code.split("\n")
64
+ while lines and not lines[0].strip():
65
+ lines.pop(0)
66
+
67
+ final_code = "\n".join(import_statements) + "\n\n" + "\n".join(lines)
68
+
69
+ return ExtractedAssets(assets=assets, code_implementation=final_code)
@@ -1,15 +1,15 @@
1
1
  from pathlib import Path
2
- from jinja2 import Template
3
- from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
4
2
  from uuid import uuid4
5
3
 
4
+ from jinja2 import Template
5
+ from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
6
6
  from pydantic import Field
7
7
 
8
8
  from minitap.mcp.core.config import settings
9
9
  from minitap.mcp.core.decorators import handle_tool_errors
10
10
  from minitap.mcp.core.device import capture_screenshot, find_mobile_device
11
11
  from minitap.mcp.core.llm import get_minitap_llm
12
- from minitap.mcp.core.utils import compress_base64_jpeg, get_screenshot_message_for_llm
12
+ from minitap.mcp.core.utils.images import compress_base64_jpeg, get_screenshot_message_for_llm
13
13
  from minitap.mcp.main import mcp
14
14
 
15
15
 
@@ -11,7 +11,7 @@ from fastmcp.tools.tool import ToolResult
11
11
  from PIL import Image
12
12
  from pydantic import Field
13
13
 
14
- from minitap.mcp.core.agents.compare_screenshots import compare_screenshots
14
+ from minitap.mcp.core.agents.compare_screenshots.agent import compare_screenshots
15
15
  from minitap.mcp.core.config import settings
16
16
  from minitap.mcp.core.decorators import handle_tool_errors
17
17
  from minitap.mcp.main import mcp
@@ -34,10 +34,6 @@ def _serialize_result(result: Any) -> Any:
34
34
  - "Open the settings app and tell me the battery level"
35
35
  - "Find the first 3 unread emails in Gmail"
36
36
  - "Take a screenshot and save it"
37
-
38
- The tool uses the Minitap platform with API key authentication.
39
- Set MINITAP_API_KEY and MINITAP_API_BASE_URL environment variables.
40
- Visit https://platform.minitap.ai to get your API key.
41
37
  """,
42
38
  )
43
39
  @handle_tool_errors
@@ -53,11 +49,21 @@ async def execute_mobile_command(
53
49
  default="default",
54
50
  description="Name of the profile to use for this task. Defaults to 'default'.",
55
51
  ),
52
+ locked_app_package: str | None = Field(
53
+ default=None,
54
+ description="Optional package name of the app to lock the device to. "
55
+ "Will launch the app if not already running, and keep it in foreground "
56
+ "until the task is completed.",
57
+ ),
56
58
  ) -> str | dict[str, Any]:
57
59
  """Run a manual task on a mobile device via the Minitap platform."""
58
60
  try:
59
61
  request = PlatformTaskRequest(
60
- task=ManualTaskConfig(goal=goal, output_description=output_description),
62
+ task=ManualTaskConfig(
63
+ goal=goal,
64
+ output_description=output_description,
65
+ locked_app_package=locked_app_package,
66
+ ),
61
67
  )
62
68
  agent = get_mobile_use_agent()
63
69
  result = await agent.run_task(request=request)
@@ -11,11 +11,6 @@ from fastmcp.exceptions import ToolError
11
11
  from fastmcp.tools.tool import ToolResult
12
12
  from pydantic import Field
13
13
 
14
- from minitap.mcp.core.agents.extract_figma_assets import (
15
- ExtractedAssets,
16
- FigmaAsset,
17
- extract_figma_assets,
18
- )
19
14
  from minitap.mcp.core.config import settings
20
15
  from minitap.mcp.core.decorators import handle_tool_errors
21
16
  from minitap.mcp.core.logging_config import get_logger
@@ -25,6 +20,7 @@ from minitap.mcp.core.models import (
25
20
  DownloadStatus,
26
21
  FigmaDesignContextOutput,
27
22
  )
23
+ from minitap.mcp.core.utils.figma import ExtractedAssets, FigmaAsset, extract_figma_assets
28
24
  from minitap.mcp.main import mcp
29
25
  from minitap.mcp.tools.compare_screenshot_with_figma import (
30
26
  compress_image_base64,
@@ -41,9 +37,9 @@ logger = get_logger(__name__)
41
37
 
42
38
  This tool:
43
39
  1. Calls get_design_context from Figma MCP to get the React/TypeScript code
44
- 2. Extracts all asset URLs and code implementation from the code
40
+ 2. Extracts asset URLs and transforms const declarations to import statements
45
41
  3. Downloads each asset to .mobile-use/figma_assets/<node-id>/ folder
46
- 4. Saves the code implementation to .mobile-use/figma_assets/<node-id>/code_implementation.ts
42
+ 4. Saves the transformed code to .mobile-use/figma_assets/<node-id>/code_implementation.ts
47
43
  5. Returns a list of downloaded files
48
44
  """,
49
45
  )
@@ -75,10 +71,8 @@ async def save_figma_assets(
75
71
  # Step 1: Get design context from Figma MCP
76
72
  design_context = await get_design_context(node_id, file_key)
77
73
 
78
- # Step 2: Extract asset URLs using LLM agent
79
- extracted_context: ExtractedAssets = await extract_figma_assets(
80
- design_context.code_implementation
81
- )
74
+ # Step 2: Extract asset URLs and transform code
75
+ extracted_context: ExtractedAssets = extract_figma_assets(design_context.code_implementation)
82
76
  if not extracted_context.assets:
83
77
  raise ToolError("No assets found in the Figma design context.")
84
78
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitap-mcp"
3
- version = "0.4.3"
3
+ version = "0.5.1"
4
4
  description = "Model Context Protocol server for controlling Android & iOS devices with natural language"
5
5
  readme = "PYPI_README.md"
6
6
 
@@ -17,7 +17,7 @@ dependencies = [
17
17
  "python-dotenv>=1.1.1",
18
18
  "pydantic>=2.12.0",
19
19
  "pydantic-settings>=2.10.1",
20
- "minitap-mobile-use>=2.8.2",
20
+ "minitap-mobile-use>=2.9.1",
21
21
  "jinja2>=3.1.6",
22
22
  "langchain-core>=0.3.75",
23
23
  "pillow>=11.1.0",
@@ -1,64 +0,0 @@
1
- You are an expert at parsing React/TypeScript code to extract asset URLs and generate clean, documented code implementations.
2
-
3
- Your task is to:
4
-
5
- 1. Extract all asset URLs from the provided code snippet
6
- 2. Generate a clean `code_implementation` output that includes the React code with embedded comments referencing implementation and node guidelines
7
-
8
- **Instructions:**
9
-
10
- ## Part 1: Extract Asset URLs
11
-
12
- 1. Look for all constant declarations that contain URLs pointing to assets (images, SVGs, etc.)
13
- 2. These constants typically follow patterns like:
14
-
15
- - `const imgVariableName = "http://localhost:3845/assets/[hash].[extension]";`
16
- - The variable names usually start with `img` followed by a descriptive name in camelCase
17
-
18
- 3. For each asset URL found, extract:
19
- - The **variable name** (e.g., `imgSignal`, `imgBatteryThreeQuarters`)
20
- - The **full URL** (e.g., `http://localhost:3845/assets/685c5ac58caa29556e29737cf8f8c9605d9c8571.svg`)
21
- - The **file extension** from the URL (e.g., `svg`, `png`, `jpg`)
22
-
23
- ## Part 2: Generate Code Implementation
24
-
25
- The `code_implementation` field should contain:
26
-
27
- 1. The React/TypeScript code with **LOCAL asset imports** instead of HTTP URLs:
28
-
29
- - Convert `const imgSignal = "http://localhost:3845/assets/[hash].svg";`
30
- - To `import imgSignal from './assets/imgSignal.svg';` (or appropriate relative path)
31
- - Use the **exact same variable names** as in the original const declarations
32
- - **CRITICAL**: Preserve the variable naming convention
33
-
34
- 2. Preserve all `data-node-id` attributes and other metadata in the code
35
-
36
- ## Part 3: Return Format
37
-
38
- Return a JSON object with two fields:
39
-
40
- - `assets`: Array of extracted asset objects
41
- - `code_implementation`: String containing the React code with embedded guideline comments
42
-
43
- ```json
44
- {
45
- "assets": [
46
- {
47
- "variable_name": "imgSignal",
48
- "url": "http://localhost:3845/assets/685c5ac58caa29556e29737cf8f8c9605d9c8571.svg",
49
- "extension": "svg"
50
- },
51
- ...
52
- ],
53
- "code_implementation": "import ... function ..."
54
- }
55
- ```
56
-
57
- **Important:**
58
-
59
- - Only extract asset URLs
60
- - Preserve the exact variable names as they appear in the code
61
- - DO NOT MISS any assets
62
- - If no assets are found, return an empty array for `assets`
63
- - Return ONLY the JSON object with both `assets` and `code_implementation` fields
64
- - Do NOT include the const declarations of the assets in the code_implementation output - convert them to imports.
@@ -1,96 +0,0 @@
1
- """Agent to extract Figma asset URLs from design context code."""
2
-
3
- import re
4
- import uuid
5
- from pathlib import Path
6
-
7
- from jinja2 import Template
8
- from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
9
- from pydantic import BaseModel, Field
10
-
11
- from minitap.mcp.core.llm import get_minitap_llm
12
-
13
-
14
- class FigmaAsset(BaseModel):
15
- """Represents a single Figma asset."""
16
-
17
- variable_name: str = Field(description="The variable name from the code (e.g., imgSignal)")
18
- url: str = Field(description="The full URL to the asset")
19
- extension: str = Field(description="The file extension (e.g., svg, png, jpg)")
20
-
21
-
22
- class ExtractedAssets(BaseModel):
23
- """Container for all extracted Figma assets."""
24
-
25
- assets: list[FigmaAsset] = Field(
26
- default_factory=list,
27
- description="List of all extracted assets from the Figma design context",
28
- )
29
- code_implementation: str = Field(
30
- description=(
31
- "The React/TypeScript code\n"
32
- "with the local url declarations turned into const declarations"
33
- )
34
- )
35
-
36
-
37
- def sanitize_unicode_for_llm(text: str) -> str:
38
- """Remove or replace problematic Unicode characters that increase token consumption.
39
-
40
- Characters outside the Basic Multilingual Plane (BMP) like emoji and special symbols
41
- get escaped as \\U sequences when sent to LLMs, dramatically increasing token count
42
- and processing time.
43
-
44
- Args:
45
- text: The text to sanitize
46
-
47
- Returns:
48
- Text with problematic Unicode characters replaced with placeholders
49
- """
50
-
51
- # Replace characters outside BMP (U+10000 and above) with a placeholder
52
- # These are typically emoji, special symbols, or rare characters
53
- def replace_high_unicode(match):
54
- char = match.group(0)
55
- codepoint = ord(char)
56
- # Return a descriptive placeholder
57
- return f"[U+{codepoint:X}]"
58
-
59
- # Pattern matches characters with codepoints >= U+10000
60
- pattern = re.compile(r"[\U00010000-\U0010FFFF]")
61
- sanitized = pattern.sub(replace_high_unicode, text)
62
-
63
- return sanitized
64
-
65
-
66
- async def extract_figma_assets(design_context_code: str) -> ExtractedAssets:
67
- """Extract asset URLs from Figma design context code.
68
-
69
- Args:
70
- design_context_code: The React/TypeScript code from get_design_context
71
-
72
- Returns:
73
- List of dictionaries containing variable_name, url, and extension
74
- """
75
- system_message = Template(
76
- Path(__file__).parent.joinpath("extract_figma_assets.md").read_text(encoding="utf-8")
77
- ).render()
78
-
79
- sanitized_code = sanitize_unicode_for_llm(design_context_code)
80
-
81
- messages: list[BaseMessage] = [
82
- SystemMessage(content=system_message),
83
- HumanMessage(
84
- content=f"Here is the code to analyze:\n\n```typescript\n{sanitized_code}\n```"
85
- ),
86
- ]
87
-
88
- llm = get_minitap_llm(
89
- model="openai/gpt-5",
90
- temperature=0,
91
- trace_id=str(uuid.uuid4()),
92
- remote_tracing=True,
93
- ).with_structured_output(ExtractedAssets)
94
- result: ExtractedAssets = await llm.ainvoke(messages) # type: ignore
95
-
96
- return result
File without changes