@llc3233149/core 0.9.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 (457) hide show
  1. package/dist/canvaskit.d.ts +9 -0
  2. package/dist/canvaskit.d.ts.map +1 -0
  3. package/dist/canvaskit.js +55 -0
  4. package/dist/canvaskit.js.map +1 -0
  5. package/dist/clipboard.d.ts +32 -0
  6. package/dist/clipboard.d.ts.map +1 -0
  7. package/dist/clipboard.js +345 -0
  8. package/dist/clipboard.js.map +1 -0
  9. package/dist/color.d.ts +28 -0
  10. package/dist/color.d.ts.map +1 -0
  11. package/dist/color.js +67 -0
  12. package/dist/color.js.map +1 -0
  13. package/dist/constants.d.ts +159 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +224 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/copy.d.ts +20 -0
  18. package/dist/copy.d.ts.map +1 -0
  19. package/dist/copy.js +64 -0
  20. package/dist/copy.js.map +1 -0
  21. package/dist/fig-export-worker.d.ts +2 -0
  22. package/dist/fig-export-worker.d.ts.map +1 -0
  23. package/dist/fig-export-worker.js +17 -0
  24. package/dist/fig-export-worker.js.map +1 -0
  25. package/dist/fig-export.d.ts +13 -0
  26. package/dist/fig-export.d.ts.map +1 -0
  27. package/dist/fig-export.js +243 -0
  28. package/dist/fig-export.js.map +1 -0
  29. package/dist/figma-api.d.ts +268 -0
  30. package/dist/figma-api.d.ts.map +1 -0
  31. package/dist/figma-api.js +1074 -0
  32. package/dist/figma-api.js.map +1 -0
  33. package/dist/figma-rest-import.d.ts +122 -0
  34. package/dist/figma-rest-import.d.ts.map +1 -0
  35. package/dist/figma-rest-import.js +319 -0
  36. package/dist/figma-rest-import.js.map +1 -0
  37. package/dist/fonts.d.ts +24 -0
  38. package/dist/fonts.d.ts.map +1 -0
  39. package/dist/fonts.js +356 -0
  40. package/dist/fonts.js.map +1 -0
  41. package/dist/geometry.d.ts +14 -0
  42. package/dist/geometry.d.ts.map +1 -0
  43. package/dist/geometry.js +47 -0
  44. package/dist/geometry.js.map +1 -0
  45. package/dist/index.d.ts +38 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +32 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/kiwi/codec.d.ts +292 -0
  50. package/dist/kiwi/codec.d.ts.map +1 -0
  51. package/dist/kiwi/codec.js +340 -0
  52. package/dist/kiwi/codec.js.map +1 -0
  53. package/dist/kiwi/fig-file.d.ts +4 -0
  54. package/dist/kiwi/fig-file.d.ts.map +1 -0
  55. package/dist/kiwi/fig-file.js +236 -0
  56. package/dist/kiwi/fig-file.js.map +1 -0
  57. package/dist/kiwi/fig-import.d.ts +4 -0
  58. package/dist/kiwi/fig-import.d.ts.map +1 -0
  59. package/dist/kiwi/fig-import.js +248 -0
  60. package/dist/kiwi/fig-import.js.map +1 -0
  61. package/dist/kiwi/fig-parse-worker.d.ts +7 -0
  62. package/dist/kiwi/fig-parse-worker.d.ts.map +1 -0
  63. package/dist/kiwi/fig-parse-worker.js +184 -0
  64. package/dist/kiwi/fig-parse-worker.js.map +1 -0
  65. package/dist/kiwi/instance-overrides.d.ts +66 -0
  66. package/dist/kiwi/instance-overrides.d.ts.map +1 -0
  67. package/dist/kiwi/instance-overrides.js +644 -0
  68. package/dist/kiwi/instance-overrides.js.map +1 -0
  69. package/dist/kiwi/kiwi-convert.d.ts +19 -0
  70. package/dist/kiwi/kiwi-convert.d.ts.map +1 -0
  71. package/dist/kiwi/kiwi-convert.js +678 -0
  72. package/dist/kiwi/kiwi-convert.js.map +1 -0
  73. package/dist/kiwi/kiwi-schema/bb.d.ts +25 -0
  74. package/dist/kiwi/kiwi-schema/bb.d.ts.map +1 -0
  75. package/dist/kiwi/kiwi-schema/bb.js +225 -0
  76. package/dist/kiwi/kiwi-schema/bb.js.map +1 -0
  77. package/dist/kiwi/kiwi-schema/binary.d.ts +5 -0
  78. package/dist/kiwi/kiwi-schema/binary.d.ts.map +1 -0
  79. package/dist/kiwi/kiwi-schema/binary.js +86 -0
  80. package/dist/kiwi/kiwi-schema/binary.js.map +1 -0
  81. package/dist/kiwi/kiwi-schema/index.d.ts +6 -0
  82. package/dist/kiwi/kiwi-schema/index.d.ts.map +1 -0
  83. package/dist/kiwi/kiwi-schema/index.js +5 -0
  84. package/dist/kiwi/kiwi-schema/index.js.map +1 -0
  85. package/dist/kiwi/kiwi-schema/js.d.ts +4 -0
  86. package/dist/kiwi/kiwi-schema/js.d.ts.map +1 -0
  87. package/dist/kiwi/kiwi-schema/js.js +282 -0
  88. package/dist/kiwi/kiwi-schema/js.js.map +1 -0
  89. package/dist/kiwi/kiwi-schema/parser.d.ts +5 -0
  90. package/dist/kiwi/kiwi-schema/parser.d.ts.map +1 -0
  91. package/dist/kiwi/kiwi-schema/parser.js +228 -0
  92. package/dist/kiwi/kiwi-schema/parser.js.map +1 -0
  93. package/dist/kiwi/kiwi-schema/schema.d.ts +22 -0
  94. package/dist/kiwi/kiwi-schema/schema.d.ts.map +1 -0
  95. package/dist/kiwi/kiwi-schema/schema.js +2 -0
  96. package/dist/kiwi/kiwi-schema/schema.js.map +1 -0
  97. package/dist/kiwi/kiwi-schema/util.d.ts +3 -0
  98. package/dist/kiwi/kiwi-schema/util.d.ts.map +1 -0
  99. package/dist/kiwi/kiwi-schema/util.js +10 -0
  100. package/dist/kiwi/kiwi-schema/util.js.map +1 -0
  101. package/dist/kiwi/protocol.d.ts +172 -0
  102. package/dist/kiwi/protocol.d.ts.map +1 -0
  103. package/dist/kiwi/protocol.js +214 -0
  104. package/dist/kiwi/protocol.js.map +1 -0
  105. package/dist/kiwi/schema.d.ts +3 -0
  106. package/dist/kiwi/schema.d.ts.map +1 -0
  107. package/dist/kiwi/schema.js +2285 -0
  108. package/dist/kiwi/schema.js.map +1 -0
  109. package/dist/kiwi-serialize.d.ts +17 -0
  110. package/dist/kiwi-serialize.d.ts.map +1 -0
  111. package/dist/kiwi-serialize.js +419 -0
  112. package/dist/kiwi-serialize.js.map +1 -0
  113. package/dist/layout.d.ts +9 -0
  114. package/dist/layout.d.ts.map +1 -0
  115. package/dist/layout.js +533 -0
  116. package/dist/layout.js.map +1 -0
  117. package/dist/profiler/draw-call-counter.d.ts +9 -0
  118. package/dist/profiler/draw-call-counter.d.ts.map +1 -0
  119. package/dist/profiler/draw-call-counter.js +37 -0
  120. package/dist/profiler/draw-call-counter.js.map +1 -0
  121. package/dist/profiler/frame-capture.d.ts +34 -0
  122. package/dist/profiler/frame-capture.d.ts.map +1 -0
  123. package/dist/profiler/frame-capture.js +90 -0
  124. package/dist/profiler/frame-capture.js.map +1 -0
  125. package/dist/profiler/frame-stats.d.ts +33 -0
  126. package/dist/profiler/frame-stats.d.ts.map +1 -0
  127. package/dist/profiler/frame-stats.js +103 -0
  128. package/dist/profiler/frame-stats.js.map +1 -0
  129. package/dist/profiler/gpu-timer.d.ts +15 -0
  130. package/dist/profiler/gpu-timer.d.ts.map +1 -0
  131. package/dist/profiler/gpu-timer.js +77 -0
  132. package/dist/profiler/gpu-timer.js.map +1 -0
  133. package/dist/profiler/hud-renderer.d.ts +22 -0
  134. package/dist/profiler/hud-renderer.d.ts.map +1 -0
  135. package/dist/profiler/hud-renderer.js +200 -0
  136. package/dist/profiler/hud-renderer.js.map +1 -0
  137. package/dist/profiler/index.d.ts +9 -0
  138. package/dist/profiler/index.d.ts.map +1 -0
  139. package/dist/profiler/index.js +8 -0
  140. package/dist/profiler/index.js.map +1 -0
  141. package/dist/profiler/phase-timer.d.ts +9 -0
  142. package/dist/profiler/phase-timer.d.ts.map +1 -0
  143. package/dist/profiler/phase-timer.js +51 -0
  144. package/dist/profiler/phase-timer.js.map +1 -0
  145. package/dist/profiler/render-profiler.d.ts +41 -0
  146. package/dist/profiler/render-profiler.d.ts.map +1 -0
  147. package/dist/profiler/render-profiler.js +142 -0
  148. package/dist/profiler/render-profiler.js.map +1 -0
  149. package/dist/render/components.d.ts +25 -0
  150. package/dist/render/components.d.ts.map +1 -0
  151. package/dist/render/components.js +55 -0
  152. package/dist/render/components.js.map +1 -0
  153. package/dist/render/emoji-icons.d.ts +5 -0
  154. package/dist/render/emoji-icons.d.ts.map +1 -0
  155. package/dist/render/emoji-icons.js +55 -0
  156. package/dist/render/emoji-icons.js.map +1 -0
  157. package/dist/render/export-code.d.ts +9 -0
  158. package/dist/render/export-code.d.ts.map +1 -0
  159. package/dist/render/export-code.js +335 -0
  160. package/dist/render/export-code.js.map +1 -0
  161. package/dist/render/export-jsx.d.ts +5 -0
  162. package/dist/render/export-jsx.d.ts.map +1 -0
  163. package/dist/render/export-jsx.js +621 -0
  164. package/dist/render/export-jsx.js.map +1 -0
  165. package/dist/render/index.d.ts +8 -0
  166. package/dist/render/index.d.ts.map +1 -0
  167. package/dist/render/index.js +8 -0
  168. package/dist/render/index.js.map +1 -0
  169. package/dist/render/jsx-runtime.d.ts +31 -0
  170. package/dist/render/jsx-runtime.d.ts.map +1 -0
  171. package/dist/render/jsx-runtime.js +13 -0
  172. package/dist/render/jsx-runtime.js.map +1 -0
  173. package/dist/render/mini-react.d.ts +14 -0
  174. package/dist/render/mini-react.d.ts.map +1 -0
  175. package/dist/render/mini-react.js +16 -0
  176. package/dist/render/mini-react.js.map +1 -0
  177. package/dist/render/render-jsx.d.ts +24 -0
  178. package/dist/render/render-jsx.d.ts.map +1 -0
  179. package/dist/render/render-jsx.js +51 -0
  180. package/dist/render/render-jsx.js.map +1 -0
  181. package/dist/render/renderer.d.ts +16 -0
  182. package/dist/render/renderer.d.ts.map +1 -0
  183. package/dist/render/renderer.js +437 -0
  184. package/dist/render/renderer.js.map +1 -0
  185. package/dist/render/tailwind.d.ts +20 -0
  186. package/dist/render/tailwind.d.ts.map +1 -0
  187. package/dist/render/tailwind.js +115 -0
  188. package/dist/render/tailwind.js.map +1 -0
  189. package/dist/render/tree.d.ts +72 -0
  190. package/dist/render/tree.d.ts.map +1 -0
  191. package/dist/render/tree.js +63 -0
  192. package/dist/render/tree.js.map +1 -0
  193. package/dist/render-image.d.ts +19 -0
  194. package/dist/render-image.d.ts.map +1 -0
  195. package/dist/render-image.js +84 -0
  196. package/dist/render-image.js.map +1 -0
  197. package/dist/renderer/effects.d.ts +9 -0
  198. package/dist/renderer/effects.d.ts.map +1 -0
  199. package/dist/renderer/effects.js +44 -0
  200. package/dist/renderer/effects.js.map +1 -0
  201. package/dist/renderer/fills.d.ts +9 -0
  202. package/dist/renderer/fills.d.ts.map +1 -0
  203. package/dist/renderer/fills.js +189 -0
  204. package/dist/renderer/fills.js.map +1 -0
  205. package/dist/renderer/index.d.ts +2 -0
  206. package/dist/renderer/index.d.ts.map +1 -0
  207. package/dist/renderer/index.js +2 -0
  208. package/dist/renderer/index.js.map +1 -0
  209. package/dist/renderer/label-cache.d.ts +43 -0
  210. package/dist/renderer/label-cache.d.ts.map +1 -0
  211. package/dist/renderer/label-cache.js +85 -0
  212. package/dist/renderer/label-cache.js.map +1 -0
  213. package/dist/renderer/labels.d.ts +6 -0
  214. package/dist/renderer/labels.d.ts.map +1 -0
  215. package/dist/renderer/labels.js +126 -0
  216. package/dist/renderer/labels.js.map +1 -0
  217. package/dist/renderer/lru-cache.d.ts +20 -0
  218. package/dist/renderer/lru-cache.d.ts.map +1 -0
  219. package/dist/renderer/lru-cache.js +65 -0
  220. package/dist/renderer/lru-cache.js.map +1 -0
  221. package/dist/renderer/overlays.d.ts +25 -0
  222. package/dist/renderer/overlays.d.ts.map +1 -0
  223. package/dist/renderer/overlays.js +783 -0
  224. package/dist/renderer/overlays.js.map +1 -0
  225. package/dist/renderer/renderer.d.ts +229 -0
  226. package/dist/renderer/renderer.d.ts.map +1 -0
  227. package/dist/renderer/renderer.js +909 -0
  228. package/dist/renderer/renderer.js.map +1 -0
  229. package/dist/renderer/rulers.d.ts +8 -0
  230. package/dist/renderer/rulers.d.ts.map +1 -0
  231. package/dist/renderer/rulers.js +147 -0
  232. package/dist/renderer/rulers.js.map +1 -0
  233. package/dist/renderer/scene.d.ts +11 -0
  234. package/dist/renderer/scene.d.ts.map +1 -0
  235. package/dist/renderer/scene.js +392 -0
  236. package/dist/renderer/scene.js.map +1 -0
  237. package/dist/renderer/shapes.d.ts +13 -0
  238. package/dist/renderer/shapes.d.ts.map +1 -0
  239. package/dist/renderer/shapes.js +160 -0
  240. package/dist/renderer/shapes.js.map +1 -0
  241. package/dist/renderer/strokes.d.ts +9 -0
  242. package/dist/renderer/strokes.d.ts.map +1 -0
  243. package/dist/renderer/strokes.js +180 -0
  244. package/dist/renderer/strokes.js.map +1 -0
  245. package/dist/rpc/commands.d.ts +202 -0
  246. package/dist/rpc/commands.d.ts.map +1 -0
  247. package/dist/rpc/commands.js +429 -0
  248. package/dist/rpc/commands.js.map +1 -0
  249. package/dist/rpc/index.d.ts +3 -0
  250. package/dist/rpc/index.d.ts.map +1 -0
  251. package/dist/rpc/index.js +2 -0
  252. package/dist/rpc/index.js.map +1 -0
  253. package/dist/scene-graph.d.ts +318 -0
  254. package/dist/scene-graph.d.ts.map +1 -0
  255. package/dist/scene-graph.js +803 -0
  256. package/dist/scene-graph.js.map +1 -0
  257. package/dist/snap.d.ts +16 -0
  258. package/dist/snap.d.ts.map +1 -0
  259. package/dist/snap.js +97 -0
  260. package/dist/snap.js.map +1 -0
  261. package/dist/style-runs.d.ts +20 -0
  262. package/dist/style-runs.d.ts.map +1 -0
  263. package/dist/style-runs.js +168 -0
  264. package/dist/style-runs.js.map +1 -0
  265. package/dist/svg-export/defs.d.ts +19 -0
  266. package/dist/svg-export/defs.d.ts.map +1 -0
  267. package/dist/svg-export/defs.js +191 -0
  268. package/dist/svg-export/defs.js.map +1 -0
  269. package/dist/svg-export/index.d.ts +8 -0
  270. package/dist/svg-export/index.d.ts.map +1 -0
  271. package/dist/svg-export/index.js +318 -0
  272. package/dist/svg-export/index.js.map +1 -0
  273. package/dist/svg-export/paths.d.ts +9 -0
  274. package/dist/svg-export/paths.d.ts.map +1 -0
  275. package/dist/svg-export/paths.js +189 -0
  276. package/dist/svg-export/paths.js.map +1 -0
  277. package/dist/svg-node.d.ts +8 -0
  278. package/dist/svg-node.d.ts.map +1 -0
  279. package/dist/svg-node.js +48 -0
  280. package/dist/svg-node.js.map +1 -0
  281. package/dist/text-editor.d.ts +57 -0
  282. package/dist/text-editor.d.ts.map +1 -0
  283. package/dist/text-editor.js +337 -0
  284. package/dist/text-editor.js.map +1 -0
  285. package/dist/tools/ai-adapter.d.ts +22 -0
  286. package/dist/tools/ai-adapter.d.ts.map +1 -0
  287. package/dist/tools/ai-adapter.js +121 -0
  288. package/dist/tools/ai-adapter.js.map +1 -0
  289. package/dist/tools/analyze.d.ts +22 -0
  290. package/dist/tools/analyze.d.ts.map +1 -0
  291. package/dist/tools/analyze.js +677 -0
  292. package/dist/tools/analyze.js.map +1 -0
  293. package/dist/tools/create.d.ts +8 -0
  294. package/dist/tools/create.d.ts.map +1 -0
  295. package/dist/tools/create.js +204 -0
  296. package/dist/tools/create.js.map +1 -0
  297. package/dist/tools/describe.d.ts +9 -0
  298. package/dist/tools/describe.d.ts.map +1 -0
  299. package/dist/tools/describe.js +295 -0
  300. package/dist/tools/describe.js.map +1 -0
  301. package/dist/tools/export-code.d.ts +2 -0
  302. package/dist/tools/export-code.d.ts.map +1 -0
  303. package/dist/tools/export-code.js +34 -0
  304. package/dist/tools/export-code.js.map +1 -0
  305. package/dist/tools/export-tailwind-config.d.ts +27 -0
  306. package/dist/tools/export-tailwind-config.d.ts.map +1 -0
  307. package/dist/tools/export-tailwind-config.js +215 -0
  308. package/dist/tools/export-tailwind-config.js.map +1 -0
  309. package/dist/tools/icons.d.ts +5 -0
  310. package/dist/tools/icons.d.ts.map +1 -0
  311. package/dist/tools/icons.js +192 -0
  312. package/dist/tools/icons.js.map +1 -0
  313. package/dist/tools/index.d.ts +7 -0
  314. package/dist/tools/index.d.ts.map +1 -0
  315. package/dist/tools/index.js +6 -0
  316. package/dist/tools/index.js.map +1 -0
  317. package/dist/tools/modify.d.ts +22 -0
  318. package/dist/tools/modify.d.ts.map +1 -0
  319. package/dist/tools/modify.js +653 -0
  320. package/dist/tools/modify.js.map +1 -0
  321. package/dist/tools/patterns.d.ts +2 -0
  322. package/dist/tools/patterns.d.ts.map +1 -0
  323. package/dist/tools/patterns.js +173 -0
  324. package/dist/tools/patterns.js.map +1 -0
  325. package/dist/tools/read.d.ts +17 -0
  326. package/dist/tools/read.d.ts.map +1 -0
  327. package/dist/tools/read.js +379 -0
  328. package/dist/tools/read.js.map +1 -0
  329. package/dist/tools/registry.d.ts +3 -0
  330. package/dist/tools/registry.d.ts.map +1 -0
  331. package/dist/tools/registry.js +124 -0
  332. package/dist/tools/registry.js.map +1 -0
  333. package/dist/tools/schema.d.ts +46 -0
  334. package/dist/tools/schema.d.ts.map +1 -0
  335. package/dist/tools/schema.js +17 -0
  336. package/dist/tools/schema.js.map +1 -0
  337. package/dist/tools/structure.d.ts +18 -0
  338. package/dist/tools/structure.d.ts.map +1 -0
  339. package/dist/tools/structure.js +345 -0
  340. package/dist/tools/structure.js.map +1 -0
  341. package/dist/tools/variables.d.ts +12 -0
  342. package/dist/tools/variables.d.ts.map +1 -0
  343. package/dist/tools/variables.js +190 -0
  344. package/dist/tools/variables.js.map +1 -0
  345. package/dist/tools/vector.d.ts +15 -0
  346. package/dist/tools/vector.d.ts.map +1 -0
  347. package/dist/tools/vector.js +312 -0
  348. package/dist/tools/vector.js.map +1 -0
  349. package/dist/types.d.ts +29 -0
  350. package/dist/types.d.ts.map +1 -0
  351. package/dist/types.js +2 -0
  352. package/dist/types.js.map +1 -0
  353. package/dist/undo.d.ts +33 -0
  354. package/dist/undo.d.ts.map +1 -0
  355. package/dist/undo.js +98 -0
  356. package/dist/undo.js.map +1 -0
  357. package/dist/vector.d.ts +14 -0
  358. package/dist/vector.d.ts.map +1 -0
  359. package/dist/vector.js +334 -0
  360. package/dist/vector.js.map +1 -0
  361. package/dist/xpath.d.ts +8 -0
  362. package/dist/xpath.d.ts.map +1 -0
  363. package/dist/xpath.js +199 -0
  364. package/dist/xpath.js.map +1 -0
  365. package/package.json +54 -0
  366. package/src/canvaskit.ts +66 -0
  367. package/src/clipboard.ts +444 -0
  368. package/src/color.ts +83 -0
  369. package/src/constants.ts +277 -0
  370. package/src/copy.ts +75 -0
  371. package/src/fig-export-worker.ts +28 -0
  372. package/src/fig-export.ts +321 -0
  373. package/src/figma-api.ts +1297 -0
  374. package/src/figma-rest-import.ts +443 -0
  375. package/src/fonts.ts +369 -0
  376. package/src/geometry.ts +73 -0
  377. package/src/global.d.ts +20 -0
  378. package/src/index.ts +247 -0
  379. package/src/kiwi/codec.ts +614 -0
  380. package/src/kiwi/fig-file.ts +248 -0
  381. package/src/kiwi/fig-import.ts +300 -0
  382. package/src/kiwi/fig-parse-worker.ts +213 -0
  383. package/src/kiwi/instance-overrides.ts +682 -0
  384. package/src/kiwi/kiwi-convert.ts +771 -0
  385. package/src/kiwi/kiwi-schema/bb.ts +244 -0
  386. package/src/kiwi/kiwi-schema/binary.ts +102 -0
  387. package/src/kiwi/kiwi-schema/index.ts +5 -0
  388. package/src/kiwi/kiwi-schema/js.ts +339 -0
  389. package/src/kiwi/kiwi-schema/parser.ts +287 -0
  390. package/src/kiwi/kiwi-schema/schema.ts +24 -0
  391. package/src/kiwi/kiwi-schema/util.ts +10 -0
  392. package/src/kiwi/protocol.ts +237 -0
  393. package/src/kiwi/schema.ts +2286 -0
  394. package/src/kiwi-serialize.ts +467 -0
  395. package/src/layout.ts +608 -0
  396. package/src/profiler/draw-call-counter.ts +43 -0
  397. package/src/profiler/frame-capture.ts +139 -0
  398. package/src/profiler/frame-stats.ts +122 -0
  399. package/src/profiler/gpu-timer.ts +93 -0
  400. package/src/profiler/hud-renderer.ts +242 -0
  401. package/src/profiler/index.ts +8 -0
  402. package/src/profiler/phase-timer.ts +62 -0
  403. package/src/profiler/render-profiler.ts +163 -0
  404. package/src/render/components.ts +69 -0
  405. package/src/render/emoji-icons.ts +60 -0
  406. package/src/render/export-code.ts +366 -0
  407. package/src/render/export-jsx.ts +633 -0
  408. package/src/render/index.ts +38 -0
  409. package/src/render/jsx-runtime.ts +36 -0
  410. package/src/render/mini-react.ts +30 -0
  411. package/src/render/render-jsx.ts +74 -0
  412. package/src/render/renderer.ts +508 -0
  413. package/src/render/tailwind.ts +114 -0
  414. package/src/render/tree.ts +137 -0
  415. package/src/render-image.ts +139 -0
  416. package/src/renderer/effects.ts +64 -0
  417. package/src/renderer/fills.ts +248 -0
  418. package/src/renderer/index.ts +1 -0
  419. package/src/renderer/label-cache.ts +114 -0
  420. package/src/renderer/labels.ts +168 -0
  421. package/src/renderer/lru-cache.ts +71 -0
  422. package/src/renderer/overlays.ts +1089 -0
  423. package/src/renderer/renderer.ts +1226 -0
  424. package/src/renderer/rulers.ts +230 -0
  425. package/src/renderer/scene.ts +554 -0
  426. package/src/renderer/shapes.ts +193 -0
  427. package/src/renderer/strokes.ts +221 -0
  428. package/src/rpc/commands.ts +667 -0
  429. package/src/rpc/index.ts +25 -0
  430. package/src/scene-graph.ts +1254 -0
  431. package/src/snap.ts +122 -0
  432. package/src/style-runs.ts +234 -0
  433. package/src/svg-export/defs.ts +255 -0
  434. package/src/svg-export/index.ts +427 -0
  435. package/src/svg-export/paths.ts +212 -0
  436. package/src/svg-node.ts +62 -0
  437. package/src/text-editor.ts +349 -0
  438. package/src/tools/ai-adapter.ts +144 -0
  439. package/src/tools/analyze.ts +786 -0
  440. package/src/tools/create.ts +206 -0
  441. package/src/tools/describe.ts +280 -0
  442. package/src/tools/export-code.ts +41 -0
  443. package/src/tools/export-tailwind-config.ts +266 -0
  444. package/src/tools/icons.ts +213 -0
  445. package/src/tools/index.ts +6 -0
  446. package/src/tools/modify.ts +651 -0
  447. package/src/tools/patterns.ts +189 -0
  448. package/src/tools/read.ts +401 -0
  449. package/src/tools/registry.ts +155 -0
  450. package/src/tools/schema.ts +65 -0
  451. package/src/tools/structure.ts +348 -0
  452. package/src/tools/variables.ts +193 -0
  453. package/src/tools/vector.ts +311 -0
  454. package/src/types.ts +32 -0
  455. package/src/undo.ts +126 -0
  456. package/src/vector.ts +413 -0
  457. package/src/xpath.ts +259 -0
@@ -0,0 +1,786 @@
1
+ import { createTwoFilesPatch } from 'diff'
2
+
3
+ import { colorDistance, colorToHex, parseColor } from '../color'
4
+
5
+ import { defineTool } from './schema'
6
+
7
+ import type { Color } from '../types'
8
+ import type { SceneNode, Variable, VariableCollection, VariableValue } from '../scene-graph'
9
+ import type { FigmaAPI } from '../figma-api'
10
+
11
+ interface ColorEntry {
12
+ hex: string
13
+ color: Color
14
+ count: number
15
+ variableName: string | null
16
+ }
17
+
18
+ function trackColor(
19
+ colorMap: Map<string, ColorEntry>,
20
+ color: Color,
21
+ variableName: string | null
22
+ ) {
23
+ const hex = colorToHex(color)
24
+ const entry = colorMap.get(hex)
25
+ if (entry) {
26
+ entry.count++
27
+ if (!entry.variableName && variableName) entry.variableName = variableName
28
+ } else {
29
+ colorMap.set(hex, { hex, color, count: 1, variableName })
30
+ }
31
+ }
32
+
33
+ // ─── Diff helpers ─────────────────────────────────────────────
34
+
35
+ function serializePaintProps(raw: SceneNode, lines: string[]): void {
36
+ const solidFill = raw.fills.find((f) => f.type === 'SOLID' && f.visible)
37
+ if (solidFill) lines.push(`fill: ${colorToHex(solidFill.color)}`)
38
+
39
+ const solidStroke = raw.strokes.find((s) => s.visible)
40
+ if (solidStroke) {
41
+ lines.push(`stroke: ${colorToHex(solidStroke.color)}`)
42
+ if (solidStroke.weight) lines.push(`strokeWeight: ${solidStroke.weight}`)
43
+ }
44
+ }
45
+
46
+ function serializeCornerRadii(raw: SceneNode, lines: string[]): void {
47
+ const tl = raw.topLeftRadius
48
+ const tr = raw.topRightRadius
49
+ const br = raw.bottomRightRadius
50
+ const bl = raw.bottomLeftRadius
51
+ if (tl || tr || br || bl) {
52
+ if (tl === tr && tr === br && br === bl) {
53
+ lines.push(`radius: ${tl}`)
54
+ } else {
55
+ lines.push(`radii: ${tl} ${tr} ${br} ${bl}`)
56
+ }
57
+ }
58
+ }
59
+
60
+ function serializeEffects(raw: SceneNode, lines: string[]): void {
61
+ for (const effect of raw.effects) {
62
+ const parts: string[] = [effect.type]
63
+ parts.push(`r=${effect.radius}`)
64
+ parts.push(`c=${colorToHex(effect.color)}`)
65
+ parts.push(`x=${effect.offset.x} y=${effect.offset.y}`)
66
+ parts.push(`s=${effect.spread}`)
67
+ lines.push(`effect: ${parts.join(' ')}`)
68
+ }
69
+ }
70
+
71
+ function serializeTextProps(raw: SceneNode, lines: string[]): void {
72
+ if (raw.type !== 'TEXT') return
73
+ if (raw.text) lines.push(`text: ${JSON.stringify(raw.text)}`)
74
+ if (raw.fontSize) lines.push(`fontSize: ${raw.fontSize}`)
75
+ if (raw.fontFamily) lines.push(`fontFamily: ${raw.fontFamily}`)
76
+ if (raw.fontWeight) lines.push(`fontWeight: ${raw.fontWeight}`)
77
+ }
78
+
79
+ function serializeNodeProps(raw: SceneNode): string {
80
+ const lines: string[] = []
81
+ lines.push(`type: ${raw.type}`)
82
+ lines.push(`size: ${raw.width} ${raw.height}`)
83
+ lines.push(`pos: ${raw.x} ${raw.y}`)
84
+
85
+ serializePaintProps(raw, lines)
86
+
87
+ if (raw.opacity !== 1) lines.push(`opacity: ${Math.round(raw.opacity * 100) / 100}`)
88
+
89
+ serializeCornerRadii(raw, lines)
90
+
91
+ if (raw.blendMode !== 'NORMAL') lines.push(`blendMode: ${raw.blendMode}`)
92
+ if (raw.rotation !== 0) lines.push(`rotation: ${Math.round(raw.rotation * 100) / 100}`)
93
+ if (raw.clipsContent) lines.push(`clipsContent: true`)
94
+
95
+ serializeEffects(raw, lines)
96
+ serializeTextProps(raw, lines)
97
+
98
+ if (!raw.visible) lines.push(`visible: false`)
99
+ if (raw.locked) lines.push(`locked: true`)
100
+
101
+ return lines.join('\n')
102
+ }
103
+
104
+ function collectNodeTree(
105
+ figma: FigmaAPI,
106
+ nodeId: string,
107
+ parentPath: string,
108
+ depth: number,
109
+ maxDepth: number
110
+ ): Map<string, { path: string; id: string; serialized: string }> {
111
+ const result = new Map<string, { path: string; id: string; serialized: string }>()
112
+ const raw = figma.graph.getNode(nodeId)
113
+ if (!raw) return result
114
+
115
+ const path = parentPath ? `${parentPath}/${raw.name}` : `/${raw.name}`
116
+ result.set(path, { path, id: raw.id, serialized: serializeNodeProps(raw) })
117
+
118
+ if (depth < maxDepth) {
119
+ for (const childId of raw.childIds) {
120
+ const childNodes = collectNodeTree(figma, childId, path, depth + 1, maxDepth)
121
+ for (const [k, v] of childNodes) result.set(k, v)
122
+ }
123
+ }
124
+ return result
125
+ }
126
+
127
+ function createUnifiedDiff(
128
+ oldFilename: string,
129
+ newFilename: string,
130
+ oldContent: string,
131
+ newContent: string
132
+ ): string {
133
+ const patch = createTwoFilesPatch(oldFilename, newFilename, oldContent, newContent, '', '')
134
+ return patch
135
+ .split('\n')
136
+ .filter((l) => !l.startsWith('Index:') && l !== '===================================================================')
137
+ .join('\n')
138
+ .trim()
139
+ }
140
+
141
+ // ─── Analyze tools ────────────────────────────────────────────
142
+
143
+ export const analyzeColors = defineTool({
144
+ name: 'analyze_colors',
145
+ description:
146
+ 'Analyze color palette usage across the current page. Shows frequency, variable bindings, and optionally clusters similar colors.',
147
+ params: {
148
+ limit: { type: 'number', description: 'Max colors to return (default: 30)' },
149
+ show_similar: {
150
+ type: 'boolean',
151
+ description: 'Include similar-color clusters for potential merging'
152
+ },
153
+ threshold: {
154
+ type: 'number',
155
+ description: 'Distance threshold for clustering (0-50, default: 15)'
156
+ }
157
+ },
158
+ execute: (figma, args) => {
159
+ const limit = args.limit ?? 30
160
+ const threshold = args.threshold ?? 15
161
+ const page = figma.currentPage
162
+ const colorMap = new Map<string, ColorEntry>()
163
+ let totalNodes = 0
164
+
165
+ page.findAll((node) => {
166
+ totalNodes++
167
+ const raw = figma.graph.getNode(node.id)
168
+ if (!raw) return false
169
+
170
+ const boundVars = raw.boundVariables
171
+ for (const fill of raw.fills) {
172
+ if (fill.type === 'SOLID' && fill.visible) {
173
+ trackColor(colorMap, fill.color, boundVars['fills'] ? String(boundVars['fills']) : null)
174
+ }
175
+ }
176
+ for (const stroke of raw.strokes) {
177
+ if (stroke.visible) {
178
+ trackColor(colorMap, stroke.color, boundVars['strokes'] ? String(boundVars['strokes']) : null)
179
+ }
180
+ }
181
+ return false
182
+ })
183
+
184
+ const colors = [...colorMap.values()].sort((a, b) => b.count - a.count).slice(0, limit)
185
+
186
+ const result: Record<string, unknown> = {
187
+ totalNodes,
188
+ uniqueColors: colorMap.size,
189
+ colors: colors.map((c) => ({ hex: c.hex, count: c.count, variableName: c.variableName }))
190
+ }
191
+
192
+ if (args.show_similar) {
193
+ const hardcoded = [...colorMap.values()]
194
+ .filter((c) => !c.variableName)
195
+ .sort((a, b) => b.count - a.count)
196
+ const used = new Set<string>()
197
+ const clusters: { colors: string[]; totalCount: number; suggestedHex: string }[] = []
198
+
199
+ for (const color of hardcoded) {
200
+ if (used.has(color.hex)) continue
201
+ const cluster = [color]
202
+ used.add(color.hex)
203
+ for (const other of hardcoded) {
204
+ if (used.has(other.hex)) continue
205
+ if (colorDistance(color.color, other.color) <= threshold) {
206
+ cluster.push(other)
207
+ used.add(other.hex)
208
+ }
209
+ }
210
+ if (cluster.length > 1) {
211
+ clusters.push({
212
+ colors: cluster.map((c) => c.hex),
213
+ totalCount: cluster.reduce((sum, c) => sum + c.count, 0),
214
+ suggestedHex: color.hex
215
+ })
216
+ }
217
+ }
218
+
219
+ result.similarClusters = clusters.sort((a, b) => b.colors.length - a.colors.length)
220
+ }
221
+
222
+ return result
223
+ }
224
+ })
225
+
226
+ export const analyzeTypography = defineTool({
227
+ name: 'analyze_typography',
228
+ description:
229
+ 'Analyze typography usage across the current page. Shows font families, sizes, weights, and their frequencies.',
230
+ params: {
231
+ limit: { type: 'number', description: 'Max styles to return (default: 30)' },
232
+ group_by: {
233
+ type: 'string',
234
+ description: 'Group results by a property',
235
+ enum: ['family', 'size', 'weight']
236
+ }
237
+ },
238
+ execute: (figma, args) => {
239
+ const limit = args.limit ?? 30
240
+ const page = figma.currentPage
241
+ const styleMap = new Map<
242
+ string,
243
+ { family: string; size: number; weight: number; lineHeight: string; count: number }
244
+ >()
245
+ let totalTextNodes = 0
246
+
247
+ page.findAll((node) => {
248
+ if (node.type !== 'TEXT') return false
249
+ totalTextNodes++
250
+ const raw = figma.graph.getNode(node.id)
251
+ if (!raw) return false
252
+ const lh = raw.lineHeight === null ? 'auto' : `${raw.lineHeight}`
253
+ const key = `${raw.fontFamily}|${raw.fontSize}|${raw.fontWeight}|${lh}`
254
+ const entry = styleMap.get(key)
255
+ if (entry) {
256
+ entry.count++
257
+ } else {
258
+ styleMap.set(key, {
259
+ family: raw.fontFamily,
260
+ size: raw.fontSize,
261
+ weight: raw.fontWeight,
262
+ lineHeight: lh,
263
+ count: 1
264
+ })
265
+ }
266
+ return false
267
+ })
268
+
269
+ const styles = [...styleMap.values()].sort((a, b) => b.count - a.count)
270
+
271
+ if (args.group_by === 'family') {
272
+ const byFamily = new Map<string, number>()
273
+ for (const s of styles) byFamily.set(s.family, (byFamily.get(s.family) ?? 0) + s.count)
274
+ return {
275
+ totalTextNodes,
276
+ groups: [...byFamily.entries()]
277
+ .sort((a, b) => b[1] - a[1])
278
+ .map(([family, count]) => ({ family, count }))
279
+ }
280
+ }
281
+
282
+ if (args.group_by === 'size') {
283
+ const bySize = new Map<number, number>()
284
+ for (const s of styles) bySize.set(s.size, (bySize.get(s.size) ?? 0) + s.count)
285
+ return {
286
+ totalTextNodes,
287
+ groups: [...bySize.entries()]
288
+ .sort((a, b) => a[0] - b[0])
289
+ .map(([size, count]) => ({ size, count }))
290
+ }
291
+ }
292
+
293
+ if (args.group_by === 'weight') {
294
+ const byWeight = new Map<number, number>()
295
+ for (const s of styles) byWeight.set(s.weight, (byWeight.get(s.weight) ?? 0) + s.count)
296
+ return {
297
+ totalTextNodes,
298
+ groups: [...byWeight.entries()]
299
+ .sort((a, b) => b[1] - a[1])
300
+ .map(([weight, count]) => ({ weight, count }))
301
+ }
302
+ }
303
+
304
+ return {
305
+ totalTextNodes,
306
+ uniqueStyles: styleMap.size,
307
+ styles: styles.slice(0, limit)
308
+ }
309
+ }
310
+ })
311
+
312
+ export const analyzeSpacing = defineTool({
313
+ name: 'analyze_spacing',
314
+ description:
315
+ 'Analyze spacing values (gap, padding) across the current page. Checks grid compliance.',
316
+ params: {
317
+ grid: { type: 'number', description: 'Base grid size to check against (default: 8)' }
318
+ },
319
+ execute: (figma, args) => {
320
+ const gridSize = args.grid ?? 8
321
+ const page = figma.currentPage
322
+ const gapMap = new Map<number, number>()
323
+ const paddingMap = new Map<number, number>()
324
+ let totalNodes = 0
325
+
326
+ page.findAll((node) => {
327
+ totalNodes++
328
+ const raw = figma.graph.getNode(node.id)
329
+ if (!raw) return false
330
+
331
+ if (raw.layoutMode !== 'NONE' && raw.itemSpacing > 0) {
332
+ gapMap.set(raw.itemSpacing, (gapMap.get(raw.itemSpacing) ?? 0) + 1)
333
+ }
334
+
335
+ for (const p of [raw.paddingTop, raw.paddingRight, raw.paddingBottom, raw.paddingLeft]) {
336
+ if (p > 0) {
337
+ paddingMap.set(p, (paddingMap.get(p) ?? 0) + 1)
338
+ }
339
+ }
340
+ return false
341
+ })
342
+
343
+ const gaps = [...gapMap.entries()]
344
+ .sort((a, b) => b[1] - a[1])
345
+ .map(([value, count]) => ({ value, count, onGrid: value % gridSize === 0 }))
346
+
347
+ const paddings = [...paddingMap.entries()]
348
+ .sort((a, b) => b[1] - a[1])
349
+ .map(([value, count]) => ({ value, count, onGrid: value % gridSize === 0 }))
350
+
351
+ const offGridGaps = gaps.filter((g) => !g.onGrid)
352
+ const offGridPaddings = paddings.filter((p) => !p.onGrid)
353
+
354
+ return {
355
+ totalNodes,
356
+ gridSize,
357
+ gaps,
358
+ paddings,
359
+ offGridGaps: offGridGaps.map((g) => g.value),
360
+ offGridPaddings: offGridPaddings.map((p) => p.value)
361
+ }
362
+ }
363
+ })
364
+
365
+ export const analyzeClusters = defineTool({
366
+ name: 'analyze_clusters',
367
+ description:
368
+ 'Find repeated design patterns (potential components). Groups nodes by structural signature — type, size, and child structure.',
369
+ params: {
370
+ min_count: { type: 'number', description: 'Min instances to form a cluster (default: 2)' },
371
+ min_size: { type: 'number', description: 'Min node size in px (default: 30)' },
372
+ limit: { type: 'number', description: 'Max clusters to return (default: 20)' }
373
+ },
374
+ execute: (figma, args) => {
375
+ const minCount = args.min_count ?? 2
376
+ const minSize = args.min_size ?? 30
377
+ const limit = args.limit ?? 20
378
+ const page = figma.currentPage
379
+
380
+ const signatureMap = new Map<
381
+ string,
382
+ { id: string; name: string; type: string; width: number; height: number; childCount: number }[]
383
+ >()
384
+ let totalNodes = 0
385
+
386
+ page.findAll((node) => {
387
+ totalNodes++
388
+ const raw = figma.graph.getNode(node.id)
389
+ if (!raw) return false
390
+ if (raw.width < minSize || raw.height < minSize) return false
391
+ if (raw.type === 'CANVAS' || raw.type === 'INSTANCE') return false
392
+
393
+ const childTypes = new Map<string, number>()
394
+ for (const childId of raw.childIds) {
395
+ const child = figma.graph.getNode(childId)
396
+ if (child) childTypes.set(child.type, (childTypes.get(child.type) ?? 0) + 1)
397
+ }
398
+
399
+ const w = Math.round(raw.width / 10) * 10
400
+ const h = Math.round(raw.height / 10) * 10
401
+ const childSig = [...childTypes.entries()]
402
+ .sort(([a], [b]) => a.localeCompare(b))
403
+ .map(([t, c]) => `${t}:${c}`)
404
+ .join(',')
405
+ const signature = `${raw.type}:${w}x${h}|${childSig}`
406
+
407
+ const list = signatureMap.get(signature) ?? []
408
+ list.push({
409
+ id: raw.id,
410
+ name: raw.name,
411
+ type: raw.type,
412
+ width: raw.width,
413
+ height: raw.height,
414
+ childCount: raw.childIds.length
415
+ })
416
+ signatureMap.set(signature, list)
417
+ return false
418
+ })
419
+
420
+ const clusters = [...signatureMap.entries()]
421
+ .filter(([, nodes]) => nodes.length >= minCount)
422
+ .map(([signature, nodes]) => {
423
+ const avgW = nodes.reduce((s, n) => s + n.width, 0) / nodes.length
424
+ const avgH = nodes.reduce((s, n) => s + n.height, 0) / nodes.length
425
+ const widths = nodes.map((n) => n.width)
426
+ const heights = nodes.map((n) => n.height)
427
+ const widthRange = Math.max(...widths) - Math.min(...widths)
428
+ const heightRange = Math.max(...heights) - Math.min(...heights)
429
+
430
+ let confidence = 100
431
+ if (nodes.length >= 2) {
432
+ const base = nodes[0]
433
+ let score = 0
434
+ for (const n of nodes.slice(1)) {
435
+ const sizeDiff = Math.abs(n.width - base.width) + Math.abs(n.height - base.height)
436
+ const childDiff = Math.abs(n.childCount - base.childCount)
437
+ if (sizeDiff <= 4 && childDiff === 0) score++
438
+ else if (sizeDiff <= 10 && childDiff <= 1) score += 0.8
439
+ else if (sizeDiff <= 20 && childDiff <= 2) score += 0.6
440
+ else score += 0.4
441
+ }
442
+ confidence = Math.round((score / (nodes.length - 1)) * 100)
443
+ }
444
+
445
+ return {
446
+ signature,
447
+ count: nodes.length,
448
+ avgWidth: Math.round(avgW),
449
+ avgHeight: Math.round(avgH),
450
+ widthRange: Math.round(widthRange),
451
+ heightRange: Math.round(heightRange),
452
+ confidence,
453
+ examples: nodes.slice(0, 3).map((n) => ({ id: n.id, name: n.name }))
454
+ }
455
+ })
456
+ .sort((a, b) => b.count - a.count)
457
+ .slice(0, limit)
458
+
459
+ return { totalNodes, clusters }
460
+ }
461
+ })
462
+
463
+ // ─── Diff tools ───────────────────────────────────────────────
464
+
465
+ export const diffCreate = defineTool({
466
+ name: 'diff_create',
467
+ description:
468
+ 'Create a structural diff between two node trees. Compares properties (fills, strokes, effects, text, size, position) in unified diff format.',
469
+ params: {
470
+ from: { type: 'string', description: 'Source node ID', required: true },
471
+ to: { type: 'string', description: 'Target node ID', required: true },
472
+ depth: { type: 'number', description: 'Max tree depth (default: 10)' }
473
+ },
474
+ execute: (figma, args) => {
475
+ const maxDepth = args.depth ?? 10
476
+ const fromNode = figma.graph.getNode(args.from)
477
+ const toNode = figma.graph.getNode(args.to)
478
+ if (!fromNode) return { error: `Node "${args.from}" not found` }
479
+ if (!toNode) return { error: `Node "${args.to}" not found` }
480
+
481
+ const fromNodes = collectNodeTree(figma, args.from, '', 0, maxDepth)
482
+ const toNodes = collectNodeTree(figma, args.to, '', 0, maxDepth)
483
+ const allPaths = new Set([...fromNodes.keys(), ...toNodes.keys()])
484
+
485
+ const patches: string[] = []
486
+ for (const path of allPaths) {
487
+ const fromEntry = fromNodes.get(path)
488
+ const toEntry = toNodes.get(path)
489
+
490
+ if (!fromEntry && toEntry) {
491
+ const filename = `${path} #${toEntry.id}`
492
+ const newLines = toEntry.serialized.split('\n')
493
+ patches.push(
494
+ `--- /dev/null\n+++ ${filename}\n@@ -0,0 +1,${newLines.length} @@\n${newLines.map((l) => `+${l}`).join('\n')}`
495
+ )
496
+ } else if (fromEntry && !toEntry) {
497
+ const filename = `${path} #${fromEntry.id}`
498
+ const oldLines = fromEntry.serialized.split('\n')
499
+ patches.push(
500
+ `--- ${filename}\n+++ /dev/null\n@@ -1,${oldLines.length} +0,0 @@\n${oldLines.map((l) => `-${l}`).join('\n')}`
501
+ )
502
+ } else if (fromEntry && toEntry && fromEntry.serialized !== toEntry.serialized) {
503
+ patches.push(
504
+ createUnifiedDiff(
505
+ `${path} #${fromEntry.id}`,
506
+ `${path} #${toEntry.id}`,
507
+ fromEntry.serialized,
508
+ toEntry.serialized
509
+ )
510
+ )
511
+ }
512
+ }
513
+
514
+ if (patches.length === 0) return { diff: null, message: 'No differences found' }
515
+ return { diff: patches.join('\n') }
516
+ }
517
+ })
518
+
519
+ export const diffShow = defineTool({
520
+ name: 'diff_show',
521
+ description:
522
+ 'Preview what would change if properties were applied to a node. Shows a unified diff of current vs proposed state.',
523
+ params: {
524
+ id: { type: 'string', description: 'Node ID', required: true },
525
+ props: {
526
+ type: 'string',
527
+ description:
528
+ 'JSON object of new properties, e.g. \'{"opacity": 1, "fill": "#FF0000", "width": 200}\'',
529
+ required: true
530
+ }
531
+ },
532
+ execute: (figma, args) => {
533
+ const raw = figma.graph.getNode(args.id)
534
+ if (!raw) return { error: `Node "${args.id}" not found` }
535
+
536
+ const oldContent = serializeNodeProps(raw)
537
+
538
+ let newProps: Record<string, unknown>
539
+ try {
540
+ newProps = JSON.parse(args.props)
541
+ } catch {
542
+ return { error: 'Invalid JSON in props' }
543
+ }
544
+
545
+ const modified: SceneNode = structuredClone(raw)
546
+ if (newProps.fill) {
547
+ modified.fills = [
548
+ { type: 'SOLID', color: parseColor(newProps.fill as string), opacity: 1, visible: true }
549
+ ]
550
+ }
551
+ if (newProps.stroke) {
552
+ modified.strokes = [
553
+ {
554
+ color: parseColor(newProps.stroke as string),
555
+ weight: modified.strokes[0]?.weight ?? 1,
556
+ opacity: 1,
557
+ visible: true,
558
+ align: modified.strokes[0]?.align ?? 'INSIDE'
559
+ }
560
+ ]
561
+ }
562
+ if (newProps.opacity !== undefined) modified.opacity = Number(newProps.opacity)
563
+ if (newProps.radius !== undefined) {
564
+ const r = Number(newProps.radius)
565
+ modified.cornerRadius = r
566
+ modified.topLeftRadius = r
567
+ modified.topRightRadius = r
568
+ modified.bottomRightRadius = r
569
+ modified.bottomLeftRadius = r
570
+ }
571
+ if (newProps.width !== undefined) modified.width = Number(newProps.width)
572
+ if (newProps.height !== undefined) modified.height = Number(newProps.height)
573
+ if (newProps.x !== undefined) modified.x = Number(newProps.x)
574
+ if (newProps.y !== undefined) modified.y = Number(newProps.y)
575
+ if (newProps.visible !== undefined) modified.visible = Boolean(newProps.visible)
576
+ if (newProps.locked !== undefined) modified.locked = Boolean(newProps.locked)
577
+ if (newProps.rotation !== undefined) modified.rotation = Number(newProps.rotation)
578
+ if (newProps.text !== undefined) modified.text = newProps.text as string
579
+
580
+ const newContent = serializeNodeProps(modified)
581
+
582
+ if (oldContent === newContent) return { diff: null, message: 'No changes' }
583
+
584
+ const filename = `/${raw.name} #${args.id}`
585
+ return { diff: createUnifiedDiff(filename, filename, oldContent, newContent) }
586
+ }
587
+ })
588
+
589
+ // ─── Eval escape hatch ────────────────────────────────────────
590
+
591
+ export const evalCode = defineTool({
592
+ name: 'eval',
593
+ description:
594
+ 'Execute JavaScript with full Figma Plugin API access. Use for operations not covered by other tools. The `figma` global is available.',
595
+ params: {
596
+ code: { type: 'string', description: 'JavaScript code to execute', required: true }
597
+ },
598
+ execute: async (figma, { code }) => {
599
+ // eslint-disable-next-line no-empty-function
600
+ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
601
+ const wrapped = code.trim().startsWith('return') ? code : `return (async () => { ${code} })()`
602
+ const fn = new AsyncFunction('figma', wrapped)
603
+ const result = await fn(figma)
604
+ if (result && typeof result === 'object' && 'toJSON' in result) return result.toJSON()
605
+ return result ?? null
606
+ }
607
+ })
608
+
609
+ // ── Design System Diff ──────────────────────────────────────
610
+
611
+ function serializeVariableValue(v: VariableValue): string {
612
+ if (typeof v === 'object' && 'aliasId' in v) return `alias(${v.aliasId})`
613
+ if (typeof v === 'object' && 'r' in v) {
614
+ return colorToHex(v)
615
+ }
616
+ return String(v)
617
+ }
618
+
619
+ function serializeVariable(v: Variable): string {
620
+ const lines = [
621
+ `name: ${v.name}`,
622
+ `type: ${v.type}`,
623
+ `collection: ${v.collectionId}`,
624
+ `description: ${v.description || '(none)'}`,
625
+ `hidden: ${v.hiddenFromPublishing}`
626
+ ]
627
+ for (const [modeId, val] of Object.entries(v.valuesByMode)) {
628
+ lines.push(`mode[${modeId}]: ${serializeVariableValue(val)}`)
629
+ }
630
+ return lines.join('\n')
631
+ }
632
+
633
+ function serializeCollection(c: VariableCollection): string {
634
+ const lines = [
635
+ `name: ${c.name}`,
636
+ `defaultMode: ${c.defaultModeId}`,
637
+ `modes: ${c.modes.map((m) => `${m.modeId}(${m.name})`).join(', ')}`,
638
+ `variables: [${c.variableIds.join(', ')}]`
639
+ ]
640
+ return lines.join('\n')
641
+ }
642
+
643
+ export interface DesignSystemSnapshot {
644
+ variables: Record<string, { name: string; serialized: string }>
645
+ collections: Record<string, { name: string; serialized: string }>
646
+ }
647
+
648
+ export function snapshotDesignSystem(figma: FigmaAPI): DesignSystemSnapshot {
649
+ const variables: DesignSystemSnapshot['variables'] = {}
650
+ for (const v of figma.getLocalVariables()) {
651
+ variables[v.id] = { name: v.name, serialized: serializeVariable(v) }
652
+ }
653
+ const collections: DesignSystemSnapshot['collections'] = {}
654
+ for (const c of figma.getLocalVariableCollections()) {
655
+ collections[c.id] = { name: c.name, serialized: serializeCollection(c) }
656
+ }
657
+ return { variables, collections }
658
+ }
659
+
660
+ export const snapshotDesignSystemTool = defineTool({
661
+ name: 'snapshot_design_system',
662
+ description:
663
+ 'Take a snapshot of the current design system (all variables and collections). Returns a snapshot ID that can be used with diff_design_system.',
664
+ params: {
665
+ label: { type: 'string', description: 'Optional label for this snapshot' }
666
+ },
667
+ execute: (figma, args) => {
668
+ const snapshot = snapshotDesignSystem(figma)
669
+ const id = `ds_${Date.now()}`
670
+ const figmaRecord = figma as unknown as Record<string, unknown>
671
+ const store = (figmaRecord._dsSnapshots ?? new Map()) as Map<string, { label: string; snapshot: DesignSystemSnapshot; timestamp: number }>
672
+ figmaRecord._dsSnapshots = store
673
+ store.set(id, { label: args.label ?? id, snapshot, timestamp: Date.now() })
674
+ return {
675
+ snapshotId: id,
676
+ label: args.label ?? id,
677
+ variableCount: Object.keys(snapshot.variables).length,
678
+ collectionCount: Object.keys(snapshot.collections).length
679
+ }
680
+ }
681
+ })
682
+
683
+ export const diffDesignSystem = defineTool({
684
+ name: 'diff_design_system',
685
+ description:
686
+ 'Compare two design system snapshots. Shows added/removed/modified variables and collections in unified diff format.',
687
+ params: {
688
+ from: { type: 'string', description: 'Source snapshot ID', required: true },
689
+ to: {
690
+ type: 'string',
691
+ description: 'Target snapshot ID. Use "current" to compare against live state.'
692
+ }
693
+ },
694
+ execute: (figma, args) => {
695
+ const store = (figma as unknown as Record<string, unknown>)._dsSnapshots as
696
+ | Map<string, { label: string; snapshot: DesignSystemSnapshot; timestamp: number }>
697
+ | undefined
698
+ if (!store) return { error: 'No snapshots found. Use snapshot_design_system first.' }
699
+
700
+ const fromEntry = store.get(args.from)
701
+ if (!fromEntry) return { error: `Snapshot "${args.from}" not found` }
702
+
703
+ const toSnapshot =
704
+ args.to === 'current' || !args.to ? snapshotDesignSystem(figma) : store.get(args.to)?.snapshot
705
+ if (!toSnapshot) return { error: `Snapshot "${args.to}" not found` }
706
+
707
+ const fromSnap = fromEntry.snapshot
708
+ const patches: string[] = []
709
+ const summary = { added: 0, removed: 0, modified: 0 }
710
+
711
+ // Diff collections
712
+ const allCollectionIds = new Set([
713
+ ...Object.keys(fromSnap.collections),
714
+ ...Object.keys(toSnapshot.collections)
715
+ ])
716
+ for (const id of allCollectionIds) {
717
+ const from = fromSnap.collections[id] as { name: string; serialized: string } | undefined
718
+ const to = toSnapshot.collections[id] as { name: string; serialized: string } | undefined
719
+ if (!from && to) {
720
+ summary.added++
721
+ const lines = to.serialized.split('\n')
722
+ patches.push(
723
+ `--- /dev/null\n+++ collection/${to.name}\n@@ -0,0 +1,${lines.length} @@\n${lines.map((l) => `+${l}`).join('\n')}`
724
+ )
725
+ } else if (from && !to) {
726
+ summary.removed++
727
+ const lines = from.serialized.split('\n')
728
+ patches.push(
729
+ `--- collection/${from.name}\n+++ /dev/null\n@@ -1,${lines.length} +0,0 @@\n${lines.map((l) => `-${l}`).join('\n')}`
730
+ )
731
+ } else if (from && to && from.serialized !== to.serialized) {
732
+ summary.modified++
733
+ patches.push(
734
+ createTwoFilesPatch(
735
+ `collection/${from.name}`,
736
+ `collection/${to.name}`,
737
+ from.serialized,
738
+ to.serialized,
739
+ '',
740
+ '',
741
+ { context: 2 }
742
+ )
743
+ )
744
+ }
745
+ }
746
+
747
+ // Diff variables
748
+ const allVarIds = new Set([
749
+ ...Object.keys(fromSnap.variables),
750
+ ...Object.keys(toSnapshot.variables)
751
+ ])
752
+ for (const id of allVarIds) {
753
+ const from = fromSnap.variables[id] as { name: string; serialized: string } | undefined
754
+ const to = toSnapshot.variables[id] as { name: string; serialized: string } | undefined
755
+ if (!from && to) {
756
+ summary.added++
757
+ const lines = to.serialized.split('\n')
758
+ patches.push(
759
+ `--- /dev/null\n+++ variable/${to.name}\n@@ -0,0 +1,${lines.length} @@\n${lines.map((l) => `+${l}`).join('\n')}`
760
+ )
761
+ } else if (from && !to) {
762
+ summary.removed++
763
+ const lines = from.serialized.split('\n')
764
+ patches.push(
765
+ `--- variable/${from.name}\n+++ /dev/null\n@@ -1,${lines.length} +0,0 @@\n${lines.map((l) => `-${l}`).join('\n')}`
766
+ )
767
+ } else if (from && to && from.serialized !== to.serialized) {
768
+ summary.modified++
769
+ patches.push(
770
+ createTwoFilesPatch(
771
+ `variable/${from.name}`,
772
+ `variable/${to.name}`,
773
+ from.serialized,
774
+ to.serialized,
775
+ '',
776
+ '',
777
+ { context: 2 }
778
+ )
779
+ )
780
+ }
781
+ }
782
+
783
+ if (patches.length === 0) return { diff: null, summary, message: 'No differences found' }
784
+ return { diff: patches.join('\n'), summary }
785
+ }
786
+ })