pi-mono-all 1.0.0
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.
- package/CHANGELOG.md +13 -0
- package/LICENCE.md +7 -0
- package/node_modules/pi-common/package.json +22 -0
- package/node_modules/pi-common/src/auth-config.ts +290 -0
- package/node_modules/pi-common/src/auth.ts +63 -0
- package/node_modules/pi-common/src/cache.ts +60 -0
- package/node_modules/pi-common/src/errors.ts +47 -0
- package/node_modules/pi-common/src/http-client.ts +118 -0
- package/node_modules/pi-common/src/index.ts +7 -0
- package/node_modules/pi-common/src/rate-limiter.ts +32 -0
- package/node_modules/pi-common/src/tool-result.ts +27 -0
- package/node_modules/pi-mono-ask-user-question/CHANGELOG.md +185 -0
- package/node_modules/pi-mono-ask-user-question/README.md +226 -0
- package/node_modules/pi-mono-ask-user-question/index.ts +923 -0
- package/node_modules/pi-mono-ask-user-question/package.json +29 -0
- package/node_modules/pi-mono-auto-fix/CHANGELOG.md +59 -0
- package/node_modules/pi-mono-auto-fix/README.md +77 -0
- package/node_modules/pi-mono-auto-fix/index.ts +488 -0
- package/node_modules/pi-mono-auto-fix/package.json +23 -0
- package/node_modules/pi-mono-btw/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-btw/README.md +24 -0
- package/node_modules/pi-mono-btw/index.ts +499 -0
- package/node_modules/pi-mono-btw/package.json +29 -0
- package/node_modules/pi-mono-clear/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-clear/README.md +40 -0
- package/node_modules/pi-mono-clear/index.ts +45 -0
- package/node_modules/pi-mono-clear/package.json +29 -0
- package/node_modules/pi-mono-context/CHANGELOG.md +12 -0
- package/node_modules/pi-mono-context/README.md +74 -0
- package/node_modules/pi-mono-context/index.ts +641 -0
- package/node_modules/pi-mono-context/package.json +29 -0
- package/node_modules/pi-mono-context-guard/CHANGELOG.md +195 -0
- package/node_modules/pi-mono-context-guard/README.md +81 -0
- package/node_modules/pi-mono-context-guard/index.ts +212 -0
- package/node_modules/pi-mono-context-guard/package.json +23 -0
- package/node_modules/pi-mono-figma/CHANGELOG.md +59 -0
- package/node_modules/pi-mono-figma/README.md +236 -0
- package/node_modules/pi-mono-figma/__tests__/code-connect.test.ts +32 -0
- package/node_modules/pi-mono-figma/__tests__/figma-assets.test.ts +38 -0
- package/node_modules/pi-mono-figma/__tests__/figma-component-hints.test.ts +23 -0
- package/node_modules/pi-mono-figma/__tests__/figma-implementation-layout.test.ts +47 -0
- package/node_modules/pi-mono-figma/__tests__/figma-search.test.ts +51 -0
- package/node_modules/pi-mono-figma/__tests__/figma-summarizer.test.ts +65 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/complex-auto-layout.json +115 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/component-instance.json +50 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/hidden-and-vectors.json +28 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/variables-and-styles.json +40 -0
- package/node_modules/pi-mono-figma/docs/live-selection-bridge.md +16 -0
- package/node_modules/pi-mono-figma/index.ts +6 -0
- package/node_modules/pi-mono-figma/package.json +33 -0
- package/node_modules/pi-mono-figma/skills/figma/SKILL.md +143 -0
- package/node_modules/pi-mono-figma/src/code-connect.ts +110 -0
- package/node_modules/pi-mono-figma/src/figma-assets.ts +146 -0
- package/node_modules/pi-mono-figma/src/figma-cache.ts +6 -0
- package/node_modules/pi-mono-figma/src/figma-client.ts +471 -0
- package/node_modules/pi-mono-figma/src/figma-component-hints.ts +87 -0
- package/node_modules/pi-mono-figma/src/figma-implementation.ts +264 -0
- package/node_modules/pi-mono-figma/src/figma-schemas.ts +139 -0
- package/node_modules/pi-mono-figma/src/figma-search.ts +195 -0
- package/node_modules/pi-mono-figma/src/figma-summarizer.ts +673 -0
- package/node_modules/pi-mono-figma/src/figma-tokens.ts +57 -0
- package/node_modules/pi-mono-figma/src/figma-tools.ts +352 -0
- package/node_modules/pi-mono-linear/CHANGELOG.md +44 -0
- package/node_modules/pi-mono-linear/README.md +159 -0
- package/node_modules/pi-mono-linear/index.ts +6 -0
- package/node_modules/pi-mono-linear/package.json +30 -0
- package/node_modules/pi-mono-linear/skills/linear/SKILL.md +107 -0
- package/node_modules/pi-mono-linear/src/linear-client.ts +339 -0
- package/node_modules/pi-mono-linear/src/linear-queries.ts +101 -0
- package/node_modules/pi-mono-linear/src/linear-schemas.ts +90 -0
- package/node_modules/pi-mono-linear/src/linear-tools.ts +362 -0
- package/node_modules/pi-mono-loop/CHANGELOG.md +163 -0
- package/node_modules/pi-mono-loop/README.md +54 -0
- package/node_modules/pi-mono-loop/index.ts +291 -0
- package/node_modules/pi-mono-loop/package.json +26 -0
- package/node_modules/pi-mono-multi-edit/CHANGELOG.md +232 -0
- package/node_modules/pi-mono-multi-edit/README.md +244 -0
- package/node_modules/pi-mono-multi-edit/__tests__/classic.test.ts +277 -0
- package/node_modules/pi-mono-multi-edit/__tests__/diff.test.ts +77 -0
- package/node_modules/pi-mono-multi-edit/__tests__/patch.test.ts +287 -0
- package/node_modules/pi-mono-multi-edit/benchmark-edits.ts +966 -0
- package/node_modules/pi-mono-multi-edit/classic.ts +435 -0
- package/node_modules/pi-mono-multi-edit/diff.ts +143 -0
- package/node_modules/pi-mono-multi-edit/index.ts +266 -0
- package/node_modules/pi-mono-multi-edit/package.json +37 -0
- package/node_modules/pi-mono-multi-edit/patch.ts +463 -0
- package/node_modules/pi-mono-multi-edit/types.ts +53 -0
- package/node_modules/pi-mono-multi-edit/workspace.ts +85 -0
- package/node_modules/pi-mono-review/CHANGELOG.md +190 -0
- package/node_modules/pi-mono-review/README.md +30 -0
- package/node_modules/pi-mono-review/common.ts +930 -0
- package/node_modules/pi-mono-review/index.ts +8 -0
- package/node_modules/pi-mono-review/package.json +29 -0
- package/node_modules/pi-mono-review/review-tui.ts +194 -0
- package/node_modules/pi-mono-review/review.ts +119 -0
- package/node_modules/pi-mono-review/reviewer.ts +339 -0
- package/node_modules/pi-mono-sentinel/CHANGELOG.md +158 -0
- package/node_modules/pi-mono-sentinel/README.md +87 -0
- package/node_modules/pi-mono-sentinel/__tests__/output-scanner.test.ts +109 -0
- package/node_modules/pi-mono-sentinel/__tests__/permissions.test.ts +202 -0
- package/node_modules/pi-mono-sentinel/__tests__/whitelist.test.ts +59 -0
- package/node_modules/pi-mono-sentinel/guards/execution-tracker.ts +281 -0
- package/node_modules/pi-mono-sentinel/guards/output-scanner.ts +232 -0
- package/node_modules/pi-mono-sentinel/guards/permission-gate.ts +170 -0
- package/node_modules/pi-mono-sentinel/index.ts +43 -0
- package/node_modules/pi-mono-sentinel/package.json +26 -0
- package/node_modules/pi-mono-sentinel/patterns/permissions.ts +175 -0
- package/node_modules/pi-mono-sentinel/patterns/read-targets.ts +104 -0
- package/node_modules/pi-mono-sentinel/patterns/secrets.ts +143 -0
- package/node_modules/pi-mono-sentinel/session.ts +95 -0
- package/node_modules/pi-mono-sentinel/specs/2026/04/sentinel/001-permission-gate.md +145 -0
- package/node_modules/pi-mono-sentinel/types.ts +39 -0
- package/node_modules/pi-mono-sentinel/whitelist.ts +86 -0
- package/node_modules/pi-mono-simplify/CHANGELOG.md +163 -0
- package/node_modules/pi-mono-simplify/README.md +56 -0
- package/node_modules/pi-mono-simplify/index.ts +78 -0
- package/node_modules/pi-mono-simplify/package.json +29 -0
- package/node_modules/pi-mono-status-line/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-status-line/README.md +96 -0
- package/node_modules/pi-mono-status-line/basic.ts +89 -0
- package/node_modules/pi-mono-status-line/expert.ts +689 -0
- package/node_modules/pi-mono-status-line/index.ts +54 -0
- package/node_modules/pi-mono-status-line/package.json +29 -0
- package/node_modules/pi-mono-team-mode/CHANGELOG.md +278 -0
- package/node_modules/pi-mono-team-mode/README.md +246 -0
- package/node_modules/pi-mono-team-mode/__tests__/agent-manager-transient.test.ts +75 -0
- package/node_modules/pi-mono-team-mode/__tests__/delegation-manager.test.ts +118 -0
- package/node_modules/pi-mono-team-mode/__tests__/formatters.test.ts +104 -0
- package/node_modules/pi-mono-team-mode/__tests__/model-config.test.ts +272 -0
- package/node_modules/pi-mono-team-mode/__tests__/notification-box.test.ts +34 -0
- package/node_modules/pi-mono-team-mode/__tests__/parallel-utils.test.ts +32 -0
- package/node_modules/pi-mono-team-mode/__tests__/pi-stream-parser.test.ts +64 -0
- package/node_modules/pi-mono-team-mode/__tests__/prompts.test.ts +106 -0
- package/node_modules/pi-mono-team-mode/__tests__/store.test.ts +164 -0
- package/node_modules/pi-mono-team-mode/__tests__/tasks.test.ts +267 -0
- package/node_modules/pi-mono-team-mode/__tests__/teammate-specs.test.ts +114 -0
- package/node_modules/pi-mono-team-mode/__tests__/widget.test.ts +41 -0
- package/node_modules/pi-mono-team-mode/__tests__/worktree.test.ts +78 -0
- package/node_modules/pi-mono-team-mode/core/chain-utils.ts +90 -0
- package/node_modules/pi-mono-team-mode/core/fs-utils.ts +44 -0
- package/node_modules/pi-mono-team-mode/core/model-config.ts +432 -0
- package/node_modules/pi-mono-team-mode/core/parallel-utils.ts +48 -0
- package/node_modules/pi-mono-team-mode/core/prompts.ts +158 -0
- package/node_modules/pi-mono-team-mode/core/store.ts +156 -0
- package/node_modules/pi-mono-team-mode/core/tasks.ts +99 -0
- package/node_modules/pi-mono-team-mode/core/teammate-specs.ts +124 -0
- package/node_modules/pi-mono-team-mode/core/types.ts +160 -0
- package/node_modules/pi-mono-team-mode/index.ts +825 -0
- package/node_modules/pi-mono-team-mode/managers/agent-manager.ts +654 -0
- package/node_modules/pi-mono-team-mode/managers/delegation-manager.ts +211 -0
- package/node_modules/pi-mono-team-mode/managers/task-manager.ts +238 -0
- package/node_modules/pi-mono-team-mode/managers/team-manager.ts +59 -0
- package/node_modules/pi-mono-team-mode/package.json +33 -0
- package/node_modules/pi-mono-team-mode/runtime/pi-stream-parser.ts +194 -0
- package/node_modules/pi-mono-team-mode/runtime/subprocess.ts +183 -0
- package/node_modules/pi-mono-team-mode/runtime/transient-session.ts +196 -0
- package/node_modules/pi-mono-team-mode/runtime/worktree.ts +90 -0
- package/node_modules/pi-mono-team-mode/ui/formatters.ts +149 -0
- package/node_modules/pi-mono-team-mode/ui/notification-box.ts +55 -0
- package/node_modules/pi-mono-team-mode/ui/widget.ts +94 -0
- package/package.json +76 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "4:1",
|
|
3
|
+
"name": "Visibility Fixture",
|
|
4
|
+
"type": "FRAME",
|
|
5
|
+
"absoluteBoundingBox": { "width": 200, "height": 120 },
|
|
6
|
+
"children": [
|
|
7
|
+
{
|
|
8
|
+
"id": "4:2",
|
|
9
|
+
"name": "Visible text",
|
|
10
|
+
"type": "TEXT",
|
|
11
|
+
"characters": "Visible copy"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "4:3",
|
|
15
|
+
"name": "Hidden text",
|
|
16
|
+
"type": "TEXT",
|
|
17
|
+
"visible": false,
|
|
18
|
+
"characters": "Hidden copy"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "4:4",
|
|
22
|
+
"name": "Search icon",
|
|
23
|
+
"type": "VECTOR",
|
|
24
|
+
"absoluteBoundingBox": { "width": 16, "height": 16 },
|
|
25
|
+
"children": [{ "id": "4:5", "name": "Vector point", "type": "VECTOR" }]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "3:1",
|
|
3
|
+
"name": "Tokenized Button",
|
|
4
|
+
"type": "FRAME",
|
|
5
|
+
"absoluteBoundingBox": { "width": 160, "height": 48 },
|
|
6
|
+
"layoutMode": "HORIZONTAL",
|
|
7
|
+
"primaryAxisAlignItems": "CENTER",
|
|
8
|
+
"counterAxisAlignItems": "CENTER",
|
|
9
|
+
"styles": { "fill": "S:primary-fill", "stroke": "S:border-default" },
|
|
10
|
+
"boundVariables": {
|
|
11
|
+
"fills": [{ "type": "VARIABLE_ALIAS", "id": "VariableID:color-primary" }],
|
|
12
|
+
"cornerRadius": { "type": "VARIABLE_ALIAS", "id": "VariableID:radius-md" }
|
|
13
|
+
},
|
|
14
|
+
"fills": [
|
|
15
|
+
{
|
|
16
|
+
"type": "SOLID",
|
|
17
|
+
"color": { "r": 0.02, "g": 0.35, "b": 0.9 },
|
|
18
|
+
"opacity": 1
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"cornerRadius": 8,
|
|
22
|
+
"children": [
|
|
23
|
+
{
|
|
24
|
+
"id": "3:2",
|
|
25
|
+
"name": "Label",
|
|
26
|
+
"type": "TEXT",
|
|
27
|
+
"characters": "Submit",
|
|
28
|
+
"style": { "fontFamily": "Inter", "fontSize": 14, "fontWeight": 600 },
|
|
29
|
+
"styles": { "text": "S:text-button" },
|
|
30
|
+
"boundVariables": {
|
|
31
|
+
"fills": [
|
|
32
|
+
{ "type": "VARIABLE_ALIAS", "id": "VariableID:text-on-primary" }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"fills": [
|
|
36
|
+
{ "type": "SOLID", "color": { "r": 1, "g": 1, "b": 1 }, "opacity": 1 }
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Future live-selection bridge
|
|
2
|
+
|
|
3
|
+
The current Figma extension is REST/API-based and read-only. It operates on explicit `fileKey` and `nodeId` values and does not receive live selection events from the Figma app.
|
|
4
|
+
|
|
5
|
+
A future live-selection workflow should be implemented as a separate bridge/plugin layer:
|
|
6
|
+
|
|
7
|
+
1. A Figma plugin reads the current page and selected node IDs.
|
|
8
|
+
2. A local bridge exposes that selection through localhost or Pi extension IPC.
|
|
9
|
+
3. A Pi tool such as `figma_get_current_selection` returns file key, page, selected node IDs, and optional render metadata.
|
|
10
|
+
4. Existing processed tools (`figma_get_node_summary`, `figma_get_implementation_context`, search, and asset extraction) run against those selected node IDs.
|
|
11
|
+
|
|
12
|
+
Non-goals for the REST baseline:
|
|
13
|
+
|
|
14
|
+
- no Figma file edits,
|
|
15
|
+
- no private APIs,
|
|
16
|
+
- no claim of live selection support until a plugin/bridge exists.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-mono-figma",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Pi extension and skill for Figma design context tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"pi-skill",
|
|
10
|
+
"figma"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "npx tsx --test '__tests__/**/*.test.ts'"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"pi-common": "workspace:*"
|
|
17
|
+
},
|
|
18
|
+
"bundledDependencies": [
|
|
19
|
+
"pi-common"
|
|
20
|
+
],
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
23
|
+
"@sinclair/typebox": "*"
|
|
24
|
+
},
|
|
25
|
+
"pi": {
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./index.ts"
|
|
28
|
+
],
|
|
29
|
+
"skills": [
|
|
30
|
+
"./skills"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: figma
|
|
3
|
+
description: Access Figma design files using native pi tools — read LLM-ready summaries, explanations, implementation context, screenshots, components, styles, variables, and design tokens. Requires a Figma personal access token.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Figma Design Integration
|
|
7
|
+
|
|
8
|
+
Use the native `figma_*` tools to read Figma files and translate designs into code. Prefer processed, LLM-ready tools over raw Figma JSON.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- User provides a Figma file URL and asks you to explain or implement a design.
|
|
13
|
+
- User asks about Figma colors, typography, spacing, components, variables, or layout.
|
|
14
|
+
- You need screenshots/assets for visual validation.
|
|
15
|
+
|
|
16
|
+
## Authentication
|
|
17
|
+
|
|
18
|
+
The tools read the token from an in-memory override, `FIGMA_TOKEN`, or `~/.pi/agent/auth.json` at `.figma.token`.
|
|
19
|
+
|
|
20
|
+
If auth is missing, invalid, or expired, do **not** ask the user to paste the token in chat. Use the native `figma_configure_auth` tool or ask the user to run `/figma-auth --force`. The prompt is masked and the token is stored by the extension without returning it to the model.
|
|
21
|
+
|
|
22
|
+
## URL Parsing
|
|
23
|
+
|
|
24
|
+
Given:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
https://www.figma.com/design/ABC123def456/Project-Name?node-id=123-456
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- File key: `ABC123def456`
|
|
31
|
+
- Node ID: `123-456` from the URL (`figma_*` tools also accept API format `123:456`)
|
|
32
|
+
|
|
33
|
+
Use `figma_parse_url` when you need to extract these values from a full URL.
|
|
34
|
+
|
|
35
|
+
## Default Tool Workflow
|
|
36
|
+
|
|
37
|
+
1. Use `figma_configure_auth` only when auth is missing, invalid, expired, or the user asks to update the token.
|
|
38
|
+
2. Use `figma_parse_url` for full Figma URLs.
|
|
39
|
+
3. If the exact target node is unclear, use `figma_get_design_context` plus `figma_find_nodes_by_name` or `figma_find_nodes_by_text` before raw exploration.
|
|
40
|
+
4. Use `figma_render_nodes` for screenshots and `figma_extract_assets` when icons, node renders, or image fills are needed for implementation.
|
|
41
|
+
- Do **not** pass `outputDir` unless the user explicitly requests persistent files in a specific location. Let the extension use its OS temp directory default for generated images/assets.
|
|
42
|
+
5. Use `figma_explain_node` or `figma_get_node_summary` for the target frame/component.
|
|
43
|
+
6. Use enriched `figma_get_implementation_context` when coding from a design. Pass `framework`, `styling`, `resolveTokens`, and `includeCodeSnippets` when useful.
|
|
44
|
+
7. Use `figma_find_code_connect_mapping` only when the local repo may contain Code Connect mappings or Figma URL/node references.
|
|
45
|
+
8. Use `figma_get_component_implementation_hints` for component-level implementation planning.
|
|
46
|
+
9. Use `figma_get_nodes` only for raw debugging.
|
|
47
|
+
|
|
48
|
+
**Do not call `figma_get_nodes` by default. Prefer processed tools.**
|
|
49
|
+
|
|
50
|
+
Prefer batch calls where supported: pass multiple node IDs to `figma_render_nodes`, `figma_get_node_metadata`, or raw `figma_get_nodes` instead of looping.
|
|
51
|
+
|
|
52
|
+
## Recommended Workflows
|
|
53
|
+
|
|
54
|
+
### Explaining a component
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
figma_parse_url
|
|
58
|
+
figma_render_nodes
|
|
59
|
+
figma_explain_node
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Implementing a design
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
figma_parse_url
|
|
66
|
+
figma_find_nodes_by_name/text if the URL lacks an exact target node
|
|
67
|
+
figma_render_nodes
|
|
68
|
+
figma_get_implementation_context with framework/styling/token options when useful
|
|
69
|
+
figma_get_node_summary for specific subnodes if needed
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Extracting assets
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
figma_extract_assets
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use this when visual assets are needed. Prefer the manifest's `nodePath`, `suggestedName`, hashes, and local paths over guessing layer-to-file mappings.
|
|
79
|
+
|
|
80
|
+
### Finding local implementation mappings
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
figma_find_code_connect_mapping
|
|
84
|
+
figma_get_component_implementation_hints
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Only use these when you are working in a local code repository where Code Connect or Figma URL/node references may exist.
|
|
88
|
+
|
|
89
|
+
### Debugging the extension or raw Figma data
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
figma_get_nodes
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Processed Tools
|
|
96
|
+
|
|
97
|
+
- `figma_get_node_summary` returns compact structured summaries: name, type, size, layout, spacing/padding, fills/strokes/effects, visible text, component properties, and immediate child hierarchy.
|
|
98
|
+
- `figma_extract_text` returns visible text nodes only.
|
|
99
|
+
- `figma_explain_node` returns human-readable Markdown for questions like “Explain this component.”
|
|
100
|
+
- `figma_find_nodes_by_name` and `figma_find_nodes_by_text` search names/text with compact path-aware matches. Use them before raw file exploration.
|
|
101
|
+
- `figma_get_implementation_context` returns coding-ready context: purpose, sections, fields/buttons, measurements, typography, colors, spacing, CSS layout/responsive hints, accessibility hints, design tokens, assets, framework hints, and hierarchy.
|
|
102
|
+
- `figma_extract_assets` returns an asset manifest for SVG icons, node renders, and image fills.
|
|
103
|
+
- `figma_find_code_connect_mapping` scans the local repo for Code Connect/Figma references.
|
|
104
|
+
- `figma_get_component_implementation_hints` combines Figma context, variants, tokens, assets, accessibility, Code Connect matches, and optional starter snippets.
|
|
105
|
+
|
|
106
|
+
Processed tools default to shallow, safe fetches:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
{
|
|
110
|
+
depth: 2,
|
|
111
|
+
includeHidden: false,
|
|
112
|
+
includeVectors: false,
|
|
113
|
+
includeComponentInternals: false,
|
|
114
|
+
framework?: "react" | "html" | "vue" | "angular" | "react-native",
|
|
115
|
+
styling?: "css" | "css-modules" | "styled-components" | "tailwind" | "inline",
|
|
116
|
+
resolveTokens?: true,
|
|
117
|
+
includeCodeSnippets?: false
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Only increase `depth` (max 4) or enable internals/vectors for a specific child node when needed.
|
|
122
|
+
|
|
123
|
+
## Raw Escape Hatches
|
|
124
|
+
|
|
125
|
+
- `figma_get_file`
|
|
126
|
+
- `figma_get_nodes`
|
|
127
|
+
|
|
128
|
+
Use these only when raw Figma JSON is explicitly needed or when debugging the extension. They may return very large responses and reduce answer quality.
|
|
129
|
+
|
|
130
|
+
## Output Limits
|
|
131
|
+
|
|
132
|
+
Processed tools enforce compact defaults and may include `metadata.truncated: true` plus `nextSteps`, for example:
|
|
133
|
+
|
|
134
|
+
- Call `figma_get_node_summary` with a deeper `depth`.
|
|
135
|
+
- Inspect a specific child node by ID.
|
|
136
|
+
- Enable `includeComponentInternals=true` for a focused component instance.
|
|
137
|
+
|
|
138
|
+
## Notes
|
|
139
|
+
|
|
140
|
+
- Figma API is rate-limited; batch node IDs where supported.
|
|
141
|
+
- Large responses may be truncated; narrow to a specific child node when needed.
|
|
142
|
+
- This integration is read-only and cannot modify Figma files.
|
|
143
|
+
- Live Figma selection is not implemented. True Dev Mode-style live selection requires a future local Figma plugin/bridge.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface CodeConnectScanOptions {
|
|
5
|
+
fileKey: string;
|
|
6
|
+
nodeId?: string;
|
|
7
|
+
componentKey?: string;
|
|
8
|
+
rootDir?: string;
|
|
9
|
+
cwd: string;
|
|
10
|
+
maxMatches?: number;
|
|
11
|
+
maxFiles?: number;
|
|
12
|
+
maxFileBytes?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CodeConnectMatch {
|
|
16
|
+
path: string;
|
|
17
|
+
line: number;
|
|
18
|
+
kind: "figma-connect" | "figma-config" | "figma-file-reference" | "figma-node-reference" | "component-key-reference";
|
|
19
|
+
preview: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CodeConnectScanResult {
|
|
23
|
+
rootDir: string;
|
|
24
|
+
matches: CodeConnectMatch[];
|
|
25
|
+
metadata: { truncated: boolean; truncatedReasons: string[]; nextSteps: string[] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const IGNORE_DIRS = new Set(["node_modules", "dist", "build", ".git", "coverage", ".next", ".turbo", ".cache"]);
|
|
29
|
+
const DEFAULT_MAX_MATCHES = 40;
|
|
30
|
+
const DEFAULT_MAX_FILES = 1500;
|
|
31
|
+
const DEFAULT_MAX_FILE_BYTES = 300_000;
|
|
32
|
+
|
|
33
|
+
export async function findCodeConnectMapping(options: CodeConnectScanOptions): Promise<CodeConnectScanResult> {
|
|
34
|
+
const rootDir = resolveRoot(options.cwd, options.rootDir);
|
|
35
|
+
const maxMatches = clampInteger(options.maxMatches ?? DEFAULT_MAX_MATCHES, 1, 200);
|
|
36
|
+
const maxFiles = clampInteger(options.maxFiles ?? DEFAULT_MAX_FILES, 1, 10_000);
|
|
37
|
+
const maxFileBytes = clampInteger(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES, 1_000, 2_000_000);
|
|
38
|
+
const normalizedNodeId = options.nodeId?.replace(/-/g, ":");
|
|
39
|
+
const urlNodeId = normalizedNodeId?.replace(/:/g, "-");
|
|
40
|
+
const matches: CodeConnectMatch[] = [];
|
|
41
|
+
const truncatedReasons: string[] = [];
|
|
42
|
+
let filesSeen = 0;
|
|
43
|
+
|
|
44
|
+
async function scanDir(dir: string): Promise<void> {
|
|
45
|
+
if (filesSeen >= maxFiles || matches.length >= maxMatches) return;
|
|
46
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
if (IGNORE_DIRS.has(entry.name) || (entry.name.startsWith(".") && !entry.name.startsWith(".figma"))) continue;
|
|
49
|
+
await scanDir(join(dir, entry.name));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!entry.isFile() || !isLikelySource(entry.name)) continue;
|
|
53
|
+
filesSeen += 1;
|
|
54
|
+
if (filesSeen > maxFiles) {
|
|
55
|
+
truncatedReasons.push(`Scanned file cap ${maxFiles} reached.`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const filePath = join(dir, entry.name);
|
|
59
|
+
const info = await stat(filePath);
|
|
60
|
+
if (info.size > maxFileBytes) continue;
|
|
61
|
+
await scanFile(filePath);
|
|
62
|
+
if (matches.length >= maxMatches) {
|
|
63
|
+
truncatedReasons.push(`Reached maxMatches ${maxMatches}; additional matches were omitted.`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function scanFile(filePath: string): Promise<void> {
|
|
70
|
+
const text = await readFile(filePath, "utf8");
|
|
71
|
+
const lines = text.split(/\r?\n/);
|
|
72
|
+
lines.forEach((line, index) => {
|
|
73
|
+
for (const kind of classify(line, filePath, options.fileKey, normalizedNodeId, urlNodeId, options.componentKey)) {
|
|
74
|
+
if (matches.length < maxMatches) matches.push({ path: relative(rootDir, filePath), line: index + 1, kind, preview: line.trim().slice(0, 240) });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await scanDir(rootDir);
|
|
80
|
+
const nextSteps = matches.length ? ["Open matched files to inspect local component props and implementation conventions."] : ["No local Code Connect mapping was found; use Figma implementation context and existing component search next."];
|
|
81
|
+
if (truncatedReasons.length) nextSteps.push("Narrow rootDir or raise maxMatches/maxFiles if you expect more mappings.");
|
|
82
|
+
return { rootDir, matches, metadata: { truncated: truncatedReasons.length > 0, truncatedReasons: [...new Set(truncatedReasons)], nextSteps } };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function classify(line: string, filePath: string, fileKey: string, nodeId?: string, urlNodeId?: string, componentKey?: string): Array<CodeConnectMatch["kind"]> {
|
|
86
|
+
const lowerPath = filePath.toLowerCase();
|
|
87
|
+
const kinds: Array<CodeConnectMatch["kind"]> = [];
|
|
88
|
+
if (/figma\.connect\s*\(/.test(line)) kinds.push("figma-connect");
|
|
89
|
+
if ((/figma\.config\.|figma\.config\.(js|ts|mjs|cjs)$/.test(line) || /figma\.config\./.test(lowerPath) || /\.figma\./.test(lowerPath)) && line.trim()) kinds.push("figma-config");
|
|
90
|
+
if (line.includes(fileKey)) kinds.push("figma-file-reference");
|
|
91
|
+
if (nodeId && (line.includes(nodeId) || (urlNodeId && line.includes(urlNodeId)))) kinds.push("figma-node-reference");
|
|
92
|
+
if (componentKey && line.includes(componentKey)) kinds.push("component-key-reference");
|
|
93
|
+
return kinds;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveRoot(cwd: string, rootDir?: string): string {
|
|
97
|
+
if (!rootDir) return cwd;
|
|
98
|
+
const resolved = isAbsolute(rootDir) ? resolve(rootDir) : resolve(cwd, rootDir);
|
|
99
|
+
const cwdResolved = resolve(cwd);
|
|
100
|
+
if (resolved !== cwdResolved && !resolved.startsWith(`${cwdResolved}/`)) throw new Error("rootDir must be inside the current working directory.");
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isLikelySource(name: string): boolean {
|
|
105
|
+
return /(?:figma\.config\..*|\.figma\..*|\.(tsx?|jsx?|vue|svelte|mdx?|json|ya?ml))$/i.test(name);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function clampInteger(value: number, min: number, max: number): number {
|
|
109
|
+
return Math.max(min, Math.min(max, Math.trunc(value)));
|
|
110
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, extname } from "node:path";
|
|
4
|
+
|
|
5
|
+
export type FigmaAssetType = "svgIcons" | "nodeRenders" | "imageFills";
|
|
6
|
+
export type FigmaAssetKind = "svgIcon" | "nodeRender" | "imageFill";
|
|
7
|
+
|
|
8
|
+
export interface FigmaAssetCandidate {
|
|
9
|
+
kind: FigmaAssetKind;
|
|
10
|
+
nodeId?: string;
|
|
11
|
+
nodeName?: string;
|
|
12
|
+
nodeType?: string;
|
|
13
|
+
nodePath: string;
|
|
14
|
+
imageRef?: string;
|
|
15
|
+
fillIndex?: number;
|
|
16
|
+
suggestedName: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FigmaAssetManifestEntry extends FigmaAssetCandidate {
|
|
20
|
+
format: "png" | "jpg" | "svg" | "webp" | "unknown";
|
|
21
|
+
path?: string;
|
|
22
|
+
url?: string | null;
|
|
23
|
+
sha256?: string;
|
|
24
|
+
bytes?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface FigmaAssetCollectionResult {
|
|
28
|
+
assets: FigmaAssetCandidate[];
|
|
29
|
+
metadata: { truncated: boolean; truncatedReasons: string[]; nextSteps: string[] };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FigmaExtractAssetsResult {
|
|
33
|
+
nodeId: string;
|
|
34
|
+
assetTypes: FigmaAssetType[];
|
|
35
|
+
assets: FigmaAssetManifestEntry[];
|
|
36
|
+
metadata: { truncated: boolean; truncatedReasons: string[]; nextSteps: string[] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const VECTOR_TYPES = new Set(["VECTOR", "BOOLEAN_OPERATION", "STAR", "LINE", "ELLIPSE", "POLYGON", "REGULAR_POLYGON"]);
|
|
40
|
+
const DEFAULT_MAX_ASSETS = 80;
|
|
41
|
+
|
|
42
|
+
export function collectAssetCandidates(node: unknown, options: { assetTypes?: FigmaAssetType[]; includeHidden?: boolean; maxAssets?: number } = {}): FigmaAssetCollectionResult {
|
|
43
|
+
const assetTypes = new Set(options.assetTypes?.length ? options.assetTypes : (["svgIcons", "nodeRenders", "imageFills"] as FigmaAssetType[]));
|
|
44
|
+
const maxAssets = clampInteger(options.maxAssets ?? DEFAULT_MAX_ASSETS, 1, 500);
|
|
45
|
+
const assets: FigmaAssetCandidate[] = [];
|
|
46
|
+
const truncatedReasons: string[] = [];
|
|
47
|
+
|
|
48
|
+
function push(candidate: FigmaAssetCandidate): void {
|
|
49
|
+
if (assets.length >= maxAssets) {
|
|
50
|
+
if (!truncatedReasons.some((reason) => reason.includes("maxAssets"))) truncatedReasons.push(`Reached maxAssets ${maxAssets}; additional assets were omitted.`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
assets.push(candidate);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
walk(node, options.includeHidden ?? false, (record, path, level) => {
|
|
57
|
+
const name = String(record.name ?? "Asset");
|
|
58
|
+
const type = String(record.type ?? "UNKNOWN");
|
|
59
|
+
const id = stringValue(record.id);
|
|
60
|
+
if (assetTypes.has("svgIcons") && id && isIconCandidate(record)) {
|
|
61
|
+
push({ kind: "svgIcon", nodeId: id, nodeName: name, nodeType: type, nodePath: path, suggestedName: `${safeFilename(name || id)}.svg` });
|
|
62
|
+
}
|
|
63
|
+
if (assetTypes.has("nodeRenders") && id && (level === 0 || /asset|illustration|image|card|avatar|logo/i.test(name))) {
|
|
64
|
+
push({ kind: "nodeRender", nodeId: id, nodeName: name, nodeType: type, nodePath: path, suggestedName: `${safeFilename(name || id)}.png` });
|
|
65
|
+
}
|
|
66
|
+
if (assetTypes.has("imageFills")) {
|
|
67
|
+
const fills = Array.isArray(record.fills) ? record.fills : [];
|
|
68
|
+
fills.forEach((fill, index) => {
|
|
69
|
+
const fillRecord = asRecord(fill);
|
|
70
|
+
const imageRef = stringValue(fillRecord.imageRef);
|
|
71
|
+
if (imageRef) push({ kind: "imageFill", nodeId: id, nodeName: name, nodeType: type, nodePath: path, imageRef, fillIndex: index, suggestedName: `${safeFilename(name || imageRef)}.${formatFromScaleMode(fillRecord.scaleMode)}` });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
assets,
|
|
78
|
+
metadata: {
|
|
79
|
+
truncated: truncatedReasons.length > 0,
|
|
80
|
+
truncatedReasons,
|
|
81
|
+
nextSteps: truncatedReasons.length ? ["Raise maxAssets or extract assets from a narrower child node."] : [],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function manifestEntryFromFile(candidate: FigmaAssetCandidate, filePath: string, url?: string | null, format?: FigmaAssetManifestEntry["format"]): Promise<FigmaAssetManifestEntry> {
|
|
87
|
+
const bytes = await readFile(filePath);
|
|
88
|
+
return { ...candidate, path: filePath, url, format: format ?? formatFromPath(filePath), sha256: sha256(bytes), bytes: bytes.byteLength };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function manifestEntryFromUrl(candidate: FigmaAssetCandidate, url: string | null | undefined, format: FigmaAssetManifestEntry["format"]): FigmaAssetManifestEntry {
|
|
92
|
+
return { ...candidate, url: url ?? null, format };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function sha256(value: Buffer | Uint8Array | string): string {
|
|
96
|
+
return createHash("sha256").update(value).digest("hex");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function safeFilename(value: string): string {
|
|
100
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase() || "figma-asset";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function formatFromPath(path: string): FigmaAssetManifestEntry["format"] {
|
|
104
|
+
const extension = extname(path || basename(path)).toLowerCase().replace(/^\./, "");
|
|
105
|
+
if (extension === "png" || extension === "jpg" || extension === "svg" || extension === "webp") return extension;
|
|
106
|
+
if (extension === "jpeg") return "jpg";
|
|
107
|
+
return "unknown";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatFromScaleMode(_value: unknown): "png" {
|
|
111
|
+
return "png";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isIconCandidate(record: Record<string, unknown>): boolean {
|
|
115
|
+
const type = String(record.type ?? "UNKNOWN");
|
|
116
|
+
const name = String(record.name ?? "");
|
|
117
|
+
const box = asRecord(record.absoluteBoundingBox);
|
|
118
|
+
const width = numberValue(box.width) ?? 0;
|
|
119
|
+
const height = numberValue(box.height) ?? 0;
|
|
120
|
+
return VECTOR_TYPES.has(type) || /\b(icon|logo|glyph)\b/i.test(name) || (width > 0 && height > 0 && width <= 64 && height <= 64 && !Array.isArray(record.children));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function walk(node: unknown, includeHidden: boolean, visit: (record: Record<string, unknown>, path: string, level: number) => void, path = "", level = 0): void {
|
|
124
|
+
const record = asRecord(node);
|
|
125
|
+
if (!includeHidden && record.visible === false) return;
|
|
126
|
+
const name = String(record.name ?? "Unnamed node");
|
|
127
|
+
const nextPath = path ? `${path} > ${name}` : name;
|
|
128
|
+
visit(record, nextPath, level);
|
|
129
|
+
for (const child of Array.isArray(record.children) ? record.children : []) walk(child, includeHidden, visit, nextPath, level + 1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
133
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function stringValue(value: unknown): string | undefined {
|
|
137
|
+
return typeof value === "string" ? value : undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function numberValue(value: unknown): number | undefined {
|
|
141
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function clampInteger(value: number, min: number, max: number): number {
|
|
145
|
+
return Math.max(min, Math.min(max, Math.trunc(value)));
|
|
146
|
+
}
|