fundus 0.24.0 → 0.26.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 (154) hide show
  1. package/README.md +42 -20
  2. package/build/client/_app/immutable/chunks/{CCIxsMRK.js → B_4qwt4o.js} +1 -1
  3. package/build/client/_app/immutable/chunks/B_4qwt4o.js.br +0 -0
  4. package/build/client/_app/immutable/chunks/B_4qwt4o.js.gz +0 -0
  5. package/build/client/_app/immutable/chunks/{CIZHDrUF.js → DS1jTxvm.js} +1 -1
  6. package/build/client/_app/immutable/chunks/DS1jTxvm.js.br +2 -0
  7. package/build/client/_app/immutable/chunks/DS1jTxvm.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/{CnnMN4wM.js → p2q3f3Ml.js} +1 -1
  9. package/build/client/_app/immutable/chunks/p2q3f3Ml.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/{CnnMN4wM.js.gz → p2q3f3Ml.js.gz} +0 -0
  11. package/build/client/_app/immutable/entry/{app.BPBGxhbo.js → app.DgW6Eg19.js} +2 -2
  12. package/build/client/_app/immutable/entry/app.DgW6Eg19.js.br +0 -0
  13. package/build/client/_app/immutable/entry/app.DgW6Eg19.js.gz +0 -0
  14. package/build/client/_app/immutable/entry/start.D3J5OM6w.js +1 -0
  15. package/build/client/_app/immutable/entry/start.D3J5OM6w.js.br +1 -0
  16. package/build/client/_app/immutable/entry/start.D3J5OM6w.js.gz +0 -0
  17. package/build/client/_app/immutable/nodes/{0.CquXHiCP.js → 0.DdcKoZu5.js} +1 -1
  18. package/build/client/_app/immutable/nodes/0.DdcKoZu5.js.br +0 -0
  19. package/build/client/_app/immutable/nodes/0.DdcKoZu5.js.gz +0 -0
  20. package/build/client/_app/immutable/nodes/{1.xSi5hUiK.js → 1.CoM7ng4c.js} +1 -1
  21. package/build/client/_app/immutable/nodes/1.CoM7ng4c.js.br +0 -0
  22. package/build/client/_app/immutable/nodes/1.CoM7ng4c.js.gz +0 -0
  23. package/build/client/_app/immutable/nodes/{2.CgzHO6l0.js → 2.CV-vWxFI.js} +1 -1
  24. package/build/client/_app/immutable/nodes/2.CV-vWxFI.js.br +0 -0
  25. package/build/client/_app/immutable/nodes/2.CV-vWxFI.js.gz +0 -0
  26. package/build/client/_app/immutable/nodes/{3.B-HZ9QO3.js → 3.B9mDsYGS.js} +1 -1
  27. package/build/client/_app/immutable/nodes/3.B9mDsYGS.js.br +0 -0
  28. package/build/client/_app/immutable/nodes/3.B9mDsYGS.js.gz +0 -0
  29. package/build/client/_app/immutable/nodes/{4._rTlKXDo.js → 4.CFfIs-6D.js} +1 -1
  30. package/build/client/_app/immutable/nodes/4.CFfIs-6D.js.br +0 -0
  31. package/build/client/_app/immutable/nodes/4.CFfIs-6D.js.gz +0 -0
  32. package/build/client/_app/version.json +1 -1
  33. package/build/client/_app/version.json.br +0 -0
  34. package/build/client/_app/version.json.gz +0 -0
  35. package/build/handler.js +7 -7
  36. package/build/index.js +7 -7
  37. package/build/server/chunks/chunks/{context.js-4TO_atTF.js → context.js-mE8mFnsg.js} +45 -12
  38. package/build/server/chunks/chunks/context.js-mE8mFnsg.js.map +1 -0
  39. package/build/server/chunks/chunks/{exports.js-CcnW-JJJ.js → exports.js-CDl9URFG.js} +2 -2
  40. package/build/server/chunks/chunks/{exports.js-CcnW-JJJ.js.map → exports.js-CDl9URFG.js.map} +1 -1
  41. package/build/server/chunks/chunks/{internal.js-ByBTomBV.js → internal.js-Bz3sw0lq.js} +3 -3
  42. package/build/server/chunks/chunks/{internal.js-ByBTomBV.js.map → internal.js-Bz3sw0lq.js.map} +1 -1
  43. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  44. package/build/server/chunks/chunks/{shared.js-CCqNepI3.js → shared.js-DwJl8ZNw.js} +2 -2
  45. package/build/server/chunks/chunks/{shared.js-CCqNepI3.js.map → shared.js-DwJl8ZNw.js.map} +1 -1
  46. package/build/server/chunks/chunks/{utils.js-GaDYRoLP.js → utils.js-DoU5Jolh.js} +3 -3
  47. package/build/server/chunks/chunks/{utils.js-GaDYRoLP.js.map → utils.js-DoU5Jolh.js.map} +1 -1
  48. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-Aa7zYzUB.js → _server.ts.js-CM6PedpD.js} +2 -2
  49. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-Aa7zYzUB.js.map → _server.ts.js-CM6PedpD.js.map} +1 -1
  50. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CM73kt1H.js → _server.ts.js-BpfOlmKN.js} +2 -2
  51. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CM73kt1H.js.map → _server.ts.js-BpfOlmKN.js.map} +1 -1
  52. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-CyiEsjc3.js → _server.ts.js-UvDSeD5O.js} +2 -2
  53. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-CyiEsjc3.js.map → _server.ts.js-UvDSeD5O.js.map} +1 -1
  54. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/{_server.ts.js-Cpt_vVY0.js → _server.ts.js-D4kq3W1-.js} +2 -2
  55. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/{_server.ts.js-Cpt_vVY0.js.map → _server.ts.js-D4kq3W1-.js.map} +1 -1
  56. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-BVdeCRy0.js → _server.ts.js-CcV2BTCH.js} +2 -2
  57. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-BVdeCRy0.js.map → _server.ts.js-CcV2BTCH.js.map} +1 -1
  58. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-QEa0nYZE.js → _server.ts.js-Cb0Dn_a2.js} +2 -2
  59. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-QEa0nYZE.js.map → _server.ts.js-Cb0Dn_a2.js.map} +1 -1
  60. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-Dk0sFdoM.js → _server.ts.js-ChlfFRqR.js} +2 -2
  61. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-Dk0sFdoM.js.map → _server.ts.js-ChlfFRqR.js.map} +1 -1
  62. package/build/server/chunks/entries/endpoints/api/events/{_server.ts.js-CO8RZm3e.js → _server.ts.js-DnrgWGff.js} +2 -2
  63. package/build/server/chunks/entries/endpoints/api/events/{_server.ts.js-CO8RZm3e.js.map → _server.ts.js-DnrgWGff.js.map} +1 -1
  64. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-8RzyUK6g.js → _server.ts.js-CFM83bne.js} +2 -2
  65. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-8RzyUK6g.js.map → _server.ts.js-CFM83bne.js.map} +1 -1
  66. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-YWliM590.js → _server.ts.js-BtJIovEG.js} +2 -2
  67. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-YWliM590.js.map → _server.ts.js-BtJIovEG.js.map} +1 -1
  68. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CeXmOrwN.js → _server.ts.js-BAHE77r2.js} +2 -2
  69. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CeXmOrwN.js.map → _server.ts.js-BAHE77r2.js.map} +1 -1
  70. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DQaLSdGl.js → _server.ts.js-Cypw-CjT.js} +2 -2
  71. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DQaLSdGl.js.map → _server.ts.js-Cypw-CjT.js.map} +1 -1
  72. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-BE_nYFP_.js → _server.ts.js-FrntF4Gn.js} +2 -2
  73. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-BE_nYFP_.js.map → _server.ts.js-FrntF4Gn.js.map} +1 -1
  74. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BGxvZ4EV.js → _server.ts.js-BeEHDBep.js} +2 -2
  75. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BGxvZ4EV.js.map → _server.ts.js-BeEHDBep.js.map} +1 -1
  76. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-Cvwr9a0l.js → _server.ts.js-Di1kfrl2.js} +2 -2
  77. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-Cvwr9a0l.js.map → _server.ts.js-Di1kfrl2.js.map} +1 -1
  78. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DEMDXHsR.js → _server.ts.js-CrLHlnap.js} +2 -2
  79. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DEMDXHsR.js.map → _server.ts.js-CrLHlnap.js.map} +1 -1
  80. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DoeEIUoG.js → _server.ts.js-BcqKQTDk.js} +2 -2
  81. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DoeEIUoG.js.map → _server.ts.js-BcqKQTDk.js.map} +1 -1
  82. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-0hMtYoB1.js → _server.ts.js-oFUf-T_H.js} +2 -2
  83. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-0hMtYoB1.js.map → _server.ts.js-oFUf-T_H.js.map} +1 -1
  84. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-B4YWmlkC.js → _server.ts.js-D46dsJCB.js} +2 -2
  85. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-B4YWmlkC.js.map → _server.ts.js-D46dsJCB.js.map} +1 -1
  86. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-dNLtLO2U.js → _server.ts.js-BlKjsU-v.js} +2 -2
  87. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-dNLtLO2U.js.map → _server.ts.js-BlKjsU-v.js.map} +1 -1
  88. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BtCkf-hk.js → _server.ts.js-DafDgMis.js} +2 -2
  89. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BtCkf-hk.js.map → _server.ts.js-DafDgMis.js.map} +1 -1
  90. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-CozrPtRv.js → _server.ts.js-6Vn2UuF6.js} +2 -2
  91. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-CozrPtRv.js.map → _server.ts.js-6Vn2UuF6.js.map} +1 -1
  92. package/build/server/chunks/entries/{hooks.server.js-Cnsi5KsE.js → hooks.server.js-CSypLuCa.js} +2 -2
  93. package/build/server/chunks/entries/{hooks.server.js-Cnsi5KsE.js.map → hooks.server.js-CSypLuCa.js.map} +1 -1
  94. package/build/server/chunks/{handler-DegMVxhC.js → handler-C0NXVVDu.js} +5 -5
  95. package/build/server/chunks/{handler-DegMVxhC.js.map → handler-C0NXVVDu.js.map} +1 -1
  96. package/build/server/chunks/{index.js-BrCnc8Gq.js → index.js-D3rzqgKi.js} +5 -5
  97. package/build/server/chunks/{index.js-BrCnc8Gq.js.map → index.js-D3rzqgKi.js.map} +1 -1
  98. package/build/server/chunks/{manifest.js-mKxXvxuB.js → manifest.js-Dw3Fvt_0.js} +29 -29
  99. package/build/server/chunks/{manifest.js-mKxXvxuB.js.map → manifest.js-Dw3Fvt_0.js.map} +1 -1
  100. package/build/server/chunks/nodes/{0.js-Bnw_8E4c.js → 0.js-D1uRRjFL.js} +2 -2
  101. package/build/server/chunks/nodes/{0.js-Bnw_8E4c.js.map → 0.js-D1uRRjFL.js.map} +1 -1
  102. package/build/server/chunks/nodes/1.js-sYnc9kZu.js +7 -0
  103. package/build/server/chunks/nodes/{1.js-DpvNtg1z.js.map → 1.js-sYnc9kZu.js.map} +1 -1
  104. package/build/server/chunks/nodes/{2.js-h7fLpCKT.js → 2.js-B3OCWo8p.js} +2 -2
  105. package/build/server/chunks/nodes/{2.js-h7fLpCKT.js.map → 2.js-B3OCWo8p.js.map} +1 -1
  106. package/build/server/chunks/nodes/3.js-BWkr9ibc.js +7 -0
  107. package/build/server/chunks/nodes/{3.js-XlL1_Sjt.js.map → 3.js-BWkr9ibc.js.map} +1 -1
  108. package/build/server/chunks/nodes/{4.js-BRkm0s7e.js → 4.js-gujLHNff.js} +2 -2
  109. package/build/server/chunks/nodes/{4.js-BRkm0s7e.js.map → 4.js-gujLHNff.js.map} +1 -1
  110. package/dist/cli.js +9 -0
  111. package/dist/core/figma-tag-script.d.ts +141 -0
  112. package/dist/core/figma-tag-script.js +236 -0
  113. package/dist/core/index.d.ts +2 -0
  114. package/dist/core/index.js +1 -0
  115. package/dist/runtime/canvas.d.ts +14 -9
  116. package/dist/runtime/canvas.js +20 -8
  117. package/dist/runtime/index.d.ts +3 -1
  118. package/dist/runtime/index.js +1 -0
  119. package/dist/runtime/retained-entry.d.ts +3 -3
  120. package/dist/runtime/retained-entry.js +4 -4
  121. package/dist/runtime/retained-entry.svelte.d.ts +26 -0
  122. package/dist/runtime/retained-entry.svelte.js +62 -0
  123. package/dist/server/cli-commands.d.ts +6 -0
  124. package/dist/server/cli-commands.js +1 -1
  125. package/dist/server/cli-figma-tags.d.ts +2 -0
  126. package/dist/server/cli-figma-tags.js +222 -0
  127. package/dist/server/figma-tags.d.ts +61 -0
  128. package/dist/server/figma-tags.js +153 -0
  129. package/dist/server/importers/figma.d.ts +17 -0
  130. package/dist/server/importers/figma.js +43 -18
  131. package/package.json +1 -1
  132. package/build/client/_app/immutable/chunks/CCIxsMRK.js.br +0 -0
  133. package/build/client/_app/immutable/chunks/CCIxsMRK.js.gz +0 -0
  134. package/build/client/_app/immutable/chunks/CIZHDrUF.js.br +0 -0
  135. package/build/client/_app/immutable/chunks/CIZHDrUF.js.gz +0 -0
  136. package/build/client/_app/immutable/chunks/CnnMN4wM.js.br +0 -0
  137. package/build/client/_app/immutable/entry/app.BPBGxhbo.js.br +0 -0
  138. package/build/client/_app/immutable/entry/app.BPBGxhbo.js.gz +0 -0
  139. package/build/client/_app/immutable/entry/start.CTftTL5f.js +0 -1
  140. package/build/client/_app/immutable/entry/start.CTftTL5f.js.br +0 -0
  141. package/build/client/_app/immutable/entry/start.CTftTL5f.js.gz +0 -0
  142. package/build/client/_app/immutable/nodes/0.CquXHiCP.js.br +0 -0
  143. package/build/client/_app/immutable/nodes/0.CquXHiCP.js.gz +0 -0
  144. package/build/client/_app/immutable/nodes/1.xSi5hUiK.js.br +0 -1
  145. package/build/client/_app/immutable/nodes/1.xSi5hUiK.js.gz +0 -0
  146. package/build/client/_app/immutable/nodes/2.CgzHO6l0.js.br +0 -0
  147. package/build/client/_app/immutable/nodes/2.CgzHO6l0.js.gz +0 -0
  148. package/build/client/_app/immutable/nodes/3.B-HZ9QO3.js.br +0 -0
  149. package/build/client/_app/immutable/nodes/3.B-HZ9QO3.js.gz +0 -0
  150. package/build/client/_app/immutable/nodes/4._rTlKXDo.js.br +0 -0
  151. package/build/client/_app/immutable/nodes/4._rTlKXDo.js.gz +0 -0
  152. package/build/server/chunks/chunks/context.js-4TO_atTF.js.map +0 -1
  153. package/build/server/chunks/nodes/1.js-DpvNtg1z.js +0 -7
  154. package/build/server/chunks/nodes/3.js-XlL1_Sjt.js +0 -7
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Plugin-API code that reads and writes Fundus tags inside a Figma document.
3
+ * Two runtimes execute it: the Fundus Figma plugin imports these functions
4
+ * directly, and the CLI serializes them (via `Function.prototype.toString`)
5
+ * into a script an agent runs through Figma's MCP `use_figma` tool. The Figma
6
+ * REST API cannot write plugin data, so a script is the only headless write
7
+ * path.
8
+ *
9
+ * Serialization constrains every function listed in `SCRIPT_FUNCTIONS`: it
10
+ * may reference only its parameters, the other listed functions, and the
11
+ * constants in `SCRIPT_CONSTANTS` — never imports or other module scope. The
12
+ * `figma` global is passed in as a parameter so tests can drive the same code
13
+ * against a fake document.
14
+ */
15
+ /** Shared-plugin-data namespace; the REST API returns it under `sharedPluginData`. */
16
+ export const FIGMA_TAG_NAMESPACE = 'fundus';
17
+ /** Key holding the node's Fundus asset id (the tag). */
18
+ export const FIGMA_TAG_KEY = 'assetId';
19
+ /**
20
+ * Key holding the exact label of the annotation Fundus owns on the node; empty
21
+ * when it owns none. Tracking ownership out-of-band keeps the visible label
22
+ * exactly equal to the tag and leaves the user's own annotations untouched.
23
+ */
24
+ export const FIGMA_ANNOTATION_LABEL_KEY = 'annotationLabel';
25
+ /**
26
+ * Visible prefix earlier plugin builds used as the ownership marker. Still
27
+ * recognized so those annotations are migrated or stripped instead of
28
+ * duplicated.
29
+ */
30
+ export const LEGACY_ANNOTATION_PREFIX = 'Fundus asset: ';
31
+ /**
32
+ * Whether the id names a node inside an instance (`I<instance>;<source>`).
33
+ * Such nodes inherit the shared plugin data of the main component's node, so
34
+ * they report its tag without being a separate taggable node.
35
+ */
36
+ export function isInstanceSublayerId(id) {
37
+ return id.startsWith('I');
38
+ }
39
+ export function supportsAnnotations(node) {
40
+ return 'annotations' in node;
41
+ }
42
+ /**
43
+ * The annotation's shown text. Figma reports a plain-label annotation with
44
+ * `labelMarkdown: ""`, so an empty markdown field falls back to `label`.
45
+ */
46
+ export function annotationText(annotation) {
47
+ return annotation.labelMarkdown || annotation.label || '';
48
+ }
49
+ /**
50
+ * Figma returns BOTH `label` and `labelMarkdown` when reading annotations, but
51
+ * the setter rejects an annotation that carries both. Re-emit a single-field
52
+ * copy (non-empty markdown first), preserving `properties`/`categoryId`, so
53
+ * preserved annotations can be written back without failing validation.
54
+ */
55
+ export function writableAnnotation(annotation) {
56
+ const copy = {};
57
+ if (annotation.labelMarkdown)
58
+ copy.labelMarkdown = annotation.labelMarkdown;
59
+ else if (annotation.label !== undefined)
60
+ copy.label = annotation.label;
61
+ if (annotation.properties)
62
+ copy.properties = annotation.properties;
63
+ if (annotation.categoryId)
64
+ copy.categoryId = annotation.categoryId;
65
+ return copy;
66
+ }
67
+ /** Whether this annotation is one Fundus owns — by the stored label or the legacy prefix. */
68
+ export function isFundusAnnotation(annotation, ownedLabel) {
69
+ const text = annotationText(annotation);
70
+ return (ownedLabel !== '' && text === ownedLabel) || text.startsWith(LEGACY_ANNOTATION_PREFIX);
71
+ }
72
+ /**
73
+ * Ensure the node carries exactly one Fundus annotation whose label is the tag,
74
+ * preserving all others. Writes only when not already in sync, so a steady
75
+ * state neither dirties the file nor adds undo steps.
76
+ */
77
+ export function writeFundusAnnotation(node, tag) {
78
+ const owned = node.getSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_ANNOTATION_LABEL_KEY);
79
+ const current = node.annotations;
80
+ const ours = current.filter((annotation) => isFundusAnnotation(annotation, owned));
81
+ const inSync = owned === tag && ours.length === 1 && annotationText(ours[0]) === tag;
82
+ if (inSync)
83
+ return;
84
+ const kept = current
85
+ .filter((annotation) => !isFundusAnnotation(annotation, owned) && annotationText(annotation) !== tag)
86
+ .map(writableAnnotation);
87
+ node.annotations = [...kept, { label: tag }];
88
+ if (owned !== tag) {
89
+ node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_ANNOTATION_LABEL_KEY, tag);
90
+ }
91
+ }
92
+ /** Remove the Fundus annotation (incl. legacy), preserving all others. */
93
+ export function stripFundusAnnotation(node) {
94
+ const owned = node.getSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_ANNOTATION_LABEL_KEY);
95
+ const current = node.annotations;
96
+ const kept = current
97
+ .filter((annotation) => !isFundusAnnotation(annotation, owned))
98
+ .map(writableAnnotation);
99
+ if (kept.length !== current.length)
100
+ node.annotations = kept;
101
+ if (owned !== '')
102
+ node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_ANNOTATION_LABEL_KEY, '');
103
+ }
104
+ export function pageOfNode(node) {
105
+ let current = node;
106
+ while (current && current.type !== 'PAGE')
107
+ current = current.parent;
108
+ return current ? current : null;
109
+ }
110
+ /**
111
+ * Resolve a scene node and load its page. `loadAsync` loads without
112
+ * navigating, so running a script never moves anyone's canvas.
113
+ */
114
+ export async function loadTaggableNode(figma, nodeId) {
115
+ const node = await figma.getNodeByIdAsync(nodeId);
116
+ const page = node ? pageOfNode(node) : null;
117
+ if (!node || !page || node.type === 'PAGE') {
118
+ throw new Error(`Figma node "${nodeId}" does not exist in this file.`);
119
+ }
120
+ await page.loadAsync();
121
+ return node;
122
+ }
123
+ /**
124
+ * The first other node carrying `tag`, across every page. Scripts start with
125
+ * only the first page loaded, and `findAll` on an unloaded page silently
126
+ * misses content, so each page is loaded first.
127
+ */
128
+ export async function findTagHolder(figma, tag, exceptNodeId) {
129
+ for (const page of figma.root.children) {
130
+ await page.loadAsync();
131
+ const [holder] = page.findAll((candidate) => candidate.id !== exceptNodeId &&
132
+ !isInstanceSublayerId(candidate.id) &&
133
+ candidate.getSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY) === tag);
134
+ if (holder)
135
+ return { node: holder, page };
136
+ }
137
+ return null;
138
+ }
139
+ /**
140
+ * Tag a node, re-checking the live document right before writing: the CLI's
141
+ * REST snapshot can lag behind edits made since it ran.
142
+ */
143
+ export async function setFundusTag(figma, input) {
144
+ const node = await loadTaggableNode(figma, input.nodeId);
145
+ const previous = node.getSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY);
146
+ if (!input.force && previous === input.tag) {
147
+ throw new Error(`Node "${node.name}" (${node.id}) already carries tag "${input.tag}"; rerun prepare-set-tag with --force to rewrite it.`);
148
+ }
149
+ if (!input.force && previous !== '') {
150
+ throw new Error(`Node "${node.name}" (${node.id}) already carries tag "${previous}"; rerun prepare-set-tag with --force to replace it.`);
151
+ }
152
+ const holder = await findTagHolder(figma, input.tag, node.id);
153
+ if (holder) {
154
+ throw new Error(`Tag "${input.tag}" is already used by "${holder.node.name}" (${holder.node.id}) on page "${holder.page.name}"; choose another tag.`);
155
+ }
156
+ node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY, input.tag);
157
+ const annotated = supportsAnnotations(node);
158
+ if (annotated)
159
+ writeFundusAnnotation(node, input.tag);
160
+ return {
161
+ ok: true,
162
+ nodeId: node.id,
163
+ nodeName: node.name,
164
+ tag: input.tag,
165
+ previousTag: previous === '' ? null : previous,
166
+ annotated
167
+ };
168
+ }
169
+ export async function deleteFundusTag(figma, input) {
170
+ const node = await loadTaggableNode(figma, input.nodeId);
171
+ const current = node.getSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY);
172
+ if (current !== input.tag) {
173
+ throw new Error(current === ''
174
+ ? `Node "${node.name}" (${node.id}) carries no Fundus tag.`
175
+ : `Node "${node.name}" (${node.id}) carries tag "${current}", not "${input.tag}"; rerun prepare-delete-tag.`);
176
+ }
177
+ node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY, '');
178
+ let annotationRemoved = false;
179
+ if (supportsAnnotations(node)) {
180
+ const before = node.annotations.length;
181
+ stripFundusAnnotation(node);
182
+ annotationRemoved = node.annotations.length < before;
183
+ }
184
+ else {
185
+ node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_ANNOTATION_LABEL_KEY, '');
186
+ }
187
+ return {
188
+ ok: true,
189
+ nodeId: node.id,
190
+ nodeName: node.name,
191
+ removedTag: input.tag,
192
+ annotationRemoved
193
+ };
194
+ }
195
+ const SCRIPT_CONSTANTS = {
196
+ FIGMA_TAG_NAMESPACE,
197
+ FIGMA_TAG_KEY,
198
+ FIGMA_ANNOTATION_LABEL_KEY,
199
+ LEGACY_ANNOTATION_PREFIX
200
+ };
201
+ const SCRIPT_FUNCTIONS = [
202
+ isInstanceSublayerId,
203
+ supportsAnnotations,
204
+ annotationText,
205
+ writableAnnotation,
206
+ isFundusAnnotation,
207
+ writeFundusAnnotation,
208
+ stripFundusAnnotation,
209
+ pageOfNode,
210
+ loadTaggableNode,
211
+ findTagHolder,
212
+ setFundusTag,
213
+ deleteFundusTag
214
+ ];
215
+ /**
216
+ * A self-contained `use_figma` script body: top-level `await` and `return`,
217
+ * with the `figma` global in scope. It returns the operation's JSON result
218
+ * and throws, before mutating anything, when the live document disagrees.
219
+ */
220
+ function buildScript(summary, call) {
221
+ const constants = Object.entries(SCRIPT_CONSTANTS).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`);
222
+ return [
223
+ `// ${summary}`,
224
+ '// Generated by the fundus CLI — run it unchanged with the Figma MCP `use_figma` tool.',
225
+ ...constants,
226
+ ...SCRIPT_FUNCTIONS.map(String),
227
+ `return await ${call};`,
228
+ ''
229
+ ].join('\n\n');
230
+ }
231
+ export function buildSetTagScript(input) {
232
+ return buildScript(`Set Fundus tag ${JSON.stringify(input.tag)} on Figma node ${JSON.stringify(input.nodeId)}.`, `setFundusTag(figma, ${JSON.stringify(input)})`);
233
+ }
234
+ export function buildDeleteTagScript(input) {
235
+ return buildScript(`Delete Fundus tag ${JSON.stringify(input.tag)} from Figma node ${JSON.stringify(input.nodeId)}.`, `deleteFundusTag(figma, ${JSON.stringify(input)})`);
236
+ }
@@ -3,6 +3,8 @@ export { createEmptyLibrary, parseLibrary, serializeLibrary } from './library.js
3
3
  export { rawFileNameError, rawFilePathError, portableRawPathKey, isFundusTransactionName } from './raw-path.js';
4
4
  export { ROOT_FOLDER, FOLDER_SENTINEL, folderNameError, folderPathError, normalizeFolderPath, portableFolderPathKey, parentFolderPath, folderNameOf, joinFolderPath, folderOfRawPath, rawFileNameOfPath, isFolderOrDescendant, replaceFolderPrefix } from './folder-path.js';
5
5
  export { RESERVED_ASSET_IDS, isValidAssetId, assetIdError, manifestNameError } from './asset-id.js';
6
+ export type { SetFigmaTagInput, SetFigmaTagResult, DeleteFigmaTagInput, DeleteFigmaTagResult } from './figma-tag-script.js';
7
+ export { buildSetTagScript, buildDeleteTagScript, isInstanceSublayerId } from './figma-tag-script.js';
6
8
  export type { ManifestColor } from './manifest-appearance.js';
7
9
  export { MANIFEST_COLORS, MANIFEST_EMOJIS, isManifestColor, isManifestEmoji, resolveManifestColor, manifestColorError, manifestEmojiError } from './manifest-appearance.js';
8
10
  export { acceptedExtensions, acceptedExtensionsForType, typesForFileName, proposeAssetId } from './ingest.js';
@@ -2,6 +2,7 @@ export { createEmptyLibrary, parseLibrary, serializeLibrary } from './library.js
2
2
  export { rawFileNameError, rawFilePathError, portableRawPathKey, isFundusTransactionName } from './raw-path.js';
3
3
  export { ROOT_FOLDER, FOLDER_SENTINEL, folderNameError, folderPathError, normalizeFolderPath, portableFolderPathKey, parentFolderPath, folderNameOf, joinFolderPath, folderOfRawPath, rawFileNameOfPath, isFolderOrDescendant, replaceFolderPrefix } from './folder-path.js';
4
4
  export { RESERVED_ASSET_IDS, isValidAssetId, assetIdError, manifestNameError } from './asset-id.js';
5
+ export { buildSetTagScript, buildDeleteTagScript, isInstanceSublayerId } from './figma-tag-script.js';
5
6
  export { MANIFEST_COLORS, MANIFEST_EMOJIS, isManifestColor, isManifestEmoji, resolveManifestColor, manifestColorError, manifestEmojiError } from './manifest-appearance.js';
6
7
  export { acceptedExtensions, acceptedExtensionsForType, typesForFileName, proposeAssetId } from './ingest.js';
7
8
  export { canonicalizeParameters, compareCodeUnits } from './canonicalize.js';
@@ -1,5 +1,6 @@
1
1
  import type { ImageManifestEntry, SliceManifestEntry } from '../core/index.js';
2
- import type { RetainedEntry } from './retained-entry.js';
2
+ import type { EntryHandle } from './retained-entry.js';
3
+ import type { RetainedEntry } from './retained-entry.svelte.js';
3
4
  /** Uses the target's existing 2D context, including its transform and clip. */
4
5
  export type CanvasDrawTarget = HTMLCanvasElement | CanvasRenderingContext2D;
5
6
  /**
@@ -8,13 +9,15 @@ export type CanvasDrawTarget = HTMLCanvasElement | CanvasRenderingContext2D;
8
9
  * The caller owns DPR scaling; explicit dimensions are never scaled by Fundus.
9
10
  * Preserves canvas contents and context state. Non-positive sizes draw nothing.
10
11
  *
11
- * Takes the handle from `await retainEntry(entry)`; keep it unreleased until
12
- * the last draw. This function never loads or decodes the source itself.
12
+ * Takes the handle from `await retainEntry(entry)` or a `RetainedEntry`; keep
13
+ * it unreleased until the last draw. This function never loads or decodes the
14
+ * source itself.
13
15
  *
14
- * @throws {Error} When the handle was released, even for an empty draw.
16
+ * @throws {Error} When the source is released or not loaded yet, even for an
17
+ * empty draw.
15
18
  * @throws {RangeError} When any coordinate or dimension is not finite.
16
19
  */
17
- export declare function drawImage(target: CanvasDrawTarget, retained: RetainedEntry<ImageManifestEntry>, x: number, y: number, width: number, height: number): void;
20
+ export declare function drawImage(target: CanvasDrawTarget, retained: EntryHandle<ImageManifestEntry> | RetainedEntry<ImageManifestEntry>, x: number, y: number, width: number, height: number): void;
18
21
  /**
19
22
  * Synchronously paint a retained slice. (x, y, width, height) describes its
20
23
  * core layout box in current context units; baked-in overdraw paints outside it.
@@ -22,13 +25,15 @@ export declare function drawImage(target: CanvasDrawTarget, retained: RetainedEn
22
25
  * The caller owns DPR scaling, clipping, and clearing. Context state is preserved.
23
26
  * Non-positive sizes draw nothing.
24
27
  *
25
- * Takes the handle from `await retainEntry(entry)`; keep it unreleased until
26
- * the last draw. This function never loads or decodes the source itself.
28
+ * Takes the handle from `await retainEntry(entry)` or a `RetainedEntry`; keep
29
+ * it unreleased until the last draw. This function never loads or decodes the
30
+ * source itself.
27
31
  *
28
- * @throws {Error} When the handle was released, even for an empty draw.
32
+ * @throws {Error} When the source is released or not loaded yet, even for an
33
+ * empty draw.
29
34
  * @throws {RangeError} When any coordinate or dimension is not finite.
30
35
  */
31
- export declare function drawSlice(target: CanvasDrawTarget, retained: RetainedEntry<SliceManifestEntry>, x: number, y: number, width: number, height: number): void;
36
+ export declare function drawSlice(target: CanvasDrawTarget, retained: EntryHandle<SliceManifestEntry> | RetainedEntry<SliceManifestEntry>, x: number, y: number, width: number, height: number): void;
32
37
  /**
33
38
  * Paint `sliceGrid`'s cells by its paint rule, without modifying the caller's
34
39
  * transform. Shared with Slice, whose source lifecycle also supports loading
@@ -5,6 +5,14 @@ function contextOf(target) {
5
5
  throw new Error('Fundus drawing requires a 2D canvas context.');
6
6
  return context;
7
7
  }
8
+ /** Reads a `RetainedEntry`'s reactive source, so a drawing effect re-runs once it loads. */
9
+ function sourceOf(retained) {
10
+ const source = retained.source;
11
+ if (source === undefined) {
12
+ throw new Error(`RetainedEntry "${retained.entry.id}" has no loaded source.`);
13
+ }
14
+ return source;
15
+ }
8
16
  function hasArea(x, y, width, height) {
9
17
  if (![x, y, width, height].every(Number.isFinite)) {
10
18
  throw new RangeError('Fundus drawing coordinates and dimensions must be finite.');
@@ -17,14 +25,16 @@ function hasArea(x, y, width, height) {
17
25
  * The caller owns DPR scaling; explicit dimensions are never scaled by Fundus.
18
26
  * Preserves canvas contents and context state. Non-positive sizes draw nothing.
19
27
  *
20
- * Takes the handle from `await retainEntry(entry)`; keep it unreleased until
21
- * the last draw. This function never loads or decodes the source itself.
28
+ * Takes the handle from `await retainEntry(entry)` or a `RetainedEntry`; keep
29
+ * it unreleased until the last draw. This function never loads or decodes the
30
+ * source itself.
22
31
  *
23
- * @throws {Error} When the handle was released, even for an empty draw.
32
+ * @throws {Error} When the source is released or not loaded yet, even for an
33
+ * empty draw.
24
34
  * @throws {RangeError} When any coordinate or dimension is not finite.
25
35
  */
26
36
  export function drawImage(target, retained, x, y, width, height) {
27
- const source = retained.source;
37
+ const source = sourceOf(retained);
28
38
  if (!hasArea(x, y, width, height))
29
39
  return;
30
40
  contextOf(target).drawImage(source, x, y, width, height);
@@ -36,14 +46,16 @@ export function drawImage(target, retained, x, y, width, height) {
36
46
  * The caller owns DPR scaling, clipping, and clearing. Context state is preserved.
37
47
  * Non-positive sizes draw nothing.
38
48
  *
39
- * Takes the handle from `await retainEntry(entry)`; keep it unreleased until
40
- * the last draw. This function never loads or decodes the source itself.
49
+ * Takes the handle from `await retainEntry(entry)` or a `RetainedEntry`; keep
50
+ * it unreleased until the last draw. This function never loads or decodes the
51
+ * source itself.
41
52
  *
42
- * @throws {Error} When the handle was released, even for an empty draw.
53
+ * @throws {Error} When the source is released or not loaded yet, even for an
54
+ * empty draw.
43
55
  * @throws {RangeError} When any coordinate or dimension is not finite.
44
56
  */
45
57
  export function drawSlice(target, retained, x, y, width, height) {
46
- const source = retained.source;
58
+ const source = sourceOf(retained);
47
59
  if (!hasArea(x, y, width, height))
48
60
  return;
49
61
  paintSlice(contextOf(target), retained.entry, source, x, y, width, height);
@@ -2,7 +2,9 @@ export { Manifest, createManifest } from './manifest.js';
2
2
  export { drawImage, drawSlice } from './canvas.js';
3
3
  export type { CanvasDrawTarget } from './canvas.js';
4
4
  export { retainEntry } from './retained-entry.js';
5
- export type { RetainedEntry, RetainedSourceOf } from './retained-entry.js';
5
+ export type { EntryHandle, RetainedSourceOf } from './retained-entry.js';
6
+ export { RetainedEntry } from './retained-entry.svelte.js';
7
+ export type { RetainedEntryStatus } from './retained-entry.svelte.js';
6
8
  export { default as Image } from './components/Image.svelte';
7
9
  export { default as Slice } from './components/Slice.svelte';
8
10
  export { sliceGrid, sliceLayoutGeometry } from './geometry.js';
@@ -2,6 +2,7 @@
2
2
  export { Manifest, createManifest } from './manifest.js';
3
3
  export { drawImage, drawSlice } from './canvas.js';
4
4
  export { retainEntry } from './retained-entry.js';
5
+ export { RetainedEntry } from './retained-entry.svelte.js';
5
6
  export { default as Image } from './components/Image.svelte';
6
7
  export { default as Slice } from './components/Slice.svelte';
7
8
  export { sliceGrid, sliceLayoutGeometry } from './geometry.js';
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { ManifestEntry } from '../core/index.js';
9
9
  /**
10
- * The retained source a `RetainedEntry` exposes for its entry kind: decoded
10
+ * The retained source an `EntryHandle` exposes for its entry kind: decoded
11
11
  * pixels for image-backed kinds, a playable URL (an object URL over the fully
12
12
  * fetched file where supported) for media kinds.
13
13
  */
@@ -17,7 +17,7 @@ export type RetainedSourceOf<E extends ManifestEntry> = E extends {
17
17
  kind: 'video' | 'chroma-key-video' | 'audio';
18
18
  } ? string : never;
19
19
  /** One hold on an entry's loaded source, from `retainEntry`. */
20
- export interface RetainedEntry<E extends ManifestEntry> {
20
+ export interface EntryHandle<E extends ManifestEntry> {
21
21
  readonly entry: E;
22
22
  /**
23
23
  * The loaded source. The same object for every handle while any holder
@@ -45,4 +45,4 @@ export interface RetainedEntry<E extends ManifestEntry> {
45
45
  * fails, or an image-backed source cannot be decoded in this environment.
46
46
  * A rejection leaves no hold behind.
47
47
  */
48
- export declare function retainEntry<E extends ManifestEntry>(entry: E): Promise<RetainedEntry<E>>;
48
+ export declare function retainEntry<E extends ManifestEntry>(entry: E): Promise<EntryHandle<E>>;
@@ -1,6 +1,6 @@
1
1
  import { assertEntryByteSize } from './manifest.js';
2
2
  import { acquireSource, releaseSource, resolveRetainedImageSource, resolveRetainedSource } from './preload-registry.js';
3
- class RetainedEntryHandle {
3
+ class Handle {
4
4
  entry;
5
5
  /** `null` once released. */
6
6
  #source;
@@ -12,7 +12,7 @@ class RetainedEntryHandle {
12
12
  // reads it — only an explicit access after release throws.
13
13
  get source() {
14
14
  if (this.#source === null) {
15
- throw new Error(`RetainedEntry "${this.entry.id}" was released.`);
15
+ throw new Error(`EntryHandle "${this.entry.id}" was released.`);
16
16
  }
17
17
  return this.#source;
18
18
  }
@@ -55,7 +55,7 @@ export async function retainEntry(entry) {
55
55
  releaseSource(src);
56
56
  throw new Error(`Cannot retain "${id}": this environment cannot decode ${kind} sources.`);
57
57
  }
58
- return new RetainedEntryHandle(entry, source);
58
+ return new Handle(entry, source);
59
59
  }
60
- return new RetainedEntryHandle(entry, resolveRetainedSource(src));
60
+ return new Handle(entry, resolveRetainedSource(src));
61
61
  }
@@ -0,0 +1,26 @@
1
+ import type { ManifestEntry } from '../core/index.js';
2
+ import { type RetainedSourceOf } from './retained-entry.js';
3
+ export type RetainedEntryStatus = 'loading' | 'ready' | 'error' | 'released';
4
+ /**
5
+ * One hold on `entry`'s own source for the lifetime of the constructing
6
+ * component, shared with manifests and handles exactly like `retainEntry`.
7
+ * Construct it during component initialisation; the hold is dropped on
8
+ * destroy, including when the load is still in flight.
9
+ *
10
+ * Loading starts on mount, so server rendering never loads and reports
11
+ * `loading`.
12
+ *
13
+ * @throws {Error} When constructed outside component initialisation.
14
+ */
15
+ export declare class RetainedEntry<E extends ManifestEntry> {
16
+ #private;
17
+ readonly entry: E;
18
+ constructor(entry: E);
19
+ /** The loaded source; `undefined` while loading, after an error, and after release. */
20
+ get source(): RetainedSourceOf<E> | undefined;
21
+ get status(): RetainedEntryStatus;
22
+ /** The rejection reason while `status` is `error`. */
23
+ get error(): unknown;
24
+ /** Drop the hold before destroy. Idempotent; safe to call unbound. */
25
+ readonly release: () => void;
26
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Component-scoped retention: an `EntryHandle` whose lifetime follows the
3
+ * component that creates it, with the source exposed as reactive state.
4
+ */
5
+ import { onDestroy, onMount } from 'svelte';
6
+ import { retainEntry } from './retained-entry.js';
7
+ /**
8
+ * One hold on `entry`'s own source for the lifetime of the constructing
9
+ * component, shared with manifests and handles exactly like `retainEntry`.
10
+ * Construct it during component initialisation; the hold is dropped on
11
+ * destroy, including when the load is still in flight.
12
+ *
13
+ * Loading starts on mount, so server rendering never loads and reports
14
+ * `loading`.
15
+ *
16
+ * @throws {Error} When constructed outside component initialisation.
17
+ */
18
+ export class RetainedEntry {
19
+ entry;
20
+ #handle = $state.raw();
21
+ #status = $state('loading');
22
+ #error = $state.raw();
23
+ constructor(entry) {
24
+ this.entry = entry;
25
+ onMount(() => {
26
+ if (this.#status === 'released')
27
+ return;
28
+ retainEntry(entry).then((handle) => {
29
+ if (this.#status === 'released') {
30
+ handle.release();
31
+ return;
32
+ }
33
+ this.#handle = handle;
34
+ this.#status = 'ready';
35
+ }, (error) => {
36
+ // A rejected retain leaves no hold behind.
37
+ if (this.#status === 'released')
38
+ return;
39
+ this.#error = error;
40
+ this.#status = 'error';
41
+ });
42
+ });
43
+ onDestroy(this.release);
44
+ }
45
+ /** The loaded source; `undefined` while loading, after an error, and after release. */
46
+ get source() {
47
+ return this.#handle?.source;
48
+ }
49
+ get status() {
50
+ return this.#status;
51
+ }
52
+ /** The rejection reason while `status` is `error`. */
53
+ get error() {
54
+ return this.#error;
55
+ }
56
+ /** Drop the hold before destroy. Idempotent; safe to call unbound. */
57
+ release = () => {
58
+ this.#status = 'released';
59
+ this.#handle?.release();
60
+ this.#handle = undefined;
61
+ };
62
+ }
@@ -12,6 +12,12 @@ export declare const ASSET_SET_HELP = "fundus asset set \u2014 update an asset's
12
12
  export declare const ASSET_RENAME_HELP = "fundus asset rename \u2014 rename an asset id\n\nUsage: fundus asset rename <id> <newId> [--json]\n\nRenames the asset's id (the raw file keeps its name \u2014 ids are the\nstable key) and re-hashes its proxy under the new id via the autosave\nchain. Fails if another asset references <id> \u2014 update the referrers\nfirst \u2014 or if <newId> is taken or invalid.\n\nFlags:\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset rename sidebarPanel navigationPanel --json\n";
13
13
  export declare const ASSET_DELETE_HELP = "fundus asset delete \u2014 delete an asset\n\nUsage: fundus asset delete <id> [--json]\n\nDeletes the library record, its raw file, and its proxies, then\nregenerates the affected manifest modules. Fails if another asset\nreferences it \u2014 remove the references first.\n\nFlags:\n --json print {\"ok\": true, \"deletedAssetId\": \"<id>\", \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset delete obsoleteBanner --json\n";
14
14
  export declare const ASSET_MOVE_HELP = "fundus asset move \u2014 move an asset between folders\n\nUsage: fundus asset move <id> <folder|root> [--json]\n\nMoves the raw file and its current proxy without changing the stable asset id,\nthen runs the autosave chain. The destination folder must already exist; use\nthe literal \"root\" for the implicit root.\n\nFlags:\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset move navigationPanel \"UI/Buttons\" --json\n";
15
+ /**
16
+ * `--figma-file` accepts either a raw Figma file key or a full design/file link.
17
+ * A value that parses as a URL is treated as a link (fileKey extracted via
18
+ * `parseFigmaFileKey`); anything else is passed through as a raw key.
19
+ */
20
+ export declare function resolveFigmaFileKeyFlag(value: string): string;
15
21
  export declare function runAssetCommand(argumentList: string[]): Promise<void>;
16
22
  export declare const FOLDER_HELP = "fundus folder \u2014 manage the mirrored raw/proxy folder tree\n\nUsage: fundus folder <subcommand> \u2026\n\nSubcommands:\n list list registered folders and their direct assets\n create create a persisted folder (fundus folder create <path>)\n rename rename one folder segment and its subtree\n move move a complete folder subtree below another folder\n delete recursively delete a folder subtree and its assets\n\nAll mutations update rawDir and proxyDir together, maintain .gitkeep files,\nand run the full autosave chain. All subcommands accept --json and --help.\n";
17
23
  export declare function runFolderCommand(argumentList: string[]): Promise<void>;
@@ -898,7 +898,7 @@ function parseFigmaScale(value, commandPath) {
898
898
  * A value that parses as a URL is treated as a link (fileKey extracted via
899
899
  * `parseFigmaFileKey`); anything else is passed through as a raw key.
900
900
  */
901
- function resolveFigmaFileKeyFlag(value) {
901
+ export function resolveFigmaFileKeyFlag(value) {
902
902
  try {
903
903
  new URL(value);
904
904
  }
@@ -0,0 +1,2 @@
1
+ export declare const FIGMA_HELP = "fundus figma \u2014 prepare Fundus tag writes in Figma\n\nUsage: fundus figma <subcommand> \u2026 [--json]\n\nThe Figma REST API cannot write plugin data, so Fundus cannot tag nodes by\nitself. These commands check the request against the file (via REST) and the\nlibrary, then print a script. Run that script unchanged with the Figma MCP\n`use_figma` tool on the same file: it re-checks the live document (the set\nscript loads every page, which takes a few seconds on large files), writes\nthe tag and its Dev Mode annotation exactly like the Fundus Figma plugin, and\nreturns the result. It throws without writing when the document changed.\n\nSubcommands:\n prepare-set-tag tag a node (--node-id, --tag)\n prepare-delete-tag remove a node's tag (--tag or --node-id)\n\nRequires import.figma.token in fundus.config.ts.\n";
2
+ export declare function runFigmaCommand(argumentList: string[]): Promise<void>;