@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.43 → 0.1.2-dev.45

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 (238) hide show
  1. package/README.md +1 -1
  2. package/cli/README.md +184 -0
  3. package/cli/bin/muscat-pk.mjs +13 -0
  4. package/cli/prepack.mjs +10 -0
  5. package/cli/src/build.mjs +2 -29
  6. package/cli/src/dev.mjs +10 -2
  7. package/cli/src/devBack.mjs +72 -9
  8. package/cli/src/devUi.mjs +100 -34
  9. package/cli/src/manifest.mjs +15 -0
  10. package/cli/src/mcp.mjs +523 -0
  11. package/cli/src/pluginDevServer.mjs +113 -0
  12. package/cli/src/ports.mjs +160 -0
  13. package/cli/src/sdk.mjs +11 -9
  14. package/cli/src/storybook.mjs +168 -0
  15. package/cli/src/templates-embedded.mjs +1 -1
  16. package/cli/storybook-dist/_design-tokens.css +4957 -0
  17. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  18. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  19. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  20. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  21. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  22. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  23. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  24. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  25. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  26. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  27. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  28. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  29. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +44 -0
  30. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  31. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  32. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  33. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  34. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  35. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  36. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  37. package/cli/storybook-dist/_stories-src/RBIcons/RBMenuTreeIcons.stories.tsx +108 -0
  38. package/cli/storybook-dist/_stories-src/RBIcons/RBProgramIcons.stories.tsx +116 -0
  39. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  40. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  41. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  42. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  43. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +119 -0
  44. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  45. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  46. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  47. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  48. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  49. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  50. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  51. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  52. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  53. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  54. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  55. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  56. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  57. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  58. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  59. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  60. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  61. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  62. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  63. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  64. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  65. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  66. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  67. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  68. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  69. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  70. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  71. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  72. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  73. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  74. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  75. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  76. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  77. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  78. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  79. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  80. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  81. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  82. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  83. package/cli/storybook-dist/assets/Color-YHDXOIA2-BjuzF3Je.js +1 -0
  84. package/cli/storybook-dist/assets/Docs-BAFZYTMR.js +12 -0
  85. package/cli/storybook-dist/assets/Docs-BQesYW-Q.js +8 -0
  86. package/cli/storybook-dist/assets/Docs-BsMEe093.js +7 -0
  87. package/cli/storybook-dist/assets/Docs-By6k6vcM.js +20 -0
  88. package/cli/storybook-dist/assets/Docs-D1J63cJc.js +7 -0
  89. package/cli/storybook-dist/assets/Docs-GoNbntAU.js +8 -0
  90. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-BwY_WiM7.js +2 -0
  91. package/cli/storybook-dist/assets/NxAvatar.stories-CvzBHfYa.js +13 -0
  92. package/cli/storybook-dist/assets/NxButton.stories-B83PesgN.js +20 -0
  93. package/cli/storybook-dist/assets/NxIcons.stories-Ca7qpAaO.js +3 -0
  94. package/cli/storybook-dist/assets/NxNumberInput.stories-DpNcCG6B.js +26 -0
  95. package/cli/storybook-dist/assets/NxPagination.stories-Xnvd3xhH.js +11 -0
  96. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-C3YSIpM0.js +14 -0
  97. package/cli/storybook-dist/assets/NxRangeInput.stories-DIhaNZig.js +17 -0
  98. package/cli/storybook-dist/assets/NxSelectBox.stories-BGwO2mYJ.js +17 -0
  99. package/cli/storybook-dist/assets/NxSlideRange.stories-BXlt5Bgt.js +14 -0
  100. package/cli/storybook-dist/assets/NxSwitch.stories-C5ZqIRtw.js +11 -0
  101. package/cli/storybook-dist/assets/NxTextArea.stories-hm_ycALo.js +11 -0
  102. package/cli/storybook-dist/assets/NxTextInput.stories-DkZAySr_.js +11 -0
  103. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  104. package/cli/storybook-dist/assets/RBAccordion.stories--bY36GZY.js +17 -0
  105. package/cli/storybook-dist/assets/RBAccordionGroup.stories-BVr1ccI4.js +33 -0
  106. package/cli/storybook-dist/assets/RBAlert.stories-CkjFBEkv.js +17 -0
  107. package/cli/storybook-dist/assets/RBBadge.stories-CNXILDPh.js +1 -0
  108. package/cli/storybook-dist/assets/RBBattery.stories-AokMv8ZY.js +10 -0
  109. package/cli/storybook-dist/assets/RBCard.stories-CX1sLKB2.js +45 -0
  110. package/cli/storybook-dist/assets/RBCheckBox.stories-By3QHBFm.js +1 -0
  111. package/cli/storybook-dist/assets/RBCheckIconButton.stories-Dud1uK3X.js +1 -0
  112. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-C8iBkZxY.js +24 -0
  113. package/cli/storybook-dist/assets/RBChipTab.stories-DsgbiS2U.js +21 -0
  114. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-C3DZGmq0.js +1 -0
  115. package/cli/storybook-dist/assets/RBColorIcons.stories-CusR20qk.js +3 -0
  116. package/cli/storybook-dist/assets/RBCommand.stories-DhJNgaLZ.js +73 -0
  117. package/cli/storybook-dist/assets/RBConfirm.stories-B6LKpCDa.js +20 -0
  118. package/cli/storybook-dist/assets/RBDateInput.stories-PEEhQ1wJ.js +15 -0
  119. package/cli/storybook-dist/assets/RBDialog.stories-CAtOdRen.js +38 -0
  120. package/cli/storybook-dist/assets/RBDivider.stories-0zpIUlXT.js +1 -0
  121. package/cli/storybook-dist/assets/RBDrawer.stories-CWMG4glY.js +14 -0
  122. package/cli/storybook-dist/assets/RBDropdownMenu.stories-6fbEYZfk.js +86 -0
  123. package/cli/storybook-dist/assets/RBEmptyView.stories-BRDZHjn4.js +12 -0
  124. package/cli/storybook-dist/assets/RBIconButton.stories-sYrRLYob.js +1 -0
  125. package/cli/storybook-dist/assets/RBIcons.stories-DFe8O1_g.js +3 -0
  126. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-DX4VdUfk.js +1 -0
  127. package/cli/storybook-dist/assets/RBJoyStick.stories-SA2Foelf.js +11 -0
  128. package/cli/storybook-dist/assets/RBLabelButton.stories-CicBns_Y.js +1 -0
  129. package/cli/storybook-dist/assets/RBLight.stories-WlP3oEmY.js +1 -0
  130. package/cli/storybook-dist/assets/RBMenuTreeIcons.stories-CrWjPfxj.js +3 -0
  131. package/cli/storybook-dist/assets/RBMenubar.stories-Di4phWw7.js +3 -0
  132. package/cli/storybook-dist/assets/RBMultiRangeInput.stories-DS3wrR3l.js +14 -0
  133. package/cli/storybook-dist/assets/RBPagination.stories-BDVPAUVD.js +17 -0
  134. package/cli/storybook-dist/assets/RBPopover.stories-DL9b_iX9.js +11 -0
  135. package/cli/storybook-dist/assets/RBProgramIcons.stories-ByRVx-on.js +3 -0
  136. package/cli/storybook-dist/assets/RBPrompt.stories-vTmKWyA5.js +20 -0
  137. package/cli/storybook-dist/assets/RBRadioBox.stories-C9WHt4jb.js +1 -0
  138. package/cli/storybook-dist/assets/RBRadioButton.stories-D8KYq63j.js +11 -0
  139. package/cli/storybook-dist/assets/RBRangeDateInput.stories-Bvcf4Su7.js +15 -0
  140. package/cli/storybook-dist/assets/RBResizable.stories-3O2OMuH_.js +20 -0
  141. package/cli/storybook-dist/assets/RBSelectBox.stories-CKZLmPzX.js +6 -0
  142. package/cli/storybook-dist/assets/RBSheet.stories-Dko0It1W.js +39 -0
  143. package/cli/storybook-dist/assets/RBSideNumberInput.stories-CUvqVtId.js +5 -0
  144. package/cli/storybook-dist/assets/RBSlider.stories-CcbaKRYo.js +5 -0
  145. package/cli/storybook-dist/assets/RBSpinner.stories-D15e_JlE.js +14 -0
  146. package/cli/storybook-dist/assets/RBSwitch.stories-C4JRsYJ6.js +65 -0
  147. package/cli/storybook-dist/assets/RBTab.stories-BPvzQSM-.js +1 -0
  148. package/cli/storybook-dist/assets/RBTable.stories-nr8OS1mV.js +62 -0
  149. package/cli/storybook-dist/assets/RBTableRow.stories-rI0rK8Ci.js +11 -0
  150. package/cli/storybook-dist/assets/RBTabs.stories-CxMtHGWA.js +21 -0
  151. package/cli/storybook-dist/assets/RBTextArea.stories-Bru4AhJ3.js +5 -0
  152. package/cli/storybook-dist/assets/RBTextField.stories-Ah3D9lto.js +45 -0
  153. package/cli/storybook-dist/assets/RBTextInput.stories-C3qAVngJ.js +5 -0
  154. package/cli/storybook-dist/assets/RBToast.stories-C1occsvf.js +11 -0
  155. package/cli/storybook-dist/assets/RBToggle.stories-BNrNERc7.js +12 -0
  156. package/cli/storybook-dist/assets/RBToggleGroup.stories-D_RRBwxY.js +17 -0
  157. package/cli/storybook-dist/assets/RBTooltip.stories-DuyOQqrB.js +9 -0
  158. package/cli/storybook-dist/assets/RainbowUiProvider-BYlbsa69.css +1 -0
  159. package/cli/storybook-dist/assets/RainbowUiProvider-B_piZBLb.js +31 -0
  160. package/cli/storybook-dist/assets/browserUtils-DeopUpDy.js +1 -0
  161. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  162. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  163. package/cli/storybook-dist/assets/createConveyorBeltLayer-Cn41pFRH.js +6210 -0
  164. package/cli/storybook-dist/assets/createConveyorBeltLayer-Ds5AOper.css +21 -0
  165. package/cli/storybook-dist/assets/entry-preview-D-mLV0cS.js +2 -0
  166. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  167. package/cli/storybook-dist/assets/iframe-DNru2eN7.js +211 -0
  168. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  169. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  170. package/cli/storybook-dist/assets/index-C1OzuK6-.js +240 -0
  171. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  172. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  173. package/cli/storybook-dist/assets/index-Cwr2bQOB.js +575 -0
  174. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  175. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  176. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  177. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  178. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  179. package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +1 -0
  180. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  181. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  182. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  183. package/cli/storybook-dist/assets/preview-CILRoSnB.js +1 -0
  184. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  185. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  186. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  187. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  188. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  189. package/cli/storybook-dist/assets/preview-DUTcCPBe.css +1 -0
  190. package/cli/storybook-dist/assets/preview-QAsoD6Sa.js +2 -0
  191. package/cli/storybook-dist/assets/preview-rcRMbKPr.js +3 -0
  192. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  193. package/cli/storybook-dist/assets/react-resizable-panels.browser-CvAJUcrm.js +72 -0
  194. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  195. package/cli/storybook-dist/favicon.svg +1 -0
  196. package/cli/storybook-dist/iframe.html +666 -0
  197. package/cli/storybook-dist/index.html +190 -0
  198. package/cli/storybook-dist/index.json +1 -0
  199. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  200. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  201. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  202. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  203. package/cli/storybook-dist/project.json +1 -0
  204. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  205. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  206. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  207. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  208. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  209. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  210. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  211. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  212. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  213. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  214. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  215. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  216. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  217. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  218. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  219. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  220. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  221. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  222. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  223. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  224. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  225. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  226. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  227. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  228. package/cli/templates/common/.mcp.json +8 -0
  229. package/cli/templates/common/README.md +467 -53
  230. package/cli/templates/common/assets/icon.svg +44 -2
  231. package/cli/templates/common/package.json +2 -0
  232. package/cli/templates/common/ui/content.tsx +209 -26
  233. package/cli/templates/common/ui/style.css +6 -0
  234. package/cli/templates/common/vite.config.ts +6 -34
  235. package/cli/templates/cpp/cpp/main.cpp +1 -0
  236. package/cli/vite.d.mts +25 -0
  237. package/cli/vite.mjs +271 -0
  238. package/package.json +31 -4
@@ -0,0 +1,523 @@
1
+ // muscat-pk mcp — an MCP (Model Context Protocol) server that gives an AI
2
+ // coding agent (Claude Code, Codex, ...) grounded knowledge of the Muscat
3
+ // plugin UI surface, instead of it guessing prop names or grepping
4
+ // node_modules for @rainbow-robotics/plugin-ui and @repo/rb-components:
5
+ //
6
+ // Frontend UI (@rainbow-robotics/plugin-ui/react, via dist/react.d.ts):
7
+ // - list_components every exported component/hook/type.
8
+ // - get_component_props the real TypeScript type for one export.
9
+ // - get_component_example a real Storybook story (working code, not a guess).
10
+ //
11
+ // Design system (rb-styles rb-token.css):
12
+ // - list_design_tokens Tailwind classes to use (bg-bg-*, text-fg-*, typo-*, ...).
13
+ // - search_design_tokens find a token by name or hex value.
14
+ //
15
+ // Plugin SDK docs + Python backend:
16
+ // - search_rb_sdk search the plugin-author README (useRb(), rb.*, manifest, runtimes).
17
+ // - list_rb_sdk classes/functions of the installed rainbow.rb_sdk.
18
+ // - get_rb_sdk_api signature + docstring for an rb_sdk class/function.
19
+ //
20
+ // Add to an MCP client config, e.g. Claude Code's .mcp.json:
21
+ // { "mcpServers": { "muscat": { "command": "muscat-pk", "args": ["mcp"] } } }
22
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
24
+ import { z } from "zod";
25
+ import ts from "typescript";
26
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
27
+ import { spawnSync } from "node:child_process";
28
+ import { join, resolve } from "node:path";
29
+ import { fileURLToPath } from "node:url";
30
+ import { kitRoot, kitVersion } from "./util.mjs";
31
+ import { SHIPPED_STORIES_SRC, SHIPPED_DESIGN_TOKENS } from "./storybook.mjs";
32
+
33
+ const TEMPLATE_README = join(kitRoot, "cli/templates/common/README.md");
34
+ // Design tokens: prefer the shipped copy, fall back to the monorepo source.
35
+ const DESIGN_TOKENS_CSS = existsSync(SHIPPED_DESIGN_TOKENS)
36
+ ? SHIPPED_DESIGN_TOKENS
37
+ : resolve(kitRoot, "../..", "packages/rb-styles/css/output/rb-token.css");
38
+
39
+ /* -------------------------------------------------------------------------- */
40
+ /* react.d.ts introspection — component/hook list + prop types */
41
+ /* -------------------------------------------------------------------------- */
42
+
43
+ let typesCache = null;
44
+
45
+ // Resolves @rainbow-robotics/plugin-ui/react's bundled, self-contained
46
+ // dist/react.d.ts (see plugin-ui/build-dist.mjs — rb-components' own types are
47
+ // inlined into it, so this single file is enough; no need to resolve
48
+ // @repo/rb-components separately, which wouldn't exist in an external install
49
+ // anyway).
50
+ async function loadReactTypes() {
51
+ if (typesCache) return typesCache;
52
+
53
+ const entryUrl = await import.meta.resolve("@rainbow-robotics/plugin-ui/react");
54
+ const dtsPath = fileURLToPath(entryUrl).replace(/\.js$/, ".d.ts");
55
+ if (!existsSync(dtsPath)) {
56
+ throw new Error(
57
+ `plugin-ui type declarations not found at ${dtsPath} — reinstall or rebuild @rainbow-robotics/plugin-ui.`,
58
+ );
59
+ }
60
+
61
+ const program = ts.createProgram([dtsPath], {
62
+ target: ts.ScriptTarget.ES2022,
63
+ module: ts.ModuleKind.ESNext,
64
+ skipLibCheck: true,
65
+ });
66
+ const sourceFile = program.getSourceFile(dtsPath);
67
+ const checker = program.getTypeChecker();
68
+ const moduleSymbol = sourceFile && checker.getSymbolAtLocation(sourceFile);
69
+ const exports = moduleSymbol ? checker.getExportsOfModule(moduleSymbol) : [];
70
+
71
+ typesCache = { checker, sourceFile, exports };
72
+ return typesCache;
73
+ }
74
+
75
+ // The barrel's trailing `export { A, B, ... }` produces Alias symbols whose
76
+ // OWN flags don't reflect value-vs-type — resolve to the real declaration first.
77
+ function resolveExport(checker, symbol) {
78
+ return symbol.flags & ts.SymbolFlags.Alias
79
+ ? checker.getAliasedSymbol(symbol)
80
+ : symbol;
81
+ }
82
+
83
+ function exportKind(checker, symbol) {
84
+ const resolved = resolveExport(checker, symbol);
85
+ return resolved.flags & ts.SymbolFlags.Value ? "value" : "type";
86
+ }
87
+
88
+ /* -------------------------------------------------------------------------- */
89
+ /* Storybook story source lookup */
90
+ /* -------------------------------------------------------------------------- */
91
+
92
+ // Case-insensitive recursive search for `${name}.stories.tsx` under the
93
+ // staged story sources (see storybook.mjs's buildStorybook()).
94
+ function findStorySource(name) {
95
+ if (!existsSync(SHIPPED_STORIES_SRC)) return null;
96
+ const target = `${name.toLowerCase()}.stories.tsx`;
97
+ const stack = [SHIPPED_STORIES_SRC];
98
+ while (stack.length) {
99
+ const dir = stack.pop();
100
+ for (const entry of readdirSync(dir)) {
101
+ const full = join(dir, entry);
102
+ if (statSync(full).isDirectory()) {
103
+ stack.push(full);
104
+ continue;
105
+ }
106
+ if (entry.toLowerCase() === target) return readFileSync(full, "utf8");
107
+ }
108
+ }
109
+ return null;
110
+ }
111
+
112
+ /* -------------------------------------------------------------------------- */
113
+ /* README search */
114
+ /* -------------------------------------------------------------------------- */
115
+
116
+ // Splits the plugin-author README on headings and keyword-scores each
117
+ // section — good enough for short-query lookups ("save form data", "toast",
118
+ // "manifest ui") without pulling in a real search dependency.
119
+ function searchReadme(query) {
120
+ if (!existsSync(TEMPLATE_README)) return [];
121
+ const text = readFileSync(TEMPLATE_README, "utf8");
122
+ const sections = text.split(/\n(?=#{1,4} )/);
123
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
124
+
125
+ return sections
126
+ .map((section) => {
127
+ const lower = section.toLowerCase();
128
+ const score = terms.reduce((n, term) => n + (lower.includes(term) ? 1 : 0), 0);
129
+ return { section: section.trim(), score };
130
+ })
131
+ .filter((x) => x.score > 0)
132
+ .sort((a, b) => b.score - a.score)
133
+ .slice(0, 5)
134
+ .map((x) => x.section);
135
+ }
136
+
137
+ /* -------------------------------------------------------------------------- */
138
+ /* Design tokens — parsed from rb-styles' generated rb-token.css */
139
+ /* -------------------------------------------------------------------------- */
140
+
141
+ // A color var `--color-<name>` becomes Tailwind utilities `bg-<name>`,
142
+ // `text-<name>`, `border-<name>`, `fill-<name>`. The design system's naming
143
+ // already encodes intent (bg-/fg-/border-/fill-), so we surface the idiomatic
144
+ // class per group.
145
+ const COLOR_CLASS_HINT = {
146
+ bg: (n) => `bg-${n}`, // --color-bg-default → bg-bg-default
147
+ fg: (n) => `text-${n}`, // --color-fg-subtle → text-fg-subtle
148
+ border: (n) => `border-${n}`,
149
+ fill: (n) => `fill-${n}`,
150
+ };
151
+
152
+ let tokensCache = null;
153
+
154
+ function loadDesignTokens() {
155
+ if (tokensCache) return tokensCache;
156
+ if (!existsSync(DESIGN_TOKENS_CSS)) {
157
+ throw new Error(
158
+ `design token CSS not found at ${DESIGN_TOKENS_CSS} — rebuild via "muscat-pk storybook --rebuild" (monorepo) or reinstall.`,
159
+ );
160
+ }
161
+ const css = readFileSync(DESIGN_TOKENS_CSS, "utf8");
162
+
163
+ // @theme { --name: value; ... } → { group, name, value, class? }
164
+ const vars = [];
165
+ for (const [, rawName, value] of css.matchAll(
166
+ /^\s*(--[a-z0-9-]+)\s*:\s*([^;]+);/gim,
167
+ )) {
168
+ const name = rawName.replace(/^--/, "");
169
+ const m = name.match(/^color-(bg|fg|border|fill)-(.+)$/);
170
+ const token = { name, value: value.trim() };
171
+ if (m) {
172
+ token.group = `color-${m[1]}`;
173
+ token.class = COLOR_CLASS_HINT[m[1]](`${m[1]}-${m[2]}`);
174
+ } else {
175
+ token.group = name.split("-").slice(0, 2).join("-");
176
+ }
177
+ vars.push(token);
178
+ }
179
+
180
+ // @utility typo-… → composite typography classes used directly in className.
181
+ const typo = [
182
+ ...new Set(
183
+ [...css.matchAll(/@utility\s+(typo-[a-z0-9-]+)\s*\{/g)].map((m) => m[1]),
184
+ ),
185
+ ].sort();
186
+
187
+ tokensCache = { vars, typo };
188
+ return tokensCache;
189
+ }
190
+
191
+ /* -------------------------------------------------------------------------- */
192
+ /* rainbow-rb-sdk (Python) introspection */
193
+ /* -------------------------------------------------------------------------- */
194
+
195
+ function pythonCmd() {
196
+ for (const c of ["python3", "python"]) {
197
+ const r = spawnSync(c, ["--version"], { encoding: "utf8" });
198
+ if (r.status === 0) return c;
199
+ }
200
+ return null;
201
+ }
202
+
203
+ // Runs a short introspection script against the INSTALLED rainbow.rb_sdk. It's
204
+ // the plugin's own backend dependency (requirements.txt / `muscat-pk sdk`), so
205
+ // in a real plugin env it's importable; otherwise we return a clear hint.
206
+ function introspectRbSdk(script) {
207
+ const py = pythonCmd();
208
+ if (!py) {
209
+ return { error: "Python not found on PATH. Install Python 3 to use rb-sdk introspection." };
210
+ }
211
+ const probe = `
212
+ import json, importlib
213
+ try:
214
+ importlib.import_module("rainbow.rb_sdk")
215
+ except Exception as e:
216
+ print(json.dumps({"__notinstalled__": str(e)})); raise SystemExit(0)
217
+ ${script}
218
+ `;
219
+ const r = spawnSync(py, ["-c", probe], { encoding: "utf8", maxBuffer: 8 << 20 });
220
+ if (r.status !== 0) {
221
+ return { error: `python introspection failed: ${(r.stderr || "").trim().slice(0, 500)}` };
222
+ }
223
+ let data;
224
+ try {
225
+ data = JSON.parse(r.stdout.trim());
226
+ } catch {
227
+ return { error: `unexpected introspection output: ${r.stdout.slice(0, 300)}` };
228
+ }
229
+ if (data && data.__notinstalled__) {
230
+ return {
231
+ error:
232
+ "rainbow-rb-sdk is not installed in this environment. Install it with `pip install rainbow-rb-sdk` " +
233
+ "(it's already in a plugin's requirements.txt), then retry.",
234
+ };
235
+ }
236
+ return { data };
237
+ }
238
+
239
+ /* -------------------------------------------------------------------------- */
240
+ /* server */
241
+ /* -------------------------------------------------------------------------- */
242
+
243
+ export async function runMcp() {
244
+ const server = new McpServer({ name: "muscat-plugin-kit", version: kitVersion() });
245
+
246
+ server.registerTool(
247
+ "list_components",
248
+ {
249
+ title: "List Muscat plugin UI components",
250
+ description:
251
+ "List every component, hook, and type exported from @rainbow-robotics/plugin-ui/react — " +
252
+ "the full UI surface available to Muscat plugin authors. Call this first before guessing a " +
253
+ "component name.",
254
+ inputSchema: {},
255
+ },
256
+ async () => {
257
+ const { checker, exports } = await loadReactTypes();
258
+ const lines = exports
259
+ .map((sym) => `${sym.getName()} (${exportKind(checker, sym)})`)
260
+ .sort();
261
+ return { content: [{ type: "text", text: lines.join("\n") }] };
262
+ },
263
+ );
264
+
265
+ server.registerTool(
266
+ "get_component_props",
267
+ {
268
+ title: "Get a component or hook's real type signature",
269
+ description:
270
+ "Get the full TypeScript type for a named export of @rainbow-robotics/plugin-ui/react " +
271
+ "(e.g. RBSlider, RBLabelButton, useRb, useRbFormData). Use list_components to find valid names.",
272
+ inputSchema: { name: z.string().describe('Exported name, e.g. "RBSlider"') },
273
+ },
274
+ async ({ name }) => {
275
+ const { checker, sourceFile, exports } = await loadReactTypes();
276
+ const symbol = exports.find((sym) => sym.getName() === name);
277
+ if (!symbol) {
278
+ return {
279
+ isError: true,
280
+ content: [
281
+ {
282
+ type: "text",
283
+ text: `No export named "${name}" in @rainbow-robotics/plugin-ui/react. Call list_components for valid names.`,
284
+ },
285
+ ],
286
+ };
287
+ }
288
+ const resolved = resolveExport(checker, symbol);
289
+ const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0] ?? sourceFile;
290
+ const type = checker.getTypeOfSymbolAtLocation(resolved, declaration);
291
+ const text = checker.typeToString(
292
+ type,
293
+ undefined,
294
+ ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.MultilineObjectLiterals,
295
+ );
296
+ return { content: [{ type: "text", text }] };
297
+ },
298
+ );
299
+
300
+ server.registerTool(
301
+ "get_component_example",
302
+ {
303
+ title: "Get a real Storybook usage example",
304
+ description:
305
+ "Get the real Storybook story source for a component (e.g. RBSlider) — actual working " +
306
+ "props, imports, and layout, not a guessed shape. Prefer this over get_component_props when " +
307
+ "you just need to know how to use a component, not its full type.",
308
+ inputSchema: { name: z.string().describe('Component name, e.g. "RBSlider"') },
309
+ },
310
+ async ({ name }) => {
311
+ const source = findStorySource(name);
312
+ if (!source) {
313
+ return {
314
+ content: [
315
+ {
316
+ type: "text",
317
+ text: `No Storybook example found for "${name}". Run "muscat-pk storybook" to browse all components live, or use get_component_props for its type.`,
318
+ },
319
+ ],
320
+ };
321
+ }
322
+ return { content: [{ type: "text", text: source }] };
323
+ },
324
+ );
325
+
326
+ server.registerTool(
327
+ "search_rb_sdk",
328
+ {
329
+ title: "Search the Muscat plugin-author docs",
330
+ description:
331
+ "Search the Muscat plugin-author README for how to use useRb(), rb.pub/sub/action/serve, " +
332
+ "rb.toast/alert/confirm/prompt, useRbFormData/useRbSave (save/restore form data through the " +
333
+ "host), manifest.json, contribution points, and backend runtimes (Python/C++/ROS2). IMPORTANT: " +
334
+ "the backend pattern depends on the contribution point — search 'which backend pattern' before " +
335
+ "writing py/main.py. Only a program.panel command uses PluginProgramContext; a toolbar/move.panel " +
336
+ "plugin controls the robot with the SDK (RBManipulateSDK/RBAmrSDK) directly.",
337
+ inputSchema: {
338
+ query: z.string().describe('Keywords, e.g. "save form data" or "rb.action"'),
339
+ },
340
+ },
341
+ async ({ query }) => {
342
+ const sections = searchReadme(query);
343
+ if (sections.length === 0) {
344
+ return { content: [{ type: "text", text: `No matching section found for "${query}".` }] };
345
+ }
346
+ return { content: [{ type: "text", text: sections.join("\n\n---\n\n") }] };
347
+ },
348
+ );
349
+
350
+ /* --- design tokens --- */
351
+
352
+ server.registerTool(
353
+ "list_design_tokens",
354
+ {
355
+ title: "List Muscat design tokens (Tailwind classes)",
356
+ description:
357
+ "List the design system's tokens as the Tailwind classes to use in a plugin's className: " +
358
+ "semantic colors (bg-bg-*, text-fg-*, border-border-*, fill-fill-*), typography (typo-*), " +
359
+ "shadows, radius, spacing. Use these instead of raw hex/px so the plugin matches the host theme " +
360
+ "(and light/dark). Optionally filter by category.",
361
+ inputSchema: {
362
+ category: z
363
+ .enum(["bg", "fg", "border", "fill", "typography", "shadow", "radius", "spacing", "all"])
364
+ .optional()
365
+ .describe('Filter, e.g. "bg", "fg", "typography". Omit for a grouped overview.'),
366
+ },
367
+ },
368
+ async ({ category }) => {
369
+ const { vars, typo } = loadDesignTokens();
370
+ const out = [];
371
+
372
+ const colorGroups = { bg: [], fg: [], border: [], fill: [] };
373
+ const other = { shadow: [], radius: [], spacing: [] };
374
+ for (const t of vars) {
375
+ const m = t.group?.match(/^color-(bg|fg|border|fill)$/);
376
+ if (m && t.class) colorGroups[m[1]].push(`${t.class} (${t.value})`);
377
+ else if (t.name.startsWith("shadow-")) other.shadow.push(`shadow-${t.name.slice(7)}`);
378
+ else if (t.name.startsWith("radius-")) other.radius.push(`rounded-${t.name.slice(7)}`);
379
+ else if (t.name.startsWith("spacing-")) other.spacing.push(t.name);
380
+ }
381
+
382
+ const want = (c) => !category || category === "all" || category === c;
383
+ const push = (title, items) => {
384
+ if (items.length) out.push(`## ${title}\n${[...new Set(items)].sort().join("\n")}`);
385
+ };
386
+ if (want("bg")) push("Background — bg-bg-*", colorGroups.bg);
387
+ if (want("fg")) push("Text/foreground — text-fg-*", colorGroups.fg);
388
+ if (want("border")) push("Border — border-border-*", colorGroups.border);
389
+ if (want("fill")) push("SVG fill — fill-fill-*", colorGroups.fill);
390
+ if (want("typography")) push("Typography — typo-* (use directly)", typo);
391
+ if (want("shadow")) push("Shadow", other.shadow);
392
+ if (want("radius")) push("Radius", other.radius);
393
+ if (want("spacing")) push("Spacing scale (p-/m-/gap-/w-/h-)", other.spacing);
394
+
395
+ return {
396
+ content: [
397
+ {
398
+ type: "text",
399
+ text: out.join("\n\n") || `No tokens for category "${category}".`,
400
+ },
401
+ ],
402
+ };
403
+ },
404
+ );
405
+
406
+ server.registerTool(
407
+ "search_design_tokens",
408
+ {
409
+ title: "Search design tokens by name or value",
410
+ description:
411
+ "Find a design token by keyword — e.g. 'danger' (error colors), 'elevated', 'brand', or a hex " +
412
+ "like '#fb2c36'. Returns the matching Tailwind class + value.",
413
+ inputSchema: { query: z.string().describe('e.g. "danger", "elevated", "#fb2c36"') },
414
+ },
415
+ async ({ query }) => {
416
+ const { vars, typo } = loadDesignTokens();
417
+ const q = query.toLowerCase();
418
+ const hits = vars
419
+ .filter((t) => t.name.toLowerCase().includes(q) || t.value.toLowerCase().includes(q))
420
+ .map((t) => `${t.class ?? t.name} (${t.value})`);
421
+ const typoHits = typo.filter((c) => c.toLowerCase().includes(q));
422
+ const all = [...typoHits, ...hits];
423
+ return {
424
+ content: [
425
+ {
426
+ type: "text",
427
+ text: all.length ? [...new Set(all)].slice(0, 60).join("\n") : `No token matches "${query}".`,
428
+ },
429
+ ],
430
+ };
431
+ },
432
+ );
433
+
434
+ /* --- rainbow-rb-sdk (Python backend) --- */
435
+
436
+ server.registerTool(
437
+ "list_rb_sdk",
438
+ {
439
+ title: "List rainbow-rb-sdk (Python) classes and functions",
440
+ description:
441
+ "List the public classes and functions of the installed rainbow.rb_sdk Python package — the " +
442
+ "plugin BACKEND SDK (RBPluginCall, RBManipulateSDK, RBAmrSDK, PluginProgramContext, ...). Call " +
443
+ "before writing py/main.py. Requires rainbow-rb-sdk to be pip-installed.",
444
+ inputSchema: {},
445
+ },
446
+ async () => {
447
+ const { data, error } = introspectRbSdk(`
448
+ import inspect, json
449
+ mod = importlib.import_module("rainbow.rb_sdk")
450
+ out = []
451
+ for name in sorted(getattr(mod, "__all__", None) or dir(mod)):
452
+ if name.startswith("_"): continue
453
+ obj = getattr(mod, name)
454
+ if inspect.isclass(obj): out.append(name + " (class)")
455
+ elif inspect.isfunction(obj) or inspect.isbuiltin(obj): out.append(name + " (function)")
456
+ print(json.dumps(out))
457
+ `);
458
+ if (error) return { isError: true, content: [{ type: "text", text: error }] };
459
+ return { content: [{ type: "text", text: data.join("\n") }] };
460
+ },
461
+ );
462
+
463
+ server.registerTool(
464
+ "get_rb_sdk_api",
465
+ {
466
+ title: "Get a rainbow-rb-sdk class/function signature + docs",
467
+ description:
468
+ "Get the signature(s) and docstring for a rainbow.rb_sdk class or function (e.g. " +
469
+ "'PluginProgramContext', 'RBManipulateSDK'). For a class, lists its public methods with " +
470
+ "signatures. Requires rainbow-rb-sdk to be pip-installed.",
471
+ inputSchema: { name: z.string().describe('e.g. "PluginProgramContext"') },
472
+ },
473
+ async ({ name }) => {
474
+ const { data, error } = introspectRbSdk(`
475
+ import inspect, json
476
+ mod = importlib.import_module("rainbow.rb_sdk")
477
+ name = ${JSON.stringify(name)}
478
+ obj = getattr(mod, name, None)
479
+ if obj is None:
480
+ print(json.dumps({"__missing__": True})); raise SystemExit(0)
481
+ def sig(o):
482
+ try: return str(inspect.signature(o))
483
+ except Exception: return "(...)"
484
+ res = {"name": name, "doc": inspect.getdoc(obj) or ""}
485
+ if inspect.isclass(obj):
486
+ res["kind"] = "class"
487
+ res["init"] = name + sig(obj.__init__ if "__init__" in dir(obj) else obj)
488
+ methods = []
489
+ for m, fn in inspect.getmembers(obj, predicate=inspect.isfunction):
490
+ if m.startswith("_"): continue
491
+ methods.append({"name": m, "sig": m + sig(fn), "doc": (inspect.getdoc(fn) or "").split("\\n")[0]})
492
+ res["methods"] = methods
493
+ else:
494
+ res["kind"] = "function"
495
+ res["signature"] = name + sig(obj)
496
+ print(json.dumps(res))
497
+ `);
498
+ if (error) return { isError: true, content: [{ type: "text", text: error }] };
499
+ if (data.__missing__) {
500
+ return {
501
+ isError: true,
502
+ content: [
503
+ { type: "text", text: `No "${name}" in rainbow.rb_sdk. Call list_rb_sdk for valid names.` },
504
+ ],
505
+ };
506
+ }
507
+ const lines = [`# ${data.name} (${data.kind})`];
508
+ if (data.doc) lines.push(data.doc, "");
509
+ if (data.kind === "class") {
510
+ lines.push(`constructor: ${data.init}`, "", "methods:");
511
+ for (const m of data.methods) {
512
+ lines.push(` ${m.sig}${m.doc ? ` — ${m.doc}` : ""}`);
513
+ }
514
+ } else {
515
+ lines.push(data.signature);
516
+ }
517
+ return { content: [{ type: "text", text: lines.join("\n") }] };
518
+ },
519
+ );
520
+
521
+ const transport = new StdioServerTransport();
522
+ await server.connect(transport);
523
+ }
@@ -0,0 +1,113 @@
1
+
2
+ import { createRequire } from "node:module";
3
+ import { writeFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ const pluginRoot = resolve(process.argv[2]);
8
+ const port = Number(process.argv[3] || "4181");
9
+ const readyFile = process.argv[4] ? resolve(process.argv[4]) : "";
10
+
11
+
12
+ process.chdir(pluginRoot);
13
+
14
+ const require = createRequire(resolve(pluginRoot, "package.json"));
15
+
16
+ const viteMod = await import(pathToFileURL(require.resolve("vite")));
17
+ const createServer = viteMod.createServer ?? viteMod.default?.createServer;
18
+
19
+ const BOOTSTRAP_PREFIX = "\0rb-bootstrap:";
20
+ const PREAMBLE_ID = "\0rb-preamble";
21
+ const bootstrapPlugin = {
22
+ name: "rb-plugin-bootstrap",
23
+ resolveId(id) {
24
+ if (id.startsWith("rb-bootstrap:")) return "\0" + id;
25
+ if (id === "rb-preamble") return PREAMBLE_ID;
26
+ },
27
+ load(id) {
28
+ if (id === PREAMBLE_ID) {
29
+ return `
30
+ import RefreshRuntime from "/@react-refresh";
31
+ RefreshRuntime.injectIntoGlobalHook(window);
32
+ window.$RefreshReg$ = () => {};
33
+ window.$RefreshSig$ = () => (type) => type;
34
+ window.__vite_plugin_react_preamble_installed__ = true;
35
+ `;
36
+ }
37
+ if (!id.startsWith(BOOTSTRAP_PREFIX)) return;
38
+ const ui = id.slice(BOOTSTRAP_PREFIX.length) || "content";
39
+ const entry = JSON.stringify(`muscat-ui-entry:${ui}`);
40
+ return `
41
+ import "rb-preamble";
42
+ import { createElement } from "react";
43
+ import { createRoot } from "react-dom/client";
44
+ import { RbContext } from "@rainbow-robotics/plugin-ui/sdk";
45
+ import { setHostServices } from "@rainbow-robotics/plugin-ui/host-ui";
46
+ import { RbWs, createLazyRbSdk } from "@rainbow-robotics/plugin-ui/ws";
47
+ import App from ${entry};
48
+
49
+ // HMR only hands the new module to the accept callback — it does NOT rebind the
50
+ // static \`App\` import above. So we track the latest component at MODULE scope.
51
+ // Without this, navigating away and back (a fresh mount() call on this cached
52
+ // bootstrap module) would render the STALE component captured at first import;
53
+ // only a hard refresh fixed it. \`activeRender\` points at the currently-mounted
54
+ // root so live edits re-render it in place.
55
+ let latestApp = App;
56
+ let activeRender = null;
57
+ if (import.meta.hot) {
58
+ import.meta.hot.accept(${entry}, (m) => {
59
+ if (!m || !m.default) return;
60
+ latestApp = m.default;
61
+ activeRender?.(latestApp);
62
+ });
63
+ }
64
+
65
+ export function mount(container, { socketUrl, pluginId, hostServices, data, onSave }) {
66
+ container.setAttribute("data-muscat-plugin-root", "");
67
+ container.setAttribute("data-muscat-plugin-id", pluginId);
68
+ setHostServices(hostServices);
69
+ // Lazy connect: don't block the first render on the WebSocket handshake
70
+ // (there may be no backend at all in dev, e.g. no robot reachable) — each rb
71
+ // method awaits this promise internally instead.
72
+ const wsPromise = RbWs.connect(socketUrl, pluginId);
73
+ const rb = createLazyRbSdk(wsPromise, hostServices, { data, onSave });
74
+ const root = createRoot(container);
75
+ let mounted = true;
76
+ const render = (Component) => {
77
+ if (!mounted) return;
78
+ root.render(createElement(RbContext.Provider, { value: rb }, createElement(Component)));
79
+ };
80
+ const boundRender = (Component) => queueMicrotask(() => render(Component));
81
+ activeRender = boundRender;
82
+ render(latestApp);
83
+ return () => {
84
+ mounted = false;
85
+ // Don't clobber a newer mount's render (host defers unmount a microtask).
86
+ if (activeRender === boundRender) activeRender = null;
87
+ root.unmount();
88
+ };
89
+ }
90
+ `;
91
+ },
92
+ };
93
+
94
+ const server = await createServer({
95
+ root: pluginRoot,
96
+ configFile: resolve(pluginRoot, "vite.config.ts"),
97
+ mode: "development",
98
+ plugins: [bootstrapPlugin],
99
+ server: {
100
+ port,
101
+ strictPort: false,
102
+ cors: true,
103
+ hmr: { host: "localhost", protocol: "ws" },
104
+ },
105
+ });
106
+
107
+ await server.listen();
108
+ const address = server.httpServer?.address();
109
+ const actualPort = typeof address === "object" && address ? address.port : port;
110
+ if (readyFile) {
111
+ writeFileSync(readyFile, JSON.stringify({ port: actualPort }), "utf8");
112
+ }
113
+ console.log(`[plugin-dev] vite dev server → http://localhost:${actualPort}`);