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.
Files changed (161) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENCE.md +7 -0
  3. package/node_modules/pi-common/package.json +22 -0
  4. package/node_modules/pi-common/src/auth-config.ts +290 -0
  5. package/node_modules/pi-common/src/auth.ts +63 -0
  6. package/node_modules/pi-common/src/cache.ts +60 -0
  7. package/node_modules/pi-common/src/errors.ts +47 -0
  8. package/node_modules/pi-common/src/http-client.ts +118 -0
  9. package/node_modules/pi-common/src/index.ts +7 -0
  10. package/node_modules/pi-common/src/rate-limiter.ts +32 -0
  11. package/node_modules/pi-common/src/tool-result.ts +27 -0
  12. package/node_modules/pi-mono-ask-user-question/CHANGELOG.md +185 -0
  13. package/node_modules/pi-mono-ask-user-question/README.md +226 -0
  14. package/node_modules/pi-mono-ask-user-question/index.ts +923 -0
  15. package/node_modules/pi-mono-ask-user-question/package.json +29 -0
  16. package/node_modules/pi-mono-auto-fix/CHANGELOG.md +59 -0
  17. package/node_modules/pi-mono-auto-fix/README.md +77 -0
  18. package/node_modules/pi-mono-auto-fix/index.ts +488 -0
  19. package/node_modules/pi-mono-auto-fix/package.json +23 -0
  20. package/node_modules/pi-mono-btw/CHANGELOG.md +180 -0
  21. package/node_modules/pi-mono-btw/README.md +24 -0
  22. package/node_modules/pi-mono-btw/index.ts +499 -0
  23. package/node_modules/pi-mono-btw/package.json +29 -0
  24. package/node_modules/pi-mono-clear/CHANGELOG.md +180 -0
  25. package/node_modules/pi-mono-clear/README.md +40 -0
  26. package/node_modules/pi-mono-clear/index.ts +45 -0
  27. package/node_modules/pi-mono-clear/package.json +29 -0
  28. package/node_modules/pi-mono-context/CHANGELOG.md +12 -0
  29. package/node_modules/pi-mono-context/README.md +74 -0
  30. package/node_modules/pi-mono-context/index.ts +641 -0
  31. package/node_modules/pi-mono-context/package.json +29 -0
  32. package/node_modules/pi-mono-context-guard/CHANGELOG.md +195 -0
  33. package/node_modules/pi-mono-context-guard/README.md +81 -0
  34. package/node_modules/pi-mono-context-guard/index.ts +212 -0
  35. package/node_modules/pi-mono-context-guard/package.json +23 -0
  36. package/node_modules/pi-mono-figma/CHANGELOG.md +59 -0
  37. package/node_modules/pi-mono-figma/README.md +236 -0
  38. package/node_modules/pi-mono-figma/__tests__/code-connect.test.ts +32 -0
  39. package/node_modules/pi-mono-figma/__tests__/figma-assets.test.ts +38 -0
  40. package/node_modules/pi-mono-figma/__tests__/figma-component-hints.test.ts +23 -0
  41. package/node_modules/pi-mono-figma/__tests__/figma-implementation-layout.test.ts +47 -0
  42. package/node_modules/pi-mono-figma/__tests__/figma-search.test.ts +51 -0
  43. package/node_modules/pi-mono-figma/__tests__/figma-summarizer.test.ts +65 -0
  44. package/node_modules/pi-mono-figma/__tests__/fixtures/complex-auto-layout.json +115 -0
  45. package/node_modules/pi-mono-figma/__tests__/fixtures/component-instance.json +50 -0
  46. package/node_modules/pi-mono-figma/__tests__/fixtures/hidden-and-vectors.json +28 -0
  47. package/node_modules/pi-mono-figma/__tests__/fixtures/variables-and-styles.json +40 -0
  48. package/node_modules/pi-mono-figma/docs/live-selection-bridge.md +16 -0
  49. package/node_modules/pi-mono-figma/index.ts +6 -0
  50. package/node_modules/pi-mono-figma/package.json +33 -0
  51. package/node_modules/pi-mono-figma/skills/figma/SKILL.md +143 -0
  52. package/node_modules/pi-mono-figma/src/code-connect.ts +110 -0
  53. package/node_modules/pi-mono-figma/src/figma-assets.ts +146 -0
  54. package/node_modules/pi-mono-figma/src/figma-cache.ts +6 -0
  55. package/node_modules/pi-mono-figma/src/figma-client.ts +471 -0
  56. package/node_modules/pi-mono-figma/src/figma-component-hints.ts +87 -0
  57. package/node_modules/pi-mono-figma/src/figma-implementation.ts +264 -0
  58. package/node_modules/pi-mono-figma/src/figma-schemas.ts +139 -0
  59. package/node_modules/pi-mono-figma/src/figma-search.ts +195 -0
  60. package/node_modules/pi-mono-figma/src/figma-summarizer.ts +673 -0
  61. package/node_modules/pi-mono-figma/src/figma-tokens.ts +57 -0
  62. package/node_modules/pi-mono-figma/src/figma-tools.ts +352 -0
  63. package/node_modules/pi-mono-linear/CHANGELOG.md +44 -0
  64. package/node_modules/pi-mono-linear/README.md +159 -0
  65. package/node_modules/pi-mono-linear/index.ts +6 -0
  66. package/node_modules/pi-mono-linear/package.json +30 -0
  67. package/node_modules/pi-mono-linear/skills/linear/SKILL.md +107 -0
  68. package/node_modules/pi-mono-linear/src/linear-client.ts +339 -0
  69. package/node_modules/pi-mono-linear/src/linear-queries.ts +101 -0
  70. package/node_modules/pi-mono-linear/src/linear-schemas.ts +90 -0
  71. package/node_modules/pi-mono-linear/src/linear-tools.ts +362 -0
  72. package/node_modules/pi-mono-loop/CHANGELOG.md +163 -0
  73. package/node_modules/pi-mono-loop/README.md +54 -0
  74. package/node_modules/pi-mono-loop/index.ts +291 -0
  75. package/node_modules/pi-mono-loop/package.json +26 -0
  76. package/node_modules/pi-mono-multi-edit/CHANGELOG.md +232 -0
  77. package/node_modules/pi-mono-multi-edit/README.md +244 -0
  78. package/node_modules/pi-mono-multi-edit/__tests__/classic.test.ts +277 -0
  79. package/node_modules/pi-mono-multi-edit/__tests__/diff.test.ts +77 -0
  80. package/node_modules/pi-mono-multi-edit/__tests__/patch.test.ts +287 -0
  81. package/node_modules/pi-mono-multi-edit/benchmark-edits.ts +966 -0
  82. package/node_modules/pi-mono-multi-edit/classic.ts +435 -0
  83. package/node_modules/pi-mono-multi-edit/diff.ts +143 -0
  84. package/node_modules/pi-mono-multi-edit/index.ts +266 -0
  85. package/node_modules/pi-mono-multi-edit/package.json +37 -0
  86. package/node_modules/pi-mono-multi-edit/patch.ts +463 -0
  87. package/node_modules/pi-mono-multi-edit/types.ts +53 -0
  88. package/node_modules/pi-mono-multi-edit/workspace.ts +85 -0
  89. package/node_modules/pi-mono-review/CHANGELOG.md +190 -0
  90. package/node_modules/pi-mono-review/README.md +30 -0
  91. package/node_modules/pi-mono-review/common.ts +930 -0
  92. package/node_modules/pi-mono-review/index.ts +8 -0
  93. package/node_modules/pi-mono-review/package.json +29 -0
  94. package/node_modules/pi-mono-review/review-tui.ts +194 -0
  95. package/node_modules/pi-mono-review/review.ts +119 -0
  96. package/node_modules/pi-mono-review/reviewer.ts +339 -0
  97. package/node_modules/pi-mono-sentinel/CHANGELOG.md +158 -0
  98. package/node_modules/pi-mono-sentinel/README.md +87 -0
  99. package/node_modules/pi-mono-sentinel/__tests__/output-scanner.test.ts +109 -0
  100. package/node_modules/pi-mono-sentinel/__tests__/permissions.test.ts +202 -0
  101. package/node_modules/pi-mono-sentinel/__tests__/whitelist.test.ts +59 -0
  102. package/node_modules/pi-mono-sentinel/guards/execution-tracker.ts +281 -0
  103. package/node_modules/pi-mono-sentinel/guards/output-scanner.ts +232 -0
  104. package/node_modules/pi-mono-sentinel/guards/permission-gate.ts +170 -0
  105. package/node_modules/pi-mono-sentinel/index.ts +43 -0
  106. package/node_modules/pi-mono-sentinel/package.json +26 -0
  107. package/node_modules/pi-mono-sentinel/patterns/permissions.ts +175 -0
  108. package/node_modules/pi-mono-sentinel/patterns/read-targets.ts +104 -0
  109. package/node_modules/pi-mono-sentinel/patterns/secrets.ts +143 -0
  110. package/node_modules/pi-mono-sentinel/session.ts +95 -0
  111. package/node_modules/pi-mono-sentinel/specs/2026/04/sentinel/001-permission-gate.md +145 -0
  112. package/node_modules/pi-mono-sentinel/types.ts +39 -0
  113. package/node_modules/pi-mono-sentinel/whitelist.ts +86 -0
  114. package/node_modules/pi-mono-simplify/CHANGELOG.md +163 -0
  115. package/node_modules/pi-mono-simplify/README.md +56 -0
  116. package/node_modules/pi-mono-simplify/index.ts +78 -0
  117. package/node_modules/pi-mono-simplify/package.json +29 -0
  118. package/node_modules/pi-mono-status-line/CHANGELOG.md +180 -0
  119. package/node_modules/pi-mono-status-line/README.md +96 -0
  120. package/node_modules/pi-mono-status-line/basic.ts +89 -0
  121. package/node_modules/pi-mono-status-line/expert.ts +689 -0
  122. package/node_modules/pi-mono-status-line/index.ts +54 -0
  123. package/node_modules/pi-mono-status-line/package.json +29 -0
  124. package/node_modules/pi-mono-team-mode/CHANGELOG.md +278 -0
  125. package/node_modules/pi-mono-team-mode/README.md +246 -0
  126. package/node_modules/pi-mono-team-mode/__tests__/agent-manager-transient.test.ts +75 -0
  127. package/node_modules/pi-mono-team-mode/__tests__/delegation-manager.test.ts +118 -0
  128. package/node_modules/pi-mono-team-mode/__tests__/formatters.test.ts +104 -0
  129. package/node_modules/pi-mono-team-mode/__tests__/model-config.test.ts +272 -0
  130. package/node_modules/pi-mono-team-mode/__tests__/notification-box.test.ts +34 -0
  131. package/node_modules/pi-mono-team-mode/__tests__/parallel-utils.test.ts +32 -0
  132. package/node_modules/pi-mono-team-mode/__tests__/pi-stream-parser.test.ts +64 -0
  133. package/node_modules/pi-mono-team-mode/__tests__/prompts.test.ts +106 -0
  134. package/node_modules/pi-mono-team-mode/__tests__/store.test.ts +164 -0
  135. package/node_modules/pi-mono-team-mode/__tests__/tasks.test.ts +267 -0
  136. package/node_modules/pi-mono-team-mode/__tests__/teammate-specs.test.ts +114 -0
  137. package/node_modules/pi-mono-team-mode/__tests__/widget.test.ts +41 -0
  138. package/node_modules/pi-mono-team-mode/__tests__/worktree.test.ts +78 -0
  139. package/node_modules/pi-mono-team-mode/core/chain-utils.ts +90 -0
  140. package/node_modules/pi-mono-team-mode/core/fs-utils.ts +44 -0
  141. package/node_modules/pi-mono-team-mode/core/model-config.ts +432 -0
  142. package/node_modules/pi-mono-team-mode/core/parallel-utils.ts +48 -0
  143. package/node_modules/pi-mono-team-mode/core/prompts.ts +158 -0
  144. package/node_modules/pi-mono-team-mode/core/store.ts +156 -0
  145. package/node_modules/pi-mono-team-mode/core/tasks.ts +99 -0
  146. package/node_modules/pi-mono-team-mode/core/teammate-specs.ts +124 -0
  147. package/node_modules/pi-mono-team-mode/core/types.ts +160 -0
  148. package/node_modules/pi-mono-team-mode/index.ts +825 -0
  149. package/node_modules/pi-mono-team-mode/managers/agent-manager.ts +654 -0
  150. package/node_modules/pi-mono-team-mode/managers/delegation-manager.ts +211 -0
  151. package/node_modules/pi-mono-team-mode/managers/task-manager.ts +238 -0
  152. package/node_modules/pi-mono-team-mode/managers/team-manager.ts +59 -0
  153. package/node_modules/pi-mono-team-mode/package.json +33 -0
  154. package/node_modules/pi-mono-team-mode/runtime/pi-stream-parser.ts +194 -0
  155. package/node_modules/pi-mono-team-mode/runtime/subprocess.ts +183 -0
  156. package/node_modules/pi-mono-team-mode/runtime/transient-session.ts +196 -0
  157. package/node_modules/pi-mono-team-mode/runtime/worktree.ts +90 -0
  158. package/node_modules/pi-mono-team-mode/ui/formatters.ts +149 -0
  159. package/node_modules/pi-mono-team-mode/ui/notification-box.ts +55 -0
  160. package/node_modules/pi-mono-team-mode/ui/widget.ts +94 -0
  161. package/package.json +76 -0
@@ -0,0 +1,236 @@
1
+ # pi-mono-figma
2
+
3
+ A pi extension and skill package that exposes native Figma tools for design exploration and design-to-code workflows. The default tools return compact, LLM-ready design context instead of raw Figma JSON.
4
+
5
+ ## Benefits over Figma MCP
6
+
7
+ This package is Pi-native and uses Figma's REST API directly, with tools designed specifically for LLM-friendly design exploration and design-to-code workflows.
8
+
9
+ - **No hosted Figma MCP quota path:** the extension calls Figma's REST API directly instead of using a hosted Claude/Figma connector quota path. It is still subject to Figma API limits, and the client smooths calls with a fixed 1s limiter plus a 5-minute TTL cache.
10
+ - **Better LLM-shaped output:** `figma_get_node_summary`, `figma_explain_node`, and `figma_get_implementation_context` avoid raw Figma JSON by default. They cap depth, skip hidden nodes, vector internals, and component internals unless requested, and return `metadata.nextSteps` when follow-up inspection would help.
11
+ - **Design-to-code specialization:** `figma_get_implementation_context` extracts fields, buttons, layout measurements, typography, colors, spacing, CSS/flex/grid hints, responsive guidance, accessibility hints, design tokens, assets, and optional framework starter snippets instead of simply relaying generic server output.
12
+ - **Safer local auth UX:** `figma_configure_auth` uses masked local prompting and stores the token in Pi auth storage. The model never sees the token.
13
+ - **Good raw escape hatches:** raw `figma_get_file` and `figma_get_nodes` tools are available for debugging, while tool descriptions steer agents toward processed tools first.
14
+ - **Local asset handling:** `figma_render_nodes` can download rendered images to an OS temp directory by default, while `figma_extract_assets` returns a manifest for SVG icons, node renders, and image fills with node paths, hashes, byte sizes, and suggested names. Persistent project directories are used only when `outputDir` is explicitly provided.
15
+ - **Broader inspection surface:** styles, variables, components, component sets, component search, metadata, text extraction, rendering, summaries, explanations, and implementation context are exposed as separate native tools.
16
+
17
+ ## Tools
18
+
19
+ ### Processed, LLM-ready tools (preferred)
20
+
21
+ - `figma_parse_url` — parse a Figma URL into `fileKey` and `nodeId`.
22
+ - `figma_find_nodes_by_name` — search layer/node names in a file or subtree and return compact path-aware matches.
23
+ - `figma_find_nodes_by_text` — search visible text in a file or subtree and return matches with nearest parent context.
24
+ - `figma_render_nodes` — render node image URLs and optionally download assets locally. Downloads use an OS temp directory by default unless the user explicitly provides `outputDir`.
25
+ - `figma_get_node_summary` — fetch a compact structured summary of a node: name, type, size, layout, spacing, visual style, visible text, component properties, and shallow child hierarchy.
26
+ - `figma_extract_text` — return visible text nodes only.
27
+ - `figma_explain_node` — explain a node in Markdown using summary, visible text, hierarchy, and optional rendered asset.
28
+ - `figma_get_implementation_context` — return coding-ready design context: purpose, sections, fields/buttons, measurements, typography, colors, spacing, CSS layout, responsive hints, accessibility hints, design token resolution, assets, component hierarchy, and optional snippets.
29
+ - `figma_extract_assets` — extract SVG/icon exports, node renders, and image fills into a node-path manifest with hashes and local paths.
30
+ - `figma_find_code_connect_mapping` — scan the current repo for Code Connect files, `figma.connect(...)`, Figma URLs/node IDs, and component key references.
31
+ - `figma_get_component_implementation_hints` — combine summary, implementation context, variants/properties, tokens, assets, accessibility, optional Code Connect matches, and starter snippets.
32
+ - `figma_get_design_context` — fetch compact file context. With `nodeId`, returns target node summary plus location/sibling context; without `nodeId`, returns canvases and top-level frames only.
33
+ - `figma_get_node_metadata` — fetch compact spatial/layout metadata for one or more nodes.
34
+ - `figma_get_styles` — fetch named styles.
35
+ - `figma_get_variables` — fetch local variables/collections for design tokens.
36
+ - `figma_get_components` — fetch component metadata.
37
+ - `figma_get_component_sets` — fetch component set metadata.
38
+ - `figma_search_components` — search components by name or description.
39
+ - `figma_configure_auth` — securely prompt for and store a Figma token without exposing it to the model.
40
+
41
+ ### Raw escape hatches
42
+
43
+ - `figma_get_file` — fetch raw Figma file JSON. Use only when raw Figma JSON is explicitly needed or when debugging the extension.
44
+ - `figma_get_nodes` — fetch raw node JSON. Use only when raw Figma JSON is explicitly needed or when debugging the extension.
45
+
46
+ The package also bundles the `figma` skill under `skills/figma/SKILL.md`.
47
+
48
+ ## Recommended workflows
49
+
50
+ ### Explaining a component
51
+
52
+ ```text
53
+ figma_parse_url
54
+ figma_render_nodes
55
+ figma_explain_node
56
+ ```
57
+
58
+ ### Implementing a design
59
+
60
+ ```text
61
+ figma_parse_url
62
+ figma_find_nodes_by_name or figma_find_nodes_by_text when the URL does not include the exact target node
63
+ figma_render_nodes
64
+ figma_get_implementation_context with framework/styling when useful
65
+ figma_get_node_summary for specific subnodes if needed
66
+ ```
67
+
68
+ Example implementation-context options:
69
+
70
+ ```ts
71
+ {
72
+ framework: "react",
73
+ styling: "styled-components",
74
+ resolveTokens: true,
75
+ includeCodeSnippets: true
76
+ }
77
+ ```
78
+
79
+ ### Finding a frame or layer before implementation
80
+
81
+ ```text
82
+ figma_get_design_context
83
+ figma_find_nodes_by_name query="Checkout" nodeId=<top-level-frame-if-known>
84
+ figma_find_nodes_by_text query="Submit" nodeId=<candidate-frame>
85
+ ```
86
+
87
+ ### Extracting assets for a frame
88
+
89
+ ```text
90
+ figma_extract_assets assetTypes=["svgIcons", "nodeRenders", "imageFills"]
91
+ ```
92
+
93
+ Use returned `nodePath`, `suggestedName`, `sha256`, and `bytes` to map downloaded files back to Figma layers and avoid duplicate assets.
94
+ Omit `outputDir` unless the user asked for files to be saved in a persistent project location; the default is an OS temp directory.
95
+
96
+ ### Finding local Code Connect mappings
97
+
98
+ ```text
99
+ figma_find_code_connect_mapping fileKey=<fileKey> nodeId=<nodeId>
100
+ figma_get_component_implementation_hints includeCodeConnect=true framework="react"
101
+ ```
102
+
103
+ Use Code Connect matches only as local implementation hints; no Figma write access is used.
104
+
105
+ ### Debugging raw Figma data
106
+
107
+ ```text
108
+ figma_get_nodes
109
+ ```
110
+
111
+ ## Live selection boundary
112
+
113
+ This package is REST/API-based and read-only. It can inspect files, nodes, styles, variables, renders, and local repository mappings when you provide a file key/node ID, but it does **not** currently know the live selection in an open Figma desktop/browser session.
114
+
115
+ True Dev Mode-style live selection parity would require a separate local bridge or Figma plugin that captures the current selection and exposes selected file/node IDs to Pi. See [`docs/live-selection-bridge.md`](docs/live-selection-bridge.md) for a future architecture sketch.
116
+
117
+ ## Processed node options
118
+
119
+ Processed tools fetch nodes with compact depth limits and summarize safely:
120
+
121
+ ```ts
122
+ {
123
+ depth?: number; // default 2, capped at 4
124
+ includeHidden?: boolean; // default false
125
+ includeVectors?: boolean; // default false
126
+ includeComponentInternals?: boolean; // default false
127
+ framework?: "react" | "html" | "vue" | "angular" | "react-native";
128
+ styling?: "css" | "css-modules" | "styled-components" | "tailwind" | "inline";
129
+ resolveTokens?: boolean; // implementation context, default true
130
+ includeCodeSnippets?: boolean; // implementation context, default false
131
+ }
132
+ ```
133
+
134
+ Defaults intentionally avoid hidden layers, vector internals, and huge component instance trees. Increase depth or include internals only for a focused child node.
135
+
136
+ ## Output limits and truncation
137
+
138
+ Processed tools default to compact responses (~20k chars unless overridden by `maxResponseChars`) and cap common large arrays:
139
+
140
+ - visible text: 200 entries
141
+ - children: 100 entries
142
+ - depth: 4 max
143
+
144
+ When data is capped, responses include `metadata.truncated: true`, `truncatedReasons`, and `nextSteps` such as inspecting a child node or increasing depth.
145
+
146
+ ## Authentication
147
+
148
+ The extension looks for a Figma token in this order:
149
+
150
+ 1. in-memory token override created by `/figma-auth --force` or `figma_configure_auth`
151
+ 2. `FIGMA_TOKEN` environment variable
152
+ 3. `~/.pi/agent/auth.json` at `.figma.token`
153
+
154
+ If no token is found, or if Figma rejects the token as invalid/expired, the native tools can prompt you with a masked local dialog and store the replacement token without returning it to the model.
155
+
156
+ Do **not** paste tokens into an LLM chat.
157
+
158
+ ### Recommended: native auth command
159
+
160
+ Run this in pi:
161
+
162
+ ```text
163
+ /figma-auth --force
164
+ ```
165
+
166
+ The command shows the Figma token URL and required scopes, prompts for the token with masked input, and writes it to `~/.pi/agent/auth.json` at `.figma.token`.
167
+
168
+ The same flow is available to the agent through the `figma_configure_auth` tool. That tool returns only metadata such as `stored: true`; it never returns the token.
169
+
170
+ ### Option A: environment variable
171
+
172
+ For the current shell session:
173
+
174
+ ```bash
175
+ export FIGMA_TOKEN="figd_xxx"
176
+ ```
177
+
178
+ To persist it, add that export to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.) or your preferred secrets manager.
179
+
180
+ ### Option B: pi auth file
181
+
182
+ Create or update `~/.pi/agent/auth.json`:
183
+
184
+ ```json
185
+ {
186
+ "figma": {
187
+ "token": "figd_xxx"
188
+ }
189
+ }
190
+ ```
191
+
192
+ Recommended file permissions:
193
+
194
+ ```bash
195
+ mkdir -p ~/.pi/agent
196
+ chmod 700 ~/.pi/agent
197
+ chmod 600 ~/.pi/agent/auth.json
198
+ ```
199
+
200
+ If the file already contains other credentials, merge the `figma.token` entry instead of overwriting the file.
201
+
202
+ ## Creating a Figma token
203
+
204
+ 1. Open Figma.
205
+ 2. Go to **Settings → Security → Personal access tokens**.
206
+ 3. Click **Generate new token**.
207
+ 4. Name it something recognizable, for example `pi-agent`.
208
+ 5. Enable the required scopes/permissions below.
209
+ 6. Copy the token once and store it via `FIGMA_TOKEN` or `~/.pi/agent/auth.json`.
210
+
211
+ ## Required scopes / permissions
212
+
213
+ This extension is read-only. It needs permission to read file content and render nodes.
214
+
215
+ Enable:
216
+
217
+ - **File content** / read access to files
218
+
219
+ Also make sure the token has access to the specific Figma file, project, or team you want to inspect. If your Figma organization supports resource scoping, grant access only to the minimum projects/files needed.
220
+
221
+ No write/admin scopes are required.
222
+
223
+ ## Troubleshooting
224
+
225
+ ### `Token expired`
226
+
227
+ Generate a new Figma personal access token and update `FIGMA_TOKEN` or `~/.pi/agent/auth.json`.
228
+
229
+ ### Missing or inaccessible file
230
+
231
+ Confirm that:
232
+
233
+ - the file key is correct,
234
+ - the token has file-content read permission,
235
+ - the token owner can access the file in Figma,
236
+ - the node ID uses either URL format (`27399-4245`) or API format (`27399:4245`).
@@ -0,0 +1,32 @@
1
+ import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { findCodeConnectMapping } from "../src/code-connect.js";
7
+
8
+ test("findCodeConnectMapping discovers figma.connect, URLs, and node IDs", async () => {
9
+ const root = await mkdtemp(join(tmpdir(), "figma-code-connect-"));
10
+ await writeFile(join(root, "Button.figma.tsx"), "figma.connect(Button, 'https://www.figma.com/design/FILE123/Name?node-id=1-2')\n");
11
+ await writeFile(join(root, "README.md"), "component key COMPONENT123\n");
12
+ const result = await findCodeConnectMapping({ cwd: root, fileKey: "FILE123", nodeId: "1:2", componentKey: "COMPONENT123" });
13
+ assert.ok(result.matches.some((match) => match.kind === "figma-connect"));
14
+ assert.ok(result.matches.some((match) => match.kind === "figma-file-reference"));
15
+ assert.ok(result.matches.some((match) => match.kind === "component-key-reference"));
16
+ });
17
+
18
+ test("findCodeConnectMapping ignores node_modules and enforces caps", async () => {
19
+ const root = await mkdtemp(join(tmpdir(), "figma-code-connect-"));
20
+ await mkdir(join(root, "node_modules"));
21
+ await writeFile(join(root, "node_modules", "Ignored.ts"), "figma.connect(Ignored)\n");
22
+ await writeFile(join(root, "One.ts"), "figma.connect(One)\nfigma.connect(Two)\n");
23
+ const result = await findCodeConnectMapping({ cwd: root, fileKey: "FILE123", maxMatches: 1 });
24
+ assert.equal(result.matches.length, 1);
25
+ assert.equal(result.matches[0]?.path, "One.ts");
26
+ assert.equal(result.metadata.truncated, true);
27
+ });
28
+
29
+ test("findCodeConnectMapping rejects rootDir outside cwd", async () => {
30
+ const root = await mkdtemp(join(tmpdir(), "figma-code-connect-"));
31
+ await assert.rejects(() => findCodeConnectMapping({ cwd: root, rootDir: "/", fileKey: "FILE123" }), /rootDir/);
32
+ });
@@ -0,0 +1,38 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { collectAssetCandidates, formatFromPath, safeFilename, sha256 } from "../src/figma-assets.js";
4
+
5
+ const assetTree = {
6
+ id: "10:1",
7
+ name: "Hero Card",
8
+ type: "FRAME",
9
+ absoluteBoundingBox: { width: 320, height: 200 },
10
+ fills: [{ type: "IMAGE", imageRef: "abc123", scaleMode: "FILL" }],
11
+ children: [
12
+ { id: "10:2", name: "Close icon", type: "VECTOR", absoluteBoundingBox: { width: 16, height: 16 } },
13
+ { id: "10:3", name: "Logo Mark", type: "FRAME", absoluteBoundingBox: { width: 32, height: 32 } },
14
+ { id: "10:4", name: "Hidden asset", type: "VECTOR", visible: false, absoluteBoundingBox: { width: 16, height: 16 } },
15
+ ],
16
+ };
17
+
18
+ test("collectAssetCandidates detects icons, node renders, image fills, and paths", () => {
19
+ const result = collectAssetCandidates(assetTree, { assetTypes: ["svgIcons", "nodeRenders", "imageFills"] });
20
+ assert.ok(result.assets.some((asset) => asset.kind === "svgIcon" && asset.nodePath === "Hero Card > Close icon"));
21
+ assert.ok(result.assets.some((asset) => asset.kind === "nodeRender" && asset.nodePath === "Hero Card"));
22
+ assert.ok(result.assets.some((asset) => asset.kind === "imageFill" && asset.imageRef === "abc123"));
23
+ assert.equal(result.assets.some((asset) => asset.nodeName === "Hidden asset"), false);
24
+ });
25
+
26
+ test("collectAssetCandidates supports hidden nodes and caps results", () => {
27
+ const result = collectAssetCandidates(assetTree, { includeHidden: true, maxAssets: 1 });
28
+ assert.equal(result.assets.length, 1);
29
+ assert.equal(result.metadata.truncated, true);
30
+ assert.ok(result.metadata.truncatedReasons.some((reason) => reason.includes("maxAssets")));
31
+ });
32
+
33
+ test("asset helper utilities normalize names, hashes, and formats", () => {
34
+ assert.equal(safeFilename("Close Icon / Primary"), "close-icon-primary");
35
+ assert.equal(sha256("abc"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
36
+ assert.equal(formatFromPath("/tmp/icon.svg"), "svg");
37
+ assert.equal(formatFromPath("/tmp/photo.jpeg"), "jpg");
38
+ });
@@ -0,0 +1,23 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { buildComponentImplementationHints } from "../src/figma-component-hints.js";
6
+ import { getImplementationContext, summarizeNode } from "../src/figma-summarizer.js";
7
+
8
+ async function fixture(name: string): Promise<unknown> {
9
+ return JSON.parse(await readFile(join(import.meta.dirname, "fixtures", name), "utf8"));
10
+ }
11
+
12
+ test("buildComponentImplementationHints combines summary, variants, accessibility, tokens, and snippets", async () => {
13
+ const node = await fixture("component-instance.json");
14
+ const summary = summarizeNode(node, { depth: 3 });
15
+ const context = getImplementationContext(node, { framework: "react", includeCodeSnippets: true });
16
+ const hints = buildComponentImplementationHints(summary, context, { framework: "react", includeSnippet: true, includeCodeConnect: true }, { rootDir: "/repo", matches: [], metadata: { truncated: false, truncatedReasons: [], nextSteps: [] } });
17
+ assert.equal(hints.componentName, "SettingsModal");
18
+ assert.ok(hints.suggestedProps.some((prop) => prop.name === "children"));
19
+ assert.ok(hints.statesAndVariants.some((variant) => variant.name === "State"));
20
+ assert.ok(hints.accessibilityRequirements.some((hint) => hint.role === "dialog" || hint.role === "button"));
21
+ assert.match(String(hints.frameworkHints?.snippet), /export function SettingsModal/);
22
+ assert.ok(hints.metadata.nextSteps.some((step) => step.includes("Code Connect")));
23
+ });
@@ -0,0 +1,47 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { getImplementationContext } from "../src/figma-summarizer.js";
6
+ import { buildCssLayoutHints, buildResponsiveHints } from "../src/figma-implementation.js";
7
+
8
+ async function fixture(name: string): Promise<unknown> {
9
+ return JSON.parse(await readFile(join(import.meta.dirname, "fixtures", name), "utf8"));
10
+ }
11
+
12
+ test("buildCssLayoutHints maps auto-layout to CSS flex and grid hints", async () => {
13
+ const node = await fixture("complex-auto-layout.json");
14
+ const hints = buildCssLayoutHints(node);
15
+ assert.deepEqual((hints.css as Record<string, unknown>).display, "flex");
16
+ assert.equal((hints.css as Record<string, unknown>).flexDirection, "column");
17
+ assert.equal((hints.css as Record<string, unknown>).gap, "16px");
18
+ assert.equal((hints.css as Record<string, unknown>).padding, "20px 24px 20px 24px");
19
+ assert.ok(Array.isArray((hints.css as Record<string, unknown>).layoutGrids));
20
+ });
21
+
22
+ test("buildResponsiveHints recommends fill, hug, fixed, and wrap behavior", async () => {
23
+ const node = await fixture("complex-auto-layout.json");
24
+ const hints = buildResponsiveHints(node);
25
+ assert.ok(hints.some((hint) => String(hint.name) === "Header Row" && (hint.recommendations as string[]).some((rec) => rec.includes("width: 100%"))));
26
+ assert.ok(hints.some((hint) => String(hint.name) === "Dashboard Card" && (hint.recommendations as string[]).some((rec) => rec.includes("Fixed width"))));
27
+ });
28
+
29
+ test("implementation context includes layout, responsive, accessibility, tokens, and snippets", async () => {
30
+ const node = await fixture("variables-and-styles.json");
31
+ const context = getImplementationContext(node, {
32
+ framework: "react",
33
+ styling: "styled-components",
34
+ includeCodeSnippets: true,
35
+ tokenMap: {
36
+ styles: { "S:primary-fill": { name: "Color/Primary", type: "FILL" }, "S:text-button": { name: "Typography/Button", type: "TEXT" } },
37
+ variables: { "VariableID:color-primary": { name: "color.primary" }, "VariableID:text-on-primary": { name: "color.onPrimary" }, "VariableID:radius-md": { name: "radius.md" } },
38
+ collections: {},
39
+ warnings: [],
40
+ },
41
+ });
42
+ assert.ok(context.cssLayout);
43
+ assert.ok(context.accessibility?.some((hint) => hint.role === "button"));
44
+ assert.ok((context.designTokens?.resolved as Array<Record<string, unknown>>).some((token) => token.name === "Color/Primary"));
45
+ assert.equal(context.frameworkHints?.framework, "react");
46
+ assert.match(String(context.frameworkHints?.snippet), /styled\.section/);
47
+ });
@@ -0,0 +1,51 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { findNodesByName, findNodesByText } from "../src/figma-search.js";
6
+
7
+ async function fixture(name: string): Promise<unknown> {
8
+ return JSON.parse(await readFile(join(import.meta.dirname, "fixtures", name), "utf8"));
9
+ }
10
+
11
+ test("findNodesByName supports partial, exact, and case-sensitive matching", async () => {
12
+ const node = await fixture("complex-auto-layout.json");
13
+ assert.deepEqual(findNodesByName(node, { query: "button" }).matches.map((match) => match.name), ["Save button", "Button label"]);
14
+ assert.equal(findNodesByName(node, { query: "save button", exact: true }).matches.length, 1);
15
+ assert.equal(findNodesByName(node, { query: "save button", exact: true, caseSensitive: true }).matches.length, 0);
16
+ });
17
+
18
+ test("findNodesByText returns path and parent context", async () => {
19
+ const node = await fixture("complex-auto-layout.json");
20
+ const result = findNodesByText(node, { query: "Water risk" });
21
+ assert.equal(result.matches[0]?.text, "Water risk summary");
22
+ assert.equal(result.matches[0]?.parent?.name, "Header Row");
23
+ assert.match(result.matches[0]?.path ?? "", /Dashboard Card > Header Row > Title/);
24
+ });
25
+
26
+ test("findNodesByName respects hidden and vector filters", async () => {
27
+ const node = await fixture("hidden-and-vectors.json");
28
+ assert.equal(findNodesByName(node, { query: "Hidden" }).matches.length, 0);
29
+ assert.equal(findNodesByName(node, { query: "Hidden", includeHidden: true }).matches.length, 1);
30
+ assert.equal(findNodesByName(node, { query: "icon" }).matches.length, 0);
31
+ assert.equal(findNodesByName(node, { query: "icon", includeVectors: true }).matches.length, 1);
32
+ });
33
+
34
+ test("findNodesByText searches collapsed instance text but not vector internals", async () => {
35
+ const node = await fixture("component-instance.json");
36
+ const result = findNodesByText(node, { query: "Continue" });
37
+ assert.equal(result.matches.length, 1);
38
+ assert.equal(result.matches[0]?.parent?.name, "Primary CTA instance");
39
+ assert.ok(result.metadata.nextSteps.some((step) => step.includes("includeComponentInternals")));
40
+ });
41
+
42
+ test("findNodesByName enforces depth and result caps", async () => {
43
+ const node = await fixture("complex-auto-layout.json");
44
+ const depthLimited = findNodesByName(node, { query: "Title", depth: 1 });
45
+ assert.equal(depthLimited.matches.length, 0);
46
+ assert.ok(depthLimited.metadata.truncatedReasons.some((reason) => reason.includes("depth limit")));
47
+
48
+ const capped = findNodesByName(node, { query: "", maxResults: 1 });
49
+ assert.equal(capped.matches.length, 1);
50
+ assert.ok(capped.metadata.truncatedReasons.some((reason) => reason.includes("maxResults")));
51
+ });
@@ -0,0 +1,65 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { extractVisibleText, getImplementationContext, summarizeNode } from "../src/figma-summarizer.js";
6
+
7
+ async function fixture(name: string): Promise<unknown> {
8
+ const path = join(import.meta.dirname, "fixtures", name);
9
+ return JSON.parse(await readFile(path, "utf8"));
10
+ }
11
+
12
+ test("summarizeNode excludes hidden nodes and vector internals by default", async () => {
13
+ const node = await fixture("hidden-and-vectors.json");
14
+ const summary = summarizeNode(node, { depth: 3 });
15
+ assert.deepEqual(summary.visibleText, ["Visible copy"]);
16
+ assert.equal(summary.children?.some((child) => child.name === "Hidden text"), false);
17
+ assert.equal(summary.children?.some((child) => child.name === "Search icon"), false);
18
+ });
19
+
20
+ test("summarizeNode can include hidden nodes and vectors when requested", async () => {
21
+ const node = await fixture("hidden-and-vectors.json");
22
+ const summary = summarizeNode(node, { depth: 3, includeHidden: true, includeVectors: true });
23
+ assert.deepEqual(summary.visibleText, ["Visible copy", "Hidden copy"]);
24
+ assert.ok(summary.children?.some((child) => child.name === "Hidden text"));
25
+ assert.ok(summary.children?.some((child) => child.name === "Search icon"));
26
+ });
27
+
28
+ test("component instances collapse structural internals while retaining useful text", async () => {
29
+ const node = await fixture("component-instance.json");
30
+ const summary = summarizeNode(node, { depth: 2 });
31
+ const instance = summary.children?.find((child) => child.type === "INSTANCE");
32
+ assert.equal(instance?.children, undefined);
33
+ assert.deepEqual(instance?.text, ["Continue"]);
34
+ assert.ok(summary.metadata?.truncatedReasons.some((reason) => reason.includes("Collapsed component instance")));
35
+ });
36
+
37
+ test("extractVisibleText returns capped text metadata", async () => {
38
+ const node = await fixture("complex-auto-layout.json");
39
+ const result = extractVisibleText(node, { maxVisibleText: 1 });
40
+ assert.deepEqual(result.texts, ["Water risk summary"]);
41
+ assert.equal(result.metadata.truncated, true);
42
+ assert.ok(result.metadata.nextSteps.includes("Use figma_extract_text on a narrower child node to see more text."));
43
+ });
44
+
45
+ test("getImplementationContext deterministically extracts typography colors spacing and controls", async () => {
46
+ const node = await fixture("complex-auto-layout.json");
47
+ const context = getImplementationContext(node, { depth: 3 });
48
+ assert.match(context.purpose, /Water risk summary/);
49
+ assert.equal(context.sections.length, 2);
50
+ assert.ok(context.buttons.some((button) => button.name === "Save button"));
51
+ assert.ok(context.typography.some((entry) => entry.fontFamily === "Inter" && entry.fontSize === 18));
52
+ assert.ok(context.colors.some((entry) => entry.hex === "#ffffff"));
53
+ assert.ok(context.spacing.some((entry) => entry.itemSpacing === 16));
54
+ });
55
+
56
+ test("depth and children caps produce truncation metadata", async () => {
57
+ const node = await fixture("complex-auto-layout.json");
58
+ const depthLimited = summarizeNode(node, { depth: 1 });
59
+ assert.equal(depthLimited.metadata?.truncated, true);
60
+ assert.ok(depthLimited.metadata?.nextSteps.some((step) => step.includes("depth 2")));
61
+
62
+ const childLimited = summarizeNode(node, { depth: 2, maxChildren: 1 });
63
+ assert.equal(childLimited.children?.length, 1);
64
+ assert.ok(childLimited.metadata?.truncatedReasons.some((reason) => reason.includes("Capped children")));
65
+ });
@@ -0,0 +1,115 @@
1
+ {
2
+ "id": "1:1",
3
+ "name": "Dashboard Card",
4
+ "type": "FRAME",
5
+ "visible": true,
6
+ "absoluteBoundingBox": { "width": 360, "height": 240 },
7
+ "layoutMode": "VERTICAL",
8
+ "primaryAxisAlignItems": "MIN",
9
+ "counterAxisAlignItems": "STRETCH",
10
+ "layoutWrap": "NO_WRAP",
11
+ "layoutSizingHorizontal": "FIXED",
12
+ "layoutSizingVertical": "HUG",
13
+ "itemSpacing": 16,
14
+ "paddingLeft": 24,
15
+ "paddingRight": 24,
16
+ "paddingTop": 20,
17
+ "paddingBottom": 20,
18
+ "layoutGrids": [
19
+ { "pattern": "COLUMNS", "count": 12, "gutterSize": 16, "sectionSize": 56 }
20
+ ],
21
+ "fills": [
22
+ { "type": "SOLID", "color": { "r": 1, "g": 1, "b": 1 }, "opacity": 1 }
23
+ ],
24
+ "strokes": [
25
+ {
26
+ "type": "SOLID",
27
+ "color": { "r": 0.8, "g": 0.84, "b": 0.9 },
28
+ "opacity": 1
29
+ }
30
+ ],
31
+ "cornerRadius": 12,
32
+ "children": [
33
+ {
34
+ "id": "1:2",
35
+ "name": "Header Row",
36
+ "type": "FRAME",
37
+ "absoluteBoundingBox": { "width": 312, "height": 40 },
38
+ "layoutMode": "HORIZONTAL",
39
+ "primaryAxisAlignItems": "SPACE_BETWEEN",
40
+ "counterAxisAlignItems": "CENTER",
41
+ "layoutSizingHorizontal": "FILL",
42
+ "layoutSizingVertical": "HUG",
43
+ "itemSpacing": 8,
44
+ "children": [
45
+ {
46
+ "id": "1:3",
47
+ "name": "Title",
48
+ "type": "TEXT",
49
+ "absoluteBoundingBox": { "width": 140, "height": 24 },
50
+ "characters": "Water risk summary",
51
+ "style": {
52
+ "fontFamily": "Inter",
53
+ "fontSize": 18,
54
+ "fontWeight": 700,
55
+ "lineHeightPx": 24
56
+ },
57
+ "fills": [
58
+ {
59
+ "type": "SOLID",
60
+ "color": { "r": 0.05, "g": 0.1, "b": 0.18 },
61
+ "opacity": 1
62
+ }
63
+ ]
64
+ },
65
+ {
66
+ "id": "1:4",
67
+ "name": "More icon",
68
+ "type": "VECTOR",
69
+ "absoluteBoundingBox": { "width": 20, "height": 20 },
70
+ "fills": [
71
+ {
72
+ "type": "SOLID",
73
+ "color": { "r": 0.3, "g": 0.35, "b": 0.44 },
74
+ "opacity": 1
75
+ }
76
+ ]
77
+ }
78
+ ]
79
+ },
80
+ {
81
+ "id": "1:5",
82
+ "name": "Save button",
83
+ "type": "FRAME",
84
+ "absoluteBoundingBox": { "width": 120, "height": 40 },
85
+ "layoutMode": "HORIZONTAL",
86
+ "primaryAxisAlignItems": "CENTER",
87
+ "counterAxisAlignItems": "CENTER",
88
+ "itemSpacing": 8,
89
+ "fills": [
90
+ {
91
+ "type": "SOLID",
92
+ "color": { "r": 0.05, "g": 0.42, "b": 0.95 },
93
+ "opacity": 1
94
+ }
95
+ ],
96
+ "children": [
97
+ {
98
+ "id": "1:6",
99
+ "name": "Button label",
100
+ "type": "TEXT",
101
+ "characters": "Save",
102
+ "absoluteBoundingBox": { "width": 34, "height": 20 },
103
+ "style": { "fontFamily": "Inter", "fontSize": 14, "fontWeight": 600 },
104
+ "fills": [
105
+ {
106
+ "type": "SOLID",
107
+ "color": { "r": 1, "g": 1, "b": 1 },
108
+ "opacity": 1
109
+ }
110
+ ]
111
+ }
112
+ ]
113
+ }
114
+ ]
115
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "2:1",
3
+ "name": "Settings modal",
4
+ "type": "FRAME",
5
+ "absoluteBoundingBox": { "width": 480, "height": 360 },
6
+ "children": [
7
+ {
8
+ "id": "2:2",
9
+ "name": "Primary CTA instance",
10
+ "type": "INSTANCE",
11
+ "componentId": "99:1",
12
+ "componentSetId": "99:0",
13
+ "componentProperties": {
14
+ "State": { "type": "VARIANT", "value": "Default" },
15
+ "Disabled": { "type": "BOOLEAN", "value": false }
16
+ },
17
+ "absoluteBoundingBox": { "width": 180, "height": 44 },
18
+ "children": [
19
+ {
20
+ "id": "2:3",
21
+ "name": "Hidden icon",
22
+ "type": "VECTOR",
23
+ "absoluteBoundingBox": { "width": 16, "height": 16 }
24
+ },
25
+ {
26
+ "id": "2:4",
27
+ "name": "Label",
28
+ "type": "TEXT",
29
+ "characters": "Continue",
30
+ "absoluteBoundingBox": { "width": 70, "height": 20 },
31
+ "style": { "fontFamily": "Inter", "fontSize": 14, "fontWeight": 600 }
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "id": "2:5",
37
+ "name": "Email input field",
38
+ "type": "FRAME",
39
+ "absoluteBoundingBox": { "width": 300, "height": 48 },
40
+ "children": [
41
+ {
42
+ "id": "2:6",
43
+ "name": "Placeholder",
44
+ "type": "TEXT",
45
+ "characters": "Email address"
46
+ }
47
+ ]
48
+ }
49
+ ]
50
+ }