fundus 0.28.1 → 0.29.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 (179) hide show
  1. package/README.md +41 -33
  2. package/build/client/_app/immutable/chunks/{OL1Dh1oP.js → BCmR1_e0.js} +1 -1
  3. package/build/client/_app/immutable/chunks/BCmR1_e0.js.br +0 -0
  4. package/build/client/_app/immutable/chunks/BCmR1_e0.js.gz +0 -0
  5. package/build/client/_app/immutable/chunks/{apKHI4vq.js → C0uU_Rhe.js} +2 -2
  6. package/build/client/_app/immutable/chunks/C0uU_Rhe.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/C0uU_Rhe.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/{g2rma9Ym.js → CMJ10zbJ.js} +1 -1
  9. package/build/client/_app/immutable/chunks/CMJ10zbJ.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/CMJ10zbJ.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/{QlQ-jeIO.js → kkVEh3_1.js} +1 -1
  12. package/build/client/_app/immutable/chunks/kkVEh3_1.js.br +2 -0
  13. package/build/client/_app/immutable/chunks/kkVEh3_1.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/wBLhaPea.js +1 -0
  15. package/build/client/_app/immutable/chunks/wBLhaPea.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/wBLhaPea.js.gz +0 -0
  17. package/build/client/_app/immutable/entry/{app.DpZM49_s.js → app.DCXSqiQ1.js} +2 -2
  18. package/build/client/_app/immutable/entry/app.DCXSqiQ1.js.br +0 -0
  19. package/build/client/_app/immutable/entry/app.DCXSqiQ1.js.gz +0 -0
  20. package/build/client/_app/immutable/entry/start.BOUpxuMU.js +1 -0
  21. package/build/client/_app/immutable/entry/start.BOUpxuMU.js.br +0 -0
  22. package/build/client/_app/immutable/entry/start.BOUpxuMU.js.gz +0 -0
  23. package/build/client/_app/immutable/nodes/{0.Wqvn8XD-.js → 0.CsqkK05g.js} +1 -1
  24. package/build/client/_app/immutable/nodes/0.CsqkK05g.js.br +0 -0
  25. package/build/client/_app/immutable/nodes/0.CsqkK05g.js.gz +0 -0
  26. package/build/client/_app/immutable/nodes/{1.Z-UvvLkP.js → 1.BVSXvtU9.js} +1 -1
  27. package/build/client/_app/immutable/nodes/1.BVSXvtU9.js.br +2 -0
  28. package/build/client/_app/immutable/nodes/1.BVSXvtU9.js.gz +0 -0
  29. package/build/client/_app/immutable/nodes/{2.lu89W_qJ.js → 2.BJEXWmrT.js} +1 -1
  30. package/build/client/_app/immutable/nodes/2.BJEXWmrT.js.br +0 -0
  31. package/build/client/_app/immutable/nodes/2.BJEXWmrT.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/{3.Dt32EAoq.js → 3.BDMmDyL1.js} +1 -1
  33. package/build/client/_app/immutable/nodes/3.BDMmDyL1.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/3.BDMmDyL1.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/{4.WipzcX8_.js → 4.dd49_qQE.js} +1 -1
  36. package/build/client/_app/immutable/nodes/4.dd49_qQE.js.br +0 -0
  37. package/build/client/_app/immutable/nodes/4.dd49_qQE.js.gz +0 -0
  38. package/build/client/_app/version.json +1 -1
  39. package/build/client/_app/version.json.br +0 -0
  40. package/build/client/_app/version.json.gz +0 -0
  41. package/build/handler.js +7 -7
  42. package/build/index.js +7 -7
  43. package/build/server/chunks/chunks/{context.js-BZSZNuAd.js → context.js-DFZgDxCD.js} +157 -95
  44. package/build/server/chunks/chunks/context.js-DFZgDxCD.js.map +1 -0
  45. package/build/server/chunks/chunks/{contract.js-B4JdXFgm.js → contract.js-DGZXpcNX.js} +7 -7
  46. package/build/server/chunks/chunks/{contract.js-B4JdXFgm.js.map → contract.js-DGZXpcNX.js.map} +1 -1
  47. package/build/server/chunks/chunks/{core.js-BHxa3VBo.js → core.js-AGKlECJ0.js} +32 -32
  48. package/build/server/chunks/chunks/core.js-AGKlECJ0.js.map +1 -0
  49. package/build/server/chunks/chunks/{exports.js-D0dALD2Q.js → exports.js-BsVKSGJk.js} +2 -2
  50. package/build/server/chunks/chunks/{exports.js-D0dALD2Q.js.map → exports.js-BsVKSGJk.js.map} +1 -1
  51. package/build/server/chunks/chunks/{http.js-DYYKvppy.js → http.js-BQhjMaW9.js} +2 -2
  52. package/build/server/chunks/chunks/{http.js-DYYKvppy.js.map → http.js-BQhjMaW9.js.map} +1 -1
  53. package/build/server/chunks/chunks/{internal.js-CJkB_gzV.js → internal.js-DJGcNryj.js} +3 -3
  54. package/build/server/chunks/chunks/{internal.js-CJkB_gzV.js.map → internal.js-DJGcNryj.js.map} +1 -1
  55. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  56. package/build/server/chunks/chunks/{manifest-membership.js-BkrPsM_r.js → manifest-membership.js-DkHaPk2F.js} +2 -2
  57. package/build/server/chunks/chunks/{manifest-membership.js-BkrPsM_r.js.map → manifest-membership.js-DkHaPk2F.js.map} +1 -1
  58. package/build/server/chunks/chunks/{raw-reconciliation.js-DPnNXe0b.js → raw-reconciliation.js-D2sI2C-t.js} +3 -3
  59. package/build/server/chunks/chunks/raw-reconciliation.js-D2sI2C-t.js.map +1 -0
  60. package/build/server/chunks/chunks/{raw-reconciliation2.js-DKOpJ8if.js → raw-reconciliation2.js-CMpUvuMd.js} +2 -2
  61. package/build/server/chunks/chunks/{raw-reconciliation2.js-DKOpJ8if.js.map → raw-reconciliation2.js-CMpUvuMd.js.map} +1 -1
  62. package/build/server/chunks/chunks/{shared.js-D3ZDVUlh.js → shared.js-BtmYg0tL.js} +2 -2
  63. package/build/server/chunks/chunks/{shared.js-D3ZDVUlh.js.map → shared.js-BtmYg0tL.js.map} +1 -1
  64. package/build/server/chunks/chunks/{utils.js-CqWW_v-5.js → utils.js-CqXk0Ug6.js} +3 -3
  65. package/build/server/chunks/chunks/{utils.js-CqWW_v-5.js.map → utils.js-CqXk0Ug6.js.map} +1 -1
  66. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Dw2MW5Tm.js → _server.ts.js-DKGD9Jk2.js} +6 -6
  67. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-Dw2MW5Tm.js.map → _server.ts.js-DKGD9Jk2.js.map} +1 -1
  68. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-GQDuJGug.js → _server.ts.js-C1UfF0EO.js} +7 -7
  69. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-GQDuJGug.js.map → _server.ts.js-C1UfF0EO.js.map} +1 -1
  70. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-JbLSv7Xx.js → _server.ts.js-DpASkk00.js} +7 -7
  71. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-JbLSv7Xx.js.map → _server.ts.js-DpASkk00.js.map} +1 -1
  72. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-BPae-BYF.js → _server.ts.js-C3jU2vC8.js} +7 -7
  73. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-BPae-BYF.js.map → _server.ts.js-C3jU2vC8.js.map} +1 -1
  74. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/{_server.ts.js-BafrrUxu.js → _server.ts.js-B7OwufF3.js} +7 -7
  75. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/{_server.ts.js-BafrrUxu.js.map → _server.ts.js-B7OwufF3.js.map} +1 -1
  76. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-UOlSNEi9.js → _server.ts.js-COhg1nYk.js} +7 -7
  77. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-UOlSNEi9.js.map → _server.ts.js-COhg1nYk.js.map} +1 -1
  78. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-QE1vUuAx.js → _server.ts.js-DAEE17eM.js} +7 -7
  79. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-QE1vUuAx.js.map → _server.ts.js-DAEE17eM.js.map} +1 -1
  80. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DIVfppGt.js → _server.ts.js-BrZSS4gm.js} +7 -7
  81. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DIVfppGt.js.map → _server.ts.js-BrZSS4gm.js.map} +1 -1
  82. package/build/server/chunks/entries/endpoints/api/events/{_server.ts.js-B6R_kqP8.js → _server.ts.js-Cfst-Brd.js} +7 -7
  83. package/build/server/chunks/entries/endpoints/api/events/{_server.ts.js-B6R_kqP8.js.map → _server.ts.js-Cfst-Brd.js.map} +1 -1
  84. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CertbDsH.js → _server.ts.js-aN_h2Ifo.js} +7 -7
  85. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CertbDsH.js.map → _server.ts.js-aN_h2Ifo.js.map} +1 -1
  86. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DEIF3Hu2.js → _server.ts.js-CX2A66ij.js} +7 -7
  87. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-DEIF3Hu2.js.map → _server.ts.js-CX2A66ij.js.map} +1 -1
  88. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-DNFsNGr4.js → _server.ts.js-CwTiaheP.js} +7 -7
  89. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-DNFsNGr4.js.map → _server.ts.js-CwTiaheP.js.map} +1 -1
  90. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DDW8XRPe.js → _server.ts.js-fIbsr_2e.js} +7 -7
  91. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DDW8XRPe.js.map → _server.ts.js-fIbsr_2e.js.map} +1 -1
  92. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-C222-xli.js → _server.ts.js-Dvo1bsLi.js} +7 -7
  93. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-C222-xli.js.map → _server.ts.js-Dvo1bsLi.js.map} +1 -1
  94. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-CI1P1uyd.js → _server.ts.js-VLg1G3mN.js} +7 -7
  95. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-CI1P1uyd.js.map → _server.ts.js-VLg1G3mN.js.map} +1 -1
  96. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-DpiUwaSh.js → _server.ts.js-DqctsOop.js} +7 -7
  97. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-DpiUwaSh.js.map → _server.ts.js-DqctsOop.js.map} +1 -1
  98. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DT0VPqPT.js → _server.ts.js-CoxSU5iO.js} +7 -7
  99. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DT0VPqPT.js.map → _server.ts.js-CoxSU5iO.js.map} +1 -1
  100. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DBpHM42g.js → _server.ts.js-wUxsLZdr.js} +7 -7
  101. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DBpHM42g.js.map → _server.ts.js-wUxsLZdr.js.map} +1 -1
  102. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-Cxu5b6Au.js → _server.ts.js-BKsF0Oit.js} +7 -7
  103. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-Cxu5b6Au.js.map → _server.ts.js-BKsF0Oit.js.map} +1 -1
  104. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-W4dhMo8Y.js → _server.ts.js-Dk9oOZwM.js} +7 -7
  105. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-W4dhMo8Y.js.map → _server.ts.js-Dk9oOZwM.js.map} +1 -1
  106. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DJ_adxbT.js → _server.ts.js-DxjNmmtL.js} +7 -7
  107. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DJ_adxbT.js.map → _server.ts.js-DxjNmmtL.js.map} +1 -1
  108. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-BisoHihD.js → _server.ts.js-DD0b38VV.js} +6 -6
  109. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-BisoHihD.js.map → _server.ts.js-DD0b38VV.js.map} +1 -1
  110. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-D4jLLuko.js → _server.ts.js-DId6F2i_.js} +7 -7
  111. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-D4jLLuko.js.map → _server.ts.js-DId6F2i_.js.map} +1 -1
  112. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Dfv2ItjT.js → _server.ts.js-DKKkGoK2.js} +7 -7
  113. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Dfv2ItjT.js.map → _server.ts.js-DKKkGoK2.js.map} +1 -1
  114. package/build/server/chunks/entries/{hooks.server.js-oHJX5INm.js → hooks.server.js-B5Ctvorj.js} +6 -6
  115. package/build/server/chunks/entries/{hooks.server.js-oHJX5INm.js.map → hooks.server.js-B5Ctvorj.js.map} +1 -1
  116. package/build/server/chunks/{handler-BAe0c9yw.js → handler-DwVfBx6r.js} +5 -5
  117. package/build/server/chunks/{handler-BAe0c9yw.js.map → handler-DwVfBx6r.js.map} +1 -1
  118. package/build/server/chunks/{index.js-DUYD4JB0.js → index.js-DgPuPQJn.js} +5 -5
  119. package/build/server/chunks/{index.js-DUYD4JB0.js.map → index.js-DgPuPQJn.js.map} +1 -1
  120. package/build/server/chunks/{manifest.js-zwun-T7O.js → manifest.js-B054EcAf.js} +31 -31
  121. package/build/server/chunks/{manifest.js-zwun-T7O.js.map → manifest.js-B054EcAf.js.map} +1 -1
  122. package/build/server/chunks/nodes/{0.js-BmkXwDRY.js → 0.js-DoHZTmnT.js} +2 -2
  123. package/build/server/chunks/nodes/{0.js-BmkXwDRY.js.map → 0.js-DoHZTmnT.js.map} +1 -1
  124. package/build/server/chunks/nodes/1.js-BcPlsloO.js +7 -0
  125. package/build/server/chunks/nodes/{1.js-xbOwEHCW.js.map → 1.js-BcPlsloO.js.map} +1 -1
  126. package/build/server/chunks/nodes/{2.js-BuNw1vTZ.js → 2.js-5qCXkvzl.js} +2 -2
  127. package/build/server/chunks/nodes/{2.js-BuNw1vTZ.js.map → 2.js-5qCXkvzl.js.map} +1 -1
  128. package/build/server/chunks/nodes/3.js-D6fAMX1w.js +7 -0
  129. package/build/server/chunks/nodes/{3.js-Dpr_DvNU.js.map → 3.js-D6fAMX1w.js.map} +1 -1
  130. package/build/server/chunks/nodes/4.js-CGkeT0SQ.js +7 -0
  131. package/build/server/chunks/nodes/{4.js-14Lypd4o.js.map → 4.js-CGkeT0SQ.js.map} +1 -1
  132. package/dist/cli.js +3 -4
  133. package/dist/core/figma-tag-links.d.ts +66 -0
  134. package/dist/core/figma-tag-links.js +97 -0
  135. package/dist/core/index.d.ts +2 -2
  136. package/dist/core/index.js +1 -1
  137. package/dist/server/cli-commands.d.ts +1 -1
  138. package/dist/server/cli-commands.js +1 -1
  139. package/dist/server/cli-figma-tags.d.ts +1 -1
  140. package/dist/server/cli-figma-tags.js +149 -88
  141. package/dist/server/figma-tags.d.ts +72 -24
  142. package/dist/server/figma-tags.js +130 -79
  143. package/dist/server/importers/figma.d.ts +20 -15
  144. package/dist/server/importers/figma.js +172 -92
  145. package/package.json +1 -1
  146. package/build/client/_app/immutable/chunks/Bsj1ssvc.js +0 -1
  147. package/build/client/_app/immutable/chunks/Bsj1ssvc.js.br +0 -0
  148. package/build/client/_app/immutable/chunks/Bsj1ssvc.js.gz +0 -0
  149. package/build/client/_app/immutable/chunks/OL1Dh1oP.js.br +0 -0
  150. package/build/client/_app/immutable/chunks/OL1Dh1oP.js.gz +0 -0
  151. package/build/client/_app/immutable/chunks/QlQ-jeIO.js.br +0 -2
  152. package/build/client/_app/immutable/chunks/QlQ-jeIO.js.gz +0 -0
  153. package/build/client/_app/immutable/chunks/apKHI4vq.js.br +0 -0
  154. package/build/client/_app/immutable/chunks/apKHI4vq.js.gz +0 -0
  155. package/build/client/_app/immutable/chunks/g2rma9Ym.js.br +0 -0
  156. package/build/client/_app/immutable/chunks/g2rma9Ym.js.gz +0 -0
  157. package/build/client/_app/immutable/entry/app.DpZM49_s.js.br +0 -0
  158. package/build/client/_app/immutable/entry/app.DpZM49_s.js.gz +0 -0
  159. package/build/client/_app/immutable/entry/start.B3EMVPEN.js +0 -1
  160. package/build/client/_app/immutable/entry/start.B3EMVPEN.js.br +0 -0
  161. package/build/client/_app/immutable/entry/start.B3EMVPEN.js.gz +0 -0
  162. package/build/client/_app/immutable/nodes/0.Wqvn8XD-.js.br +0 -0
  163. package/build/client/_app/immutable/nodes/0.Wqvn8XD-.js.gz +0 -0
  164. package/build/client/_app/immutable/nodes/1.Z-UvvLkP.js.br +0 -0
  165. package/build/client/_app/immutable/nodes/1.Z-UvvLkP.js.gz +0 -0
  166. package/build/client/_app/immutable/nodes/2.lu89W_qJ.js.br +0 -0
  167. package/build/client/_app/immutable/nodes/2.lu89W_qJ.js.gz +0 -0
  168. package/build/client/_app/immutable/nodes/3.Dt32EAoq.js.br +0 -0
  169. package/build/client/_app/immutable/nodes/3.Dt32EAoq.js.gz +0 -0
  170. package/build/client/_app/immutable/nodes/4.WipzcX8_.js.br +0 -0
  171. package/build/client/_app/immutable/nodes/4.WipzcX8_.js.gz +0 -0
  172. package/build/server/chunks/chunks/context.js-BZSZNuAd.js.map +0 -1
  173. package/build/server/chunks/chunks/core.js-BHxa3VBo.js.map +0 -1
  174. package/build/server/chunks/chunks/raw-reconciliation.js-DPnNXe0b.js.map +0 -1
  175. package/build/server/chunks/nodes/1.js-xbOwEHCW.js +0 -7
  176. package/build/server/chunks/nodes/3.js-Dpr_DvNU.js +0 -7
  177. package/build/server/chunks/nodes/4.js-14Lypd4o.js +0 -7
  178. package/dist/core/figma-tag-script.d.ts +0 -154
  179. package/dist/core/figma-tag-script.js +0 -248
@@ -6,7 +6,7 @@ export declare const ASSET_HELP = "fundus asset \u2014 inspect and mutate assets
6
6
  export declare const ASSET_LIST_HELP: string;
7
7
  export declare const ASSET_SHOW_HELP = "fundus asset show \u2014 one asset in detail\n\nUsage: fundus asset show <id> [--json]\n\nPrints the asset's raw file, content hash, parameters, explicit +\npassive manifest memberships, proxy status, reference edges (outgoing\nand incoming), and warnings.\n\nFlags:\n --json print {\"ok\": true, \"asset\": <AssetStateDto>}\n --help this help\n\nRead-only: nothing is processed or written.\n\nExample:\n fundus asset show navigationPanel --json\n";
8
8
  export declare const ASSET_INGEST_HELP: string;
9
- export declare const ASSET_REIMPORT_HELP = "fundus asset reimport \u2014 refresh an asset from its saved source\n\nUsage: fundus asset reimport <id> [--json]\n fundus asset reimport <id> --figma-link <selection-link> [--scale <1|2|3|4>] [--json]\n fundus asset reimport <id> --figma-tag <newTag> [--figma-file <key|link>] [--scale <1|2|3|4>] [--json]\n fundus asset reimport <id> --figma-file <key|link> [--scale <1|2|3|4>] [--json]\n fundus asset reimport <id> --scale <1|2|3|4> [--json]\n\nWithout overrides, re-materializes the persisted importer source (a Figma tag\nasset re-scans its file for the tagged node, self-healing after node moves).\nA Figma override may update the source or scale; omitted values are preserved.\nAsset type, parameters, folder, and manifests remain unchanged. Raw-file drift\nor concurrent changes abort without overwriting local edits.\n\nNode vs tag assets accept different overrides. A node asset takes --figma-link\n(and --scale); --figma-tag and --figma-file are rejected. A tag asset takes\n--figma-tag to change its tag, --figma-file to re-point in place to another\ndocument, and --scale; --figma-link is rejected. Because the tag IS the asset\nid, changing it with --figma-tag renames the asset to the new tag (the new id\nmust be valid and unused).\n\nFlags:\n --figma-link <link> node asset only: replace the persisted file/node selection\n --figma-tag <newTag> tag asset only: change the tag, renaming the asset to the\n new tag (the tag is the asset id)\n --figma-file <key|link>\n tag asset only: re-point in place to another file (raw\n key or a Figma link)\n --scale <1|2|3|4> replace the persisted Figma export scale\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset reimport navigationPanel\n fundus asset reimport navigationPanel --figma-link '<new-selection-link>'\n fundus asset reimport navigationPanel --figma-tag mainNavigationPanel\n fundus asset reimport navigationPanel --figma-file def456\n fundus asset reimport navigationPanel --scale 4\n fundus asset reimport navigationPanel --figma-link '<new-selection-link>' --scale 4 --json\n";
9
+ export declare const ASSET_REIMPORT_HELP = "fundus asset reimport \u2014 refresh an asset from its saved source\n\nUsage: fundus asset reimport <id> [--json]\n fundus asset reimport <id> --figma-link <selection-link> [--scale <1|2|3|4>] [--json]\n fundus asset reimport <id> --figma-tag <newTag> [--figma-file <key|link>] [--scale <1|2|3|4>] [--json]\n fundus asset reimport <id> --figma-file <key|link> [--scale <1|2|3|4>] [--json]\n fundus asset reimport <id> --scale <1|2|3|4> [--json]\n\nWithout overrides, re-materializes the persisted importer source (a Figma tag\nasset looks up the node carrying the tag again, self-healing after node moves).\nA Figma override may update the source or scale; omitted values are preserved.\nAsset type, parameters, folder, and manifests remain unchanged. Raw-file drift\nor concurrent changes abort without overwriting local edits.\n\nNode vs tag assets accept different overrides. A node asset takes --figma-link\n(and --scale); --figma-tag and --figma-file are rejected. A tag asset takes\n--figma-tag to change its tag, --figma-file to re-point in place to another\ndocument, and --scale; --figma-link is rejected. Because the tag IS the asset\nid, changing it with --figma-tag renames the asset to the new tag (the new id\nmust be valid and unused).\n\nFlags:\n --figma-link <link> node asset only: replace the persisted file/node selection\n --figma-tag <newTag> tag asset only: change the tag, renaming the asset to the\n new tag (the tag is the asset id)\n --figma-file <key|link>\n tag asset only: re-point in place to another file (raw\n key or a Figma link)\n --scale <1|2|3|4> replace the persisted Figma export scale\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset reimport navigationPanel\n fundus asset reimport navigationPanel --figma-link '<new-selection-link>'\n fundus asset reimport navigationPanel --figma-tag mainNavigationPanel\n fundus asset reimport navigationPanel --figma-file def456\n fundus asset reimport navigationPanel --scale 4\n fundus asset reimport navigationPanel --figma-link '<new-selection-link>' --scale 4 --json\n";
10
10
  export declare const ASSET_REPLACE_HELP = "fundus asset replace \u2014 replace an asset's raw file\n\nUsage: fundus asset replace <id> <file|figma-selection-link>\n [--from <file|figma>] [--scale <1|2|3|4>] [--json]\n fundus asset replace <id> --from figma --figma-tag <tag>\n [--figma-file <key|link>] [--scale <1|2|3|4>] [--json]\n\nReplaces the stored raw file and content hash while preserving the asset's\nid, type, folder, parameters, references, and manifest memberships. The new\nsource must be accepted by the existing asset type. File replacement remains\nthe default and disconnects any saved source. Figma replacement stores the new\nselection (node mode) or tag (tag mode) as a repeatable source. A tag\nreplacement must resolve to the asset's own id, so it can only re-point the\nsame asset's own tag. Runs the full autosave chain, so delivered assets are\nreprocessed and manifests regenerated.\n\nFlags:\n --from <file|figma> replacement source; default: file\n --figma-tag <tag> Figma tag mode: replace from the node tagged with this\n Fundus asset id (takes no positional link)\n --figma-file <key|link>\n fileKey for tag mode as a raw key or a Figma link;\n defaults to import.figma.defaultFileKey from\n fundus.config.ts\n --scale <1|2|3|4> Figma export scale; default:\n import.figma.defaults.scale from fundus.config.ts\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nExample:\n fundus asset replace navigationPanel ./exports/navigation_panel_v2.png --json\n fundus asset replace navigationPanel \\\n 'https://www.figma.com/design/abc/UI?node-id=12-34' --from figma --scale 3 --json\n fundus asset replace navigationPanel --from figma \\\n --figma-tag navigationPanel --figma-file abc --scale 3 --json\n";
11
11
  export declare const ASSET_SET_HELP = "fundus asset set \u2014 update an asset's parameters and/or memberships\n\nUsage: fundus asset set <id> [--parameters <json>] [--manifests <a,b>]\n [--compression-x <0-100>] [--compression-y <0-100>] [--json]\n\nAt least one change flag is required. Runs the full autosave chain\n(write library \u2192 process \u2192 regenerate manifest modules).\n\nFlags:\n --parameters <json> SHALLOW MERGE at the top level onto the current\n parameters, then plugin validation:\n {\"preset\": \"webp-hq\"} sets one field,\n keeps the others\n {\"chromaKey\": {\u2026}} replaces the whole\n chromaKey object\n {\"mask\": null} clears an optional\n field\n {\"mask\": {\"$asset\": \"someImage\"}}\n sets an asset\n reference\n --manifests <a,b> REPLACES the explicit membership set (declarative,\n idempotent); --manifests \"\" clears it\n --compression-x <0-100>\n --compression-y <0-100>\n set one Slice stretch-compression axis while preserving\n the omitted axis; setting both axes to 0 removes\n compression. These values override compression supplied\n through --parameters in the same invocation\n --json print {\"ok\": true, \"asset\": <AssetStateDto>, \"issues\": [\u2026]}\n --help this help\n\nThe mutation is validated first and rejected (exit 1, nothing written)\nif it would leave the library invalid \u2014 e.g. a reference to a missing\nasset, a reference cycle, or an unknown manifest name. Ingest reference\ntargets before pointing at them.\n\nExample:\n fundus asset set navigationPanel --compression-x 75 --compression-y 25 --json\n fundus asset set characterIdle \\\n --parameters '{\"mask\": {\"$asset\": \"characterIdleMask\"}}' --json\n";
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";
@@ -437,7 +437,7 @@ Usage: fundus asset reimport <id> [--json]
437
437
  fundus asset reimport <id> --scale <1|2|3|4> [--json]
438
438
 
439
439
  Without overrides, re-materializes the persisted importer source (a Figma tag
440
- asset re-scans its file for the tagged node, self-healing after node moves).
440
+ asset looks up the node carrying the tag again, self-healing after node moves).
441
441
  A Figma override may update the source or scale; omitted values are preserved.
442
442
  Asset type, parameters, folder, and manifests remain unchanged. Raw-file drift
443
443
  or concurrent changes abort without overwriting local edits.
@@ -1,2 +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";
1
+ export declare const FIGMA_HELP = "fundus figma \u2014 write Fundus tag links in Figma\n\nUsage: fundus figma <subcommand> \u2026 [--json]\n\nA Fundus tag is a Dev Resources link on the node:\nhttps://fundus.invalid/asset/<tag>, named \"Fundus: <tag>\". These commands\nread the file's links and the tagged nodes over the REST API, check the\nrequest against them and the library, then write the links directly. The\nDev Mode annotation follows once the Fundus Figma plugin opens the node's page.\n\nSubcommands:\n set-tag tag a node (--node-id, --tag)\n delete-tag remove a node's tag (--tag or --node-id)\n\nRequires import.figma.token in fundus.config.ts: a personal access token with\nthe file_content:read and file_dev_resources:read scopes, plus\nfile_dev_resources:write for these commands.\n";
2
2
  export declare function runFigmaCommand(argumentList: string[]): Promise<void>;
@@ -1,98 +1,110 @@
1
1
  /**
2
- * The `fundus figma` command group: prepare Fundus tag writes for Figma. The
3
- * REST API cannot write plugin data, so these commands only validate (tag
4
- * grammar, uniqueness in the file, library collisions) and print a script the
5
- * caller runs with Figma's MCP `use_figma` tool.
2
+ * The `fundus figma` command group: set and delete Fundus tag links on Figma
3
+ * nodes over the REST API. The tag commands validate against a confirmed
4
+ * snapshot of the file (see figma-tags.ts) and the library before writing.
6
5
  */
7
6
  import { assetIdError } from '../core/index.js';
8
7
  import { loadConfig } from './config-loader.js';
9
- import { FigmaTagConflictError, loadFigmaTagPlanContext, parseFigmaNodeId, planDeleteFigmaTag, planSetFigmaTag } from './figma-tags.js';
8
+ import { FigmaTagConflictError, applyFigmaTagWrites, loadFigmaTagSnapshot, parseFigmaNodeId, planDeleteFigmaTag, planSetFigmaTag } from './figma-tags.js';
9
+ import { LibraryStore } from './library-store.js';
10
10
  import { resolveFigmaFileKeyFlag } from './cli-commands.js';
11
11
  import { CliUsageError, parseArguments, printJsonDocument, requirePositionals } from './cli-io.js';
12
- export const FIGMA_HELP = `fundus figma — prepare Fundus tag writes in Figma
12
+ const NEXT_STEP = "The Fundus Figma plugin updates the node's annotation once it opens the node's page.";
13
+ export const FIGMA_HELP = `fundus figma — write Fundus tag links in Figma
13
14
 
14
15
  Usage: fundus figma <subcommand> … [--json]
15
16
 
16
- The Figma REST API cannot write plugin data, so Fundus cannot tag nodes by
17
- itself. These commands check the request against the file (via REST) and the
18
- library, then print a script. Run that script unchanged with the Figma MCP
19
- \`use_figma\` tool on the same file: it re-checks the live document (the set
20
- script loads every page, which takes a few seconds on large files), writes
21
- the tag and its Dev Mode annotation exactly like the Fundus Figma plugin, and
22
- returns the result. It throws without writing when the document changed.
17
+ A Fundus tag is a Dev Resources link on the node:
18
+ https://fundus.invalid/asset/<tag>, named "Fundus: <tag>". These commands
19
+ read the file's links and the tagged nodes over the REST API, check the
20
+ request against them and the library, then write the links directly. The
21
+ Dev Mode annotation follows once the Fundus Figma plugin opens the node's page.
23
22
 
24
23
  Subcommands:
25
- prepare-set-tag tag a node (--node-id, --tag)
26
- prepare-delete-tag remove a node's tag (--tag or --node-id)
24
+ set-tag tag a node (--node-id, --tag)
25
+ delete-tag remove a node's tag (--tag or --node-id)
27
26
 
28
- Requires import.figma.token in fundus.config.ts.
27
+ Requires import.figma.token in fundus.config.ts: a personal access token with
28
+ the file_content:read and file_dev_resources:read scopes, plus
29
+ file_dev_resources:write for these commands.
29
30
  `;
30
- const PREPARE_SET_TAG_HELP = `fundus figma prepare-set-tag — prepare a use_figma script that tags a node
31
+ const SET_TAG_HELP = `fundus figma set-tag — tag a Figma node with a Fundus asset id
31
32
 
32
- Usage: fundus figma prepare-set-tag --node-id <id> --tag <tag>
33
- [--file <key|link>] [--force] [--json]
33
+ Usage: fundus figma set-tag --node-id <id> --tag <tag>
34
+ [--file <key|link>] [--force] [--json]
34
35
 
35
- Refuses unless --force, listing the file's existing tags, when another node
36
- already carries the tag; --force moves the tag (and its annotation) from those
37
- nodes to this one, so it stays unique. Also refuses unless --force when the
38
- node already carries a tag (the same one or another) or a Fundus asset with
39
- that id exists from another source. An asset imported from this very node by
40
- node id is not a conflict: tagging prepares its move to the tag
41
- (\`fundus asset replace <tag> --from figma --figma-tag <tag>\` afterwards keeps
42
- its id, parameters, and manifests).
36
+ Adds the tag link to the node. A node that already carries the tag is left as
37
+ is. Refuses unless --force, listing the file's existing tags, when another node
38
+ carries the tag, when the node carries another tag, or when a Fundus asset
39
+ with that id exists from another source. An asset imported from this very
40
+ node by node id is not a conflict: afterwards
41
+ \`fundus asset replace <tag> --from figma --figma-tag <tag>\` moves it to the
42
+ tag and keeps its id, parameters, and manifests.
43
+
44
+ The new link is written first, then --force deletes the node's other tag
45
+ links and the tag's links on other nodes. A failed step stops the rest, so an
46
+ interruption at worst leaves the tag on two nodes (\`ambiguous-tag\` on
47
+ import), never loses it. Links on deleted nodes and non-Fundus links are
48
+ never touched.
43
49
 
44
50
  Flags:
45
51
  --node-id <id> the node to tag: 12:34 or 12-34
46
- --tag <tag> the Fundus asset id to store on the node (camelCase)
52
+ --tag <tag> the Fundus asset id to link to the node (camelCase)
47
53
  --file <key|link> Figma file key or link; default:
48
54
  import.figma.defaultFileKey from fundus.config.ts
49
- --force always set the tag: replace the node's current tag,
50
- rewrite the same tag, move it from other nodes, or tag
51
- despite a Fundus asset from another source
55
+ --force replace the node's other tags, move the tag away from
56
+ other nodes (this also resolves \`ambiguous-tag\`), or
57
+ tag despite a Fundus asset from another source
52
58
  --json print {"ok": true, "fileKey", "nodeId", "tag",
53
- "previousTag", "warnings", "next", "code"}; a refusal
54
- prints {"ok": false, "error": {"message"}, "existingTags"}
55
- when the file's tags help choose another
59
+ "previousTag", "movedFrom": [{"nodeId", "nodeName"}],
60
+ "warnings", "next"}; a refusal prints {"ok": false,
61
+ "error": {"message"}, "existingTags": [{"tag", "nodeId",
62
+ "nodeName"}]}; a failed write prints {"ok": false,
63
+ "error": {"message"}, "written": [{"action":
64
+ "create"|"delete", "nodeId", "nodeName", "tag"}],
65
+ "notWritten": [same]} and exits 1
56
66
  --help this help
57
67
 
58
- Without --json the script goes to stdout, everything else to stderr.
59
- The script returns {"ok": true, "nodeId", "nodeName", "tag", "previousTag",
60
- "annotated", "movedFrom": [{"nodeId", "nodeName", "page"}]}. Afterwards: \`fundus asset ingest --from figma --figma-tag <tag>\`.
68
+ Needs a token with file_content:read, file_dev_resources:read, and
69
+ file_dev_resources:write. The Dev Mode annotation appears once the Fundus
70
+ Figma plugin opens the node's page. Afterwards:
71
+ \`fundus asset ingest --from figma --figma-tag <tag>\`.
61
72
 
62
73
  Example:
63
- fundus figma prepare-set-tag --file 'https://www.figma.com/design/abc/UI' \\
74
+ fundus figma set-tag --file 'https://www.figma.com/design/abc/UI' \\
64
75
  --node-id 12-34 --tag navigationPanel --json
65
76
  `;
66
- const PREPARE_DELETE_TAG_HELP = `fundus figma prepare-delete-tag — prepare a use_figma script that removes a tag
77
+ const DELETE_TAG_HELP = `fundus figma delete-tag — remove a Fundus tag from a Figma node
67
78
 
68
- Usage: fundus figma prepare-delete-tag (--tag <tag> | --node-id <id>)
69
- [--file <key|link>] [--force] [--json]
79
+ Usage: fundus figma delete-tag (--tag <tag> | --node-id <id>)
80
+ [--file <key|link>] [--force] [--json]
70
81
 
71
- Removes the tag and its Dev Mode annotation; other annotations stay. Refuses
72
- unless --force when a Fundus asset is imported from the tag (its reimport
73
- would fail afterwards).
82
+ Deletes the node's tag link. The node must exist: a link left on a deleted
83
+ node is ignored, not cleaned up. Refuses unless --force when a Fundus asset is
84
+ imported from the tag (its reimport would fail afterwards).
74
85
 
75
86
  Flags:
76
87
  --tag <tag> the tag to remove (must be on exactly one node)
77
88
  --node-id <id> the node whose tag to remove: 12:34 or 12-34; with --tag,
78
- both must match
89
+ both must match; a node with several tags needs --tag
79
90
  --file <key|link> Figma file key or link; default:
80
91
  import.figma.defaultFileKey from fundus.config.ts
81
92
  --force delete even when a Fundus asset is imported from the tag
82
93
  --json print {"ok": true, "fileKey", "nodeId", "tag",
83
- "warnings", "next", "code"}; refusals as for
84
- prepare-set-tag
94
+ "warnings", "next"}; refusals and failed writes as for
95
+ set-tag
85
96
  --help this help
86
97
 
87
- The script returns {"ok": true, "nodeId", "nodeName", "removedTag",
88
- "annotationRemoved"}.
98
+ Needs a token with file_content:read, file_dev_resources:read, and
99
+ file_dev_resources:write. The Fundus Figma plugin removes the node's
100
+ annotation once it opens the node's page.
89
101
 
90
102
  Example:
91
- fundus figma prepare-delete-tag --tag navigationPanel --json
103
+ fundus figma delete-tag --tag navigationPanel --json
92
104
  `;
93
105
  const FIGMA_SUBCOMMAND_HELP = {
94
- 'prepare-set-tag': PREPARE_SET_TAG_HELP,
95
- 'prepare-delete-tag': PREPARE_DELETE_TAG_HELP
106
+ 'set-tag': SET_TAG_HELP,
107
+ 'delete-tag': DELETE_TAG_HELP
96
108
  };
97
109
  const TAG_FLAGS = {
98
110
  json: { takesValue: false },
@@ -112,17 +124,17 @@ function stringFlag(parsed, name) {
112
124
  const value = parsed.flags[name];
113
125
  return typeof value === 'string' ? value : undefined;
114
126
  }
115
- async function resolvePlanInputs(parsed, commandPath) {
127
+ async function resolvePlanInputs(parsed, commandPath, targetNodeId) {
116
128
  const config = await loadConfig(process.cwd());
117
129
  const fileFlag = stringFlag(parsed, 'file');
118
130
  const fileKey = fileFlag !== undefined ? resolveFigmaFileKeyFlag(fileFlag) : config.import.figma.defaultFileKey;
119
131
  if (!fileKey) {
120
132
  throw new CliUsageError(`\`${commandPath}\` needs --file <key|link> or import.figma.defaultFileKey in fundus.config.ts.`);
121
133
  }
122
- return { fileKey, context: await loadFigmaTagPlanContext(config, fileKey) };
123
- }
124
- function nextStep(fileKey, subject = '"code"') {
125
- return `Run ${subject} unchanged with the Figma MCP use_figma tool on fileKey "${fileKey}". It returns the result as JSON, or throws without writing when the live document disagrees.`;
134
+ const context = { config, fetch: globalThis.fetch };
135
+ const library = await new LibraryStore(config).read();
136
+ const snapshot = await loadFigmaTagSnapshot(context, fileKey, targetNodeId);
137
+ return { fileKey, context, plan: { library, snapshot } };
126
138
  }
127
139
  /** Refusals that list the file's tags get their own document; others take the standard error path. */
128
140
  function reportConflict(cause, json) {
@@ -139,24 +151,63 @@ function reportConflict(cause, json) {
139
151
  console.error(`fundus: ${cause.message}`);
140
152
  console.error(cause.existingTags.length === 0 ? 'No tags in this file yet.' : 'Existing tags in this file:');
141
153
  for (const entry of cause.existingTags) {
142
- console.error(` ${entry.tag} ${entry.nodeId} "${entry.nodeName}" (page "${entry.page}")`);
154
+ console.error(` ${entry.tag} ${entry.nodeId} "${entry.nodeName}"`);
143
155
  }
144
156
  }
145
157
  process.exitCode = 1;
146
158
  return true;
147
159
  }
148
- function printPlan(plan, document, json) {
160
+ /** The printed form of a write: the link id is internal. */
161
+ function publicWrite({ action, nodeId, nodeName, tag }) {
162
+ return { action, nodeId, nodeName, tag };
163
+ }
164
+ function describeWrite(write) {
165
+ return `${write.action === 'create' ? 'add' : 'remove'} tag "${write.tag}" on "${write.nodeName}" (${write.nodeId})`;
166
+ }
167
+ /**
168
+ * Applies the plan's writes and prints the outcome. Returns false after
169
+ * reporting a failed write, which leaves exit code 1.
170
+ */
171
+ async function writeAndReport(fileKey, writes, warnings, context, json) {
172
+ const { written, failure, notWritten } = await applyFigmaTagWrites(fileKey, writes, context);
173
+ if (!failure)
174
+ return true;
175
+ // Rerunning plans only the steps still missing, and prints the plan's hints again.
176
+ const message = `Could not ${describeWrite(failure.write)}: ${failure.message}${written.length > 0 ? ' Rerun the same command to finish the remaining steps.' : ''}`;
149
177
  if (json) {
150
- printJsonDocument({ ok: true, ...document, next: nextStep(plan.fileKey), code: plan.code });
178
+ printJsonDocument({
179
+ ok: false,
180
+ error: { message },
181
+ written: written.map(publicWrite),
182
+ notWritten: notWritten.map(publicWrite)
183
+ });
184
+ }
185
+ else {
186
+ for (const warning of warnings)
187
+ console.error(`⚠ ${warning}`);
188
+ console.error(`fundus: ${message}`);
189
+ console.error(written.length === 0 ? 'Nothing was written.' : 'Written:');
190
+ for (const write of written)
191
+ console.error(` ${describeWrite(write)}`);
192
+ console.error('Not written:');
193
+ for (const write of notWritten)
194
+ console.error(` ${describeWrite(write)}`);
195
+ }
196
+ process.exitCode = 1;
197
+ return false;
198
+ }
199
+ function printSuccess(summary, warnings, document, json) {
200
+ if (json) {
201
+ printJsonDocument({ ok: true, ...document, warnings, next: NEXT_STEP });
151
202
  return;
152
203
  }
153
- for (const warning of plan.warnings)
204
+ for (const warning of warnings)
154
205
  console.error(`⚠ ${warning}`);
155
- console.error(`${nextStep(plan.fileKey, 'the script below')}\n`);
156
- process.stdout.write(plan.code);
206
+ console.log(summary);
207
+ console.log(NEXT_STEP);
157
208
  }
158
- async function runPrepareSetTag(argumentList) {
159
- const commandPath = 'fundus figma prepare-set-tag';
209
+ async function runSetTag(argumentList) {
210
+ const commandPath = 'fundus figma set-tag';
160
211
  const parsed = parseArguments(argumentList, TAG_FLAGS, commandPath);
161
212
  requirePositionals(parsed, [], commandPath);
162
213
  const nodeIdFlag = stringFlag(parsed, 'node-id');
@@ -167,24 +218,30 @@ async function runPrepareSetTag(argumentList) {
167
218
  const nodeId = parseFigmaNodeId(nodeIdFlag);
168
219
  assertTag(tag);
169
220
  const json = parsed.flags.json === true;
170
- const { fileKey, context } = await resolvePlanInputs(parsed, commandPath);
221
+ const { fileKey, context, plan: planContext } = await resolvePlanInputs(parsed, commandPath, nodeId);
222
+ let plan;
171
223
  try {
172
- const plan = planSetFigmaTag({ fileKey, nodeId, tag, force: parsed.flags.force === true }, context);
173
- printPlan(plan, {
174
- fileKey,
175
- nodeId: plan.nodeId,
176
- tag: plan.tag,
177
- previousTag: plan.previousTag,
178
- warnings: plan.warnings
179
- }, json);
224
+ plan = planSetFigmaTag({ fileKey, nodeId, tag, force: parsed.flags.force === true }, planContext);
180
225
  }
181
226
  catch (cause) {
182
- if (!reportConflict(cause, json))
183
- throw cause;
227
+ if (reportConflict(cause, json))
228
+ return;
229
+ throw cause;
184
230
  }
231
+ if (!(await writeAndReport(fileKey, plan.writes, plan.warnings, context, json)))
232
+ return;
233
+ printSuccess(plan.writes.length === 0
234
+ ? `Node "${plan.nodeName}" (${nodeId}) already carries tag "${tag}"; nothing to write.`
235
+ : `Tagged "${plan.nodeName}" (${nodeId}) with "${tag}".`, plan.warnings, {
236
+ fileKey,
237
+ nodeId,
238
+ tag,
239
+ previousTag: plan.previousTag,
240
+ movedFrom: plan.movedFrom
241
+ }, json);
185
242
  }
186
- async function runPrepareDeleteTag(argumentList) {
187
- const commandPath = 'fundus figma prepare-delete-tag';
243
+ async function runDeleteTag(argumentList) {
244
+ const commandPath = 'fundus figma delete-tag';
188
245
  const parsed = parseArguments(argumentList, TAG_FLAGS, commandPath);
189
246
  requirePositionals(parsed, [], commandPath);
190
247
  const nodeIdFlag = stringFlag(parsed, 'node-id');
@@ -195,22 +252,26 @@ async function runPrepareDeleteTag(argumentList) {
195
252
  const nodeId = nodeIdFlag === undefined ? undefined : parseFigmaNodeId(nodeIdFlag);
196
253
  assertTag(tag);
197
254
  const json = parsed.flags.json === true;
198
- const { fileKey, context } = await resolvePlanInputs(parsed, commandPath);
255
+ const { fileKey, context, plan: planContext } = await resolvePlanInputs(parsed, commandPath, nodeId);
256
+ let plan;
199
257
  try {
200
- const plan = planDeleteFigmaTag({ fileKey, nodeId, tag, force: parsed.flags.force === true }, context);
201
- printPlan(plan, { fileKey, nodeId: plan.nodeId, tag: plan.tag, warnings: plan.warnings }, json);
258
+ plan = planDeleteFigmaTag({ fileKey, nodeId, tag, force: parsed.flags.force === true }, planContext);
202
259
  }
203
260
  catch (cause) {
204
- if (!reportConflict(cause, json))
205
- throw cause;
261
+ if (reportConflict(cause, json))
262
+ return;
263
+ throw cause;
206
264
  }
265
+ if (!(await writeAndReport(fileKey, plan.writes, plan.warnings, context, json)))
266
+ return;
267
+ printSuccess(`Removed tag "${plan.tag}" from "${plan.nodeName}" (${plan.nodeId}).`, plan.warnings, { fileKey, nodeId: plan.nodeId, tag: plan.tag }, json);
207
268
  }
208
269
  export async function runFigmaCommand(argumentList) {
209
270
  const [subcommand, ...rest] = argumentList;
210
271
  if (subcommand === undefined || subcommand === '--help') {
211
272
  console.log(FIGMA_HELP);
212
273
  if (subcommand === undefined) {
213
- throw new CliUsageError('`fundus figma` needs a subcommand: prepare-set-tag, prepare-delete-tag.');
274
+ throw new CliUsageError('`fundus figma` needs a subcommand: set-tag, delete-tag.');
214
275
  }
215
276
  return;
216
277
  }
@@ -219,10 +280,10 @@ export async function runFigmaCommand(argumentList) {
219
280
  return;
220
281
  }
221
282
  switch (subcommand) {
222
- case 'prepare-set-tag':
223
- return runPrepareSetTag(rest);
224
- case 'prepare-delete-tag':
225
- return runPrepareDeleteTag(rest);
283
+ case 'set-tag':
284
+ return runSetTag(rest);
285
+ case 'delete-tag':
286
+ return runDeleteTag(rest);
226
287
  default:
227
288
  throw new CliUsageError(`Unknown subcommand "figma ${subcommand}" — run \`fundus figma --help\`.`);
228
289
  }
@@ -1,46 +1,80 @@
1
1
  /**
2
- * Planning for the `fundus figma prepare-*-tag` commands. The Figma REST API
3
- * can read shared plugin data but not write it, so Fundus validates against a
4
- * REST snapshot of the file plus the local library, then hands back a
5
- * `use_figma` script (figma-tag-script.ts) that performs the write inside
6
- * Figma and re-checks the live document first.
2
+ * Planning and REST writes for `fundus figma set-tag` / `delete-tag`. Fundus
3
+ * confirms the file's tag links against the nodes that still exist (one link
4
+ * list plus one `/nodes` call), plans purely on that snapshot and the library,
5
+ * then writes the links in an order where an interruption can at worst leave
6
+ * a tag on two nodes (`ambiguous-tag`), never lose it.
7
7
  */
8
8
  import { type Library } from '../core/index.js';
9
- import type { ResolvedFundusConfig } from './config-loader.js';
9
+ import type { ImporterContext } from './importers/contract.js';
10
+ import { type FigmaNodeSummary } from './importers/figma.js';
11
+ /** A tag link on a node that exists. */
12
+ export interface ConfirmedFigmaTag {
13
+ tag: string;
14
+ nodeId: string;
15
+ nodeName: string;
16
+ nodeType: string;
17
+ linkId: string;
18
+ }
19
+ export interface FigmaTagSnapshot {
20
+ /** Tag links on nodes that exist (orphaned links dropped). */
21
+ tags: ConfirmedFigmaTag[];
22
+ /** The requested node when it exists, tagged or not. */
23
+ target: FigmaNodeSummary | null;
24
+ }
10
25
  /** A tagged node in the file, as reported alongside conflicts. */
11
26
  export interface FigmaTagListing {
12
27
  tag: string;
13
28
  nodeId: string;
14
29
  nodeName: string;
15
- page: string;
16
30
  }
17
31
  /** An expected refusal that carries the file's current tags so the caller can pick another. */
18
32
  export declare class FigmaTagConflictError extends Error {
19
33
  readonly existingTags: FigmaTagListing[];
20
34
  constructor(message: string, existingTags: FigmaTagListing[]);
21
35
  }
22
- export interface FigmaTagPlan {
23
- fileKey: string;
36
+ interface FigmaTagWriteBase {
24
37
  nodeId: string;
25
- /** Non-fatal findings the caller should relay (e.g. a Fundus asset to reimport). */
26
- warnings: string[];
27
- /** The `use_figma` script body. */
28
- code: string;
38
+ nodeName: string;
39
+ tag: string;
29
40
  }
30
- export interface SetFigmaTagPlan extends FigmaTagPlan {
41
+ /** One REST write; a delete names the link it removes. */
42
+ export type FigmaTagWrite = (FigmaTagWriteBase & {
43
+ action: 'create';
44
+ }) | (FigmaTagWriteBase & {
45
+ action: 'delete';
46
+ linkId: string;
47
+ });
48
+ export interface SetFigmaTagPlan {
49
+ fileKey: string;
50
+ nodeId: string;
51
+ nodeName: string;
31
52
  tag: string;
32
- /** The node's tag per the REST snapshot; null when untagged or not yet visible. */
53
+ /** Another tag the node carried, else `tag` when it already carried it, else null. */
33
54
  previousTag: string | null;
55
+ /** Other nodes the tag is removed from. */
56
+ movedFrom: Array<{
57
+ nodeId: string;
58
+ nodeName: string;
59
+ }>;
60
+ /** Ordered: the create (if any) first, then deletes. */
61
+ writes: FigmaTagWrite[];
62
+ /** Non-fatal findings the caller should relay (e.g. a Fundus asset to reimport). */
63
+ warnings: string[];
34
64
  }
35
- export interface DeleteFigmaTagPlan extends FigmaTagPlan {
65
+ export interface DeleteFigmaTagPlan {
66
+ fileKey: string;
67
+ nodeId: string;
68
+ nodeName: string;
36
69
  tag: string;
70
+ writes: FigmaTagWrite[];
71
+ warnings: string[];
37
72
  }
38
- /** Normalize a `12:34` / `12-34` node id; Fundus imports only this id form. */
73
+ /** Normalize a `12:34` / `12-34` node id; Fundus tags only this id form. */
39
74
  export declare function parseFigmaNodeId(value: string): string;
40
75
  export interface FigmaTagPlanContext {
41
76
  library: Library;
42
- /** REST document tree of the file, with shared plugin data. */
43
- document: unknown;
77
+ snapshot: FigmaTagSnapshot;
44
78
  }
45
79
  export interface SetFigmaTagRequest {
46
80
  fileKey: string;
@@ -48,14 +82,28 @@ export interface SetFigmaTagRequest {
48
82
  tag: string;
49
83
  force: boolean;
50
84
  }
51
- export declare function planSetFigmaTag(request: SetFigmaTagRequest, { library, document }: FigmaTagPlanContext): SetFigmaTagPlan;
85
+ export declare function planSetFigmaTag(request: SetFigmaTagRequest, { library, snapshot }: FigmaTagPlanContext): SetFigmaTagPlan;
52
86
  export interface DeleteFigmaTagRequest {
53
87
  fileKey: string;
54
- /** Exactly one of nodeId / tag identifies the node; both must agree when given. */
88
+ /** At least one of nodeId / tag identifies the node; both must agree when given. */
55
89
  nodeId?: string;
56
90
  tag?: string;
57
91
  force: boolean;
58
92
  }
59
- export declare function planDeleteFigmaTag(request: DeleteFigmaTagRequest, { library, document }: FigmaTagPlanContext): DeleteFigmaTagPlan;
60
- /** Load the library and the file's REST document for planning. */
61
- export declare function loadFigmaTagPlanContext(config: ResolvedFundusConfig, fileKey: string): Promise<FigmaTagPlanContext>;
93
+ export declare function planDeleteFigmaTag(request: DeleteFigmaTagRequest, { library, snapshot }: FigmaTagPlanContext): DeleteFigmaTagPlan;
94
+ /**
95
+ * The file's tag links confirmed against the nodes that exist: one link list,
96
+ * then one `/nodes` call over every tagged node plus the target. Links on
97
+ * missing nodes are orphaned and dropped, so nothing ever touches them.
98
+ */
99
+ export declare function loadFigmaTagSnapshot(context: ImporterContext, fileKey: string, targetNodeId?: string): Promise<FigmaTagSnapshot>;
100
+ /** Runs the writes in order and stops at the first failure; retries live in the REST client. */
101
+ export declare function applyFigmaTagWrites(fileKey: string, writes: readonly FigmaTagWrite[], context: ImporterContext): Promise<{
102
+ written: FigmaTagWrite[];
103
+ failure: {
104
+ write: FigmaTagWrite;
105
+ message: string;
106
+ } | null;
107
+ notWritten: FigmaTagWrite[];
108
+ }>;
109
+ export {};