@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.9 → 0.10.1-dev.1

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 (276) hide show
  1. package/README.ko.md +1188 -0
  2. package/README.md +1139 -57
  3. package/cli/README.md +239 -7
  4. package/cli/bin/create-muscat-plugin.mjs +1 -1
  5. package/cli/bin/muscat-pk.mjs +123 -27
  6. package/cli/bundle-templates.mjs +31 -0
  7. package/cli/docker/Dockerfile.cpp +28 -0
  8. package/cli/docker/Dockerfile.dev +17 -0
  9. package/cli/docker/Dockerfile.python +21 -0
  10. package/cli/prepack.mjs +12 -0
  11. package/cli/program-commands.json +2079 -0
  12. package/cli/src/__pycache__/host_agent.cpython-37.pyc +0 -0
  13. package/cli/src/ai-rules.mjs +108 -0
  14. package/cli/src/build.mjs +333 -93
  15. package/cli/src/check-package.mjs +8 -23
  16. package/cli/src/completion.mjs +168 -0
  17. package/cli/src/cpp.mjs +78 -0
  18. package/cli/src/cppIde.mjs +76 -0
  19. package/cli/src/dev.mjs +195 -6
  20. package/cli/src/devBack.mjs +671 -60
  21. package/cli/src/devId.mjs +54 -0
  22. package/cli/src/devSession.mjs +67 -0
  23. package/cli/src/devUi.mjs +218 -25
  24. package/cli/src/esbuild.mjs +27 -10
  25. package/cli/src/host_agent.py +223 -0
  26. package/cli/src/manifest.mjs +288 -0
  27. package/cli/src/mcp.mjs +1232 -0
  28. package/cli/src/mcpProxy.mjs +110 -0
  29. package/cli/src/muscatLocal.mjs +292 -0
  30. package/cli/src/new.mjs +205 -68
  31. package/cli/src/pluginDevServer.mjs +111 -0
  32. package/cli/src/ports.mjs +193 -0
  33. package/cli/src/programCommands.mjs +327 -0
  34. package/cli/src/projectCheck.mjs +102 -0
  35. package/cli/src/python.mjs +112 -0
  36. package/cli/src/ros2.mjs +62 -18
  37. package/cli/src/sdk.mjs +74 -24
  38. package/cli/src/sim.mjs +177 -0
  39. package/cli/src/storybook.mjs +150 -0
  40. package/cli/src/templates-embedded.mjs +4 -0
  41. package/cli/src/type.mjs +93 -34
  42. package/cli/src/uninstall.mjs +81 -0
  43. package/cli/src/update.mjs +364 -35
  44. package/cli/src/util.mjs +341 -11
  45. package/cli/src/zenohDev.mjs +83 -0
  46. package/cli/src/zip.mjs +121 -0
  47. package/cli/storybook-dist/_design-tokens.css +4957 -0
  48. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  49. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  50. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  51. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  52. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  53. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  54. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  55. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  56. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  57. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  58. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  59. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  60. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -0
  61. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  62. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  63. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  64. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  65. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  66. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  67. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  68. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  69. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  70. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  71. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  72. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -0
  73. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  74. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  75. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  76. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  77. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  78. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  79. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  84. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  85. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  86. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  87. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  88. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  89. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  90. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  91. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  92. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  93. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  94. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  95. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  96. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  97. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  98. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  99. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  100. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  101. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  102. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  103. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  104. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  105. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  106. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  107. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  108. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  109. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  110. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  111. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  112. package/cli/storybook-dist/assets/Color-YHDXOIA2-pCKrjUal.js +1 -0
  113. package/cli/storybook-dist/assets/Docs-B-Y7I1GA.js +7 -0
  114. package/cli/storybook-dist/assets/Docs-BFJcE_VS.js +8 -0
  115. package/cli/storybook-dist/assets/Docs-B_QaTtz8.js +8 -0
  116. package/cli/storybook-dist/assets/Docs-CryDGmfh.js +20 -0
  117. package/cli/storybook-dist/assets/Docs-Cw2AVNKx.js +7 -0
  118. package/cli/storybook-dist/assets/Docs-Q6r1fBeL.js +12 -0
  119. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-_OmZBCn8.js +2 -0
  120. package/cli/storybook-dist/assets/NxAvatar.stories-Ctf5G2FQ.js +13 -0
  121. package/cli/storybook-dist/assets/NxButton.stories-DeLObVSj.js +20 -0
  122. package/cli/storybook-dist/assets/NxIcons.stories-DeqrVzVs.js +3 -0
  123. package/cli/storybook-dist/assets/NxNumberInput.stories-LqoY0ed1.js +26 -0
  124. package/cli/storybook-dist/assets/NxPagination.stories-DI6bXuJM.js +11 -0
  125. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-3HSV1XmN.js +14 -0
  126. package/cli/storybook-dist/assets/NxRangeInput.stories-1pjgQVaG.js +17 -0
  127. package/cli/storybook-dist/assets/NxSelectBox.stories-bcQPZEuF.js +17 -0
  128. package/cli/storybook-dist/assets/NxSlideRange.stories-BosX4axk.js +14 -0
  129. package/cli/storybook-dist/assets/NxSwitch.stories-DxV6dS33.js +11 -0
  130. package/cli/storybook-dist/assets/NxTextArea.stories-CO545-LP.js +11 -0
  131. package/cli/storybook-dist/assets/NxTextInput.stories-BP0npai1.js +11 -0
  132. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  133. package/cli/storybook-dist/assets/RBAccordion.stories-fVR4Va2a.js +17 -0
  134. package/cli/storybook-dist/assets/RBAccordionGroup.stories-Du5bsckD.js +33 -0
  135. package/cli/storybook-dist/assets/RBAlert.stories-DNkMXUu6.js +17 -0
  136. package/cli/storybook-dist/assets/RBBadge.stories-C44pXt8D.js +1 -0
  137. package/cli/storybook-dist/assets/RBBattery.stories-DE2JHvfL.js +10 -0
  138. package/cli/storybook-dist/assets/RBCard.stories-CuIda7NI.js +45 -0
  139. package/cli/storybook-dist/assets/RBCheckBox.stories-D1qM1ayr.js +1 -0
  140. package/cli/storybook-dist/assets/RBCheckIconButton.stories-e4gkliBO.js +1 -0
  141. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-CDzCtsMI.js +24 -0
  142. package/cli/storybook-dist/assets/RBChipTab.stories-DBrc_3Mp.js +21 -0
  143. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-hlrsHjKY.js +1 -0
  144. package/cli/storybook-dist/assets/RBColorIcons.stories-BHUvpkdu.js +3 -0
  145. package/cli/storybook-dist/assets/RBCommand.stories-azAF41NP.js +73 -0
  146. package/cli/storybook-dist/assets/RBConfirm.stories-BMNjwbmT.js +20 -0
  147. package/cli/storybook-dist/assets/RBDateInput.stories-D0VezDzv.js +15 -0
  148. package/cli/storybook-dist/assets/RBDialog.stories-AOJ975Fy.js +38 -0
  149. package/cli/storybook-dist/assets/RBDivider.stories-NSvnj43Z.js +1 -0
  150. package/cli/storybook-dist/assets/RBDrawer.stories-Du0oTvD1.js +14 -0
  151. package/cli/storybook-dist/assets/RBDropdownMenu.stories-Hsxve4PW.js +86 -0
  152. package/cli/storybook-dist/assets/RBEmptyView.stories-BlwHx3zp.js +12 -0
  153. package/cli/storybook-dist/assets/RBIconButton.stories-DAPDY63x.js +1 -0
  154. package/cli/storybook-dist/assets/RBIcons.stories-W5ZUV2L1.js +3 -0
  155. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-C99j0Y3g.js +1 -0
  156. package/cli/storybook-dist/assets/RBJoyStick.stories-Du0H1iAS.js +11 -0
  157. package/cli/storybook-dist/assets/RBLabelButton.stories-BlvHaU_Z.js +1 -0
  158. package/cli/storybook-dist/assets/RBLight.stories-BY6DKrre.js +1 -0
  159. package/cli/storybook-dist/assets/RBMenubar.stories-D2PEGpM9.js +3 -0
  160. package/cli/storybook-dist/assets/RBMultiRangeInput.stories-Bm2qSmjq.js +14 -0
  161. package/cli/storybook-dist/assets/RBPagination.stories-DDleJa5N.js +17 -0
  162. package/cli/storybook-dist/assets/RBPopover.stories-27YxPSFG.js +11 -0
  163. package/cli/storybook-dist/assets/RBPrompt.stories-BFaIOctr.js +20 -0
  164. package/cli/storybook-dist/assets/RBRadioBox.stories-BroDar0t.js +1 -0
  165. package/cli/storybook-dist/assets/RBRadioButton.stories-M3O-wX7V.js +11 -0
  166. package/cli/storybook-dist/assets/RBRangeDateInput.stories-CWlM_D5X.js +15 -0
  167. package/cli/storybook-dist/assets/RBResizable.stories-ui8X1qnc.js +20 -0
  168. package/cli/storybook-dist/assets/RBSelectBox.stories-C8T6cfA0.js +74 -0
  169. package/cli/storybook-dist/assets/RBSheet.stories-BCOo_Zcc.js +39 -0
  170. package/cli/storybook-dist/assets/RBSideNumberInput.stories-CU2_MVlJ.js +5 -0
  171. package/cli/storybook-dist/assets/RBSlider.stories-DgOp1TFc.js +5 -0
  172. package/cli/storybook-dist/assets/RBSpinner.stories-qLScY3zZ.js +14 -0
  173. package/cli/storybook-dist/assets/RBSwitch.stories-SXSY1QMb.js +65 -0
  174. package/cli/storybook-dist/assets/RBTab.stories-BzXXTsXP.js +1 -0
  175. package/cli/storybook-dist/assets/RBTable.stories-DHTeRNVk.js +62 -0
  176. package/cli/storybook-dist/assets/RBTableRow.stories-DRNRsd15.js +11 -0
  177. package/cli/storybook-dist/assets/RBTabs.stories-DP0noBLp.js +21 -0
  178. package/cli/storybook-dist/assets/RBTextArea.stories-TkaejfxJ.js +5 -0
  179. package/cli/storybook-dist/assets/RBTextField.stories-B6VpCGyt.js +45 -0
  180. package/cli/storybook-dist/assets/RBTextInput.stories-D3hZ08e-.js +5 -0
  181. package/cli/storybook-dist/assets/RBToast.stories-BQ2wS2U0.js +11 -0
  182. package/cli/storybook-dist/assets/RBToggle.stories-GVOjGqo3.js +12 -0
  183. package/cli/storybook-dist/assets/RBToggleGroup.stories-pzx0btOD.js +17 -0
  184. package/cli/storybook-dist/assets/RBTooltip.stories-jufB6fa1.js +9 -0
  185. package/cli/storybook-dist/assets/SideNumberInput-BYlmNHEw.js +1109 -0
  186. package/cli/storybook-dist/assets/SideNumberInput-SAkhydsn.css +21 -0
  187. package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
  188. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  189. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  190. package/cli/storybook-dist/assets/entry-preview-CAkU-e0D.js +2 -0
  191. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  192. package/cli/storybook-dist/assets/iframe-B-BmTsxV.js +211 -0
  193. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  194. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  195. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  196. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  197. package/cli/storybook-dist/assets/index-CkB0GfaI.js +575 -0
  198. package/cli/storybook-dist/assets/index-ClAc6FBW.js +240 -0
  199. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  200. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  201. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  202. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  203. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  204. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  205. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  206. package/cli/storybook-dist/assets/preview-BVJwlGUF.js +1 -0
  207. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  208. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  209. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  210. package/cli/storybook-dist/assets/preview-CznQwwbo.js +3 -0
  211. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  212. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  213. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  214. package/cli/storybook-dist/assets/preview-M77JRg1A.js +2 -0
  215. package/cli/storybook-dist/assets/preview-kTZPgP6D.css +1 -0
  216. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  217. package/cli/storybook-dist/assets/react-resizable-panels-HsFbxPbn.css +1 -0
  218. package/cli/storybook-dist/assets/react-resizable-panels.browser-DuxpZLkF.js +120 -0
  219. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  220. package/cli/storybook-dist/favicon.svg +1 -0
  221. package/cli/storybook-dist/iframe.html +666 -0
  222. package/cli/storybook-dist/index.html +190 -0
  223. package/cli/storybook-dist/index.json +1 -0
  224. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  225. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  226. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  227. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  228. package/cli/storybook-dist/project.json +1 -0
  229. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  230. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  231. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  232. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  233. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  234. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  235. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  236. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  237. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  238. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  239. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  240. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  241. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  242. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  243. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  244. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  245. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  246. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  247. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  248. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  249. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  250. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  251. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  252. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  253. package/cli/templates/common/README.md +1070 -83
  254. package/cli/templates/common/assets/icon.svg +44 -2
  255. package/cli/templates/common/manifest.json +14 -13
  256. package/cli/templates/common/package.json +11 -4
  257. package/cli/templates/common/py/main.py +125 -0
  258. package/cli/templates/common/requirements.txt +4 -3
  259. package/cli/templates/common/tsconfig.json +1 -1
  260. package/cli/templates/common/ui/content.tsx +216 -0
  261. package/cli/templates/common/ui/style.css +6 -0
  262. package/cli/templates/common/vite.config.ts +6 -25
  263. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  264. package/cli/templates/cpp/cpp/main.cpp +73 -0
  265. package/cli/templates/readme-cpp/README.md +1012 -0
  266. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +12 -1
  267. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +22 -0
  268. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  269. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +41 -0
  270. package/cli/vite.d.mts +25 -0
  271. package/cli/vite.mjs +292 -0
  272. package/package.json +34 -7
  273. package/cli/src/pack.mjs +0 -61
  274. package/cli/src/preview.mjs +0 -91
  275. package/cli/templates/common/main.py +0 -114
  276. package/cli/templates/common/src/content.tsx +0 -33
@@ -0,0 +1,1012 @@
1
+ # __PLUGIN_NAME__
2
+
3
+ A Muscat plugin project. Build, develop, and ship with `muscat-pk` CLI.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ muscat-pk dev # UI preview + backend together
9
+ muscat-pk dev:ui # UI preview only
10
+ muscat-pk dev:back # backend only (C++ rebuild + restart in Docker)
11
+ muscat-pk build # build + distributable ZIP
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Project structure
17
+
18
+ ```
19
+ __PLUGIN_DIR__/
20
+ ├─ ui/
21
+ │ ├─ content.tsx # plugin UI entry point
22
+ │ └─ style.css # plugin styles auto-loaded by muscat()
23
+ ├─ cpp/
24
+ │ ├─ CMakeLists.txt # links rb_sdk::rb_sdk (C++ SDK)
25
+ │ └─ main.cpp # C++ backend entry point
26
+ ├─ assets/
27
+ │ └─ icon.svg # icon referenced from manifest
28
+ ├─ manifest.json # metadata, contribution points, runtime config
29
+ └─ README.md # this file (copied to dist/ on build)
30
+ ```
31
+
32
+ > ROS2 (rclcpp) plugins have `ros2_ws/` instead of `cpp/`.
33
+
34
+ ---
35
+
36
+ ## Frontend (`ui/content.tsx`)
37
+
38
+ ### JavaScript dependencies
39
+
40
+ `muscat-pk build` runs this plugin project's own Vite build. Vite resolves
41
+ imports from installed `node_modules`; it does not care whether a package is
42
+ listed under `dependencies` or `devDependencies`, but the package must be
43
+ installed.
44
+
45
+ Do not run a production-only install such as `npm install --omit=dev` before
46
+ building the plugin UI, because build tools like `vite`, `typescript`,
47
+ `tailwindcss`, `@tailwindcss/vite`, `sass`, or `less` are usually
48
+ `devDependencies`.
49
+
50
+ The scaffold keeps this split:
51
+
52
+ | package kind | examples | why |
53
+ |---|---|---|
54
+ | `dependencies` | `@rainbow-robotics/plugin-ui`, UI libraries imported by plugin source | Author-facing APIs used by `ui/*.tsx` |
55
+ | `devDependencies` | `vite`, `typescript`, `tailwindcss`, `@tailwindcss/vite`, `sass`, `less` | Build/dev tools needed before `dist/*.js` is produced |
56
+
57
+ The built plugin ZIP does not include `package.json` or `node_modules`. For
58
+ production, Muscat provides `@rainbow-robotics/plugin-ui`, React, and ReactDOM
59
+ through the host import map; plugin UI build output imports those as host
60
+ externals.
61
+
62
+ ### useRb() hook
63
+
64
+ Access the communication API between the plugin module and UI from any plugin component.
65
+
66
+ ```tsx
67
+ import { useRb } from "@rainbow-robotics/plugin-ui/sdk";
68
+
69
+ function App() {
70
+ const rb = useRb();
71
+ }
72
+ ```
73
+
74
+ ### rb API
75
+
76
+ | Method | Description |
77
+ | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
78
+ | `rb.pub(topic, payload)` | Publish a message to a topic |
79
+ | `rb.sub(topic, handler)` | Subscribe to a topic — returns `{ undeclare() }` |
80
+ | `rb.action(topic, payload?)` | Send a request and await a reply |
81
+ | `rb.serve(topic, handler)` | Handle incoming requests from the backend |
82
+ | `rb.toast({ title?, message, type?, actionLabel?, handleActionClick?, closeOnActionClick?, duration?, onClose? })` | Show a host toast notification. `message` accepts a string or `ReactElement`. `type` can be `"info"`, `"danger"`, `"warning"`, `"caution"`, or `"success"` |
83
+ | `rb.alert({ title?, message, buttonText? })` | Show a host alert dialog. Resolves when the dialog is closed |
84
+ | `rb.confirm({ title?, message, buttonText? })` | Show a host confirmation dialog. `buttonText` accepts `{ yes, no }`. Resolves to `true` or `false` |
85
+ | `rb.prompt({ title?, message, value?, placeholder?, buttonText? })` | Show a host prompt dialog. `buttonText` accepts `{ yes, no }`. Resolves to the entered string or `undefined` |
86
+
87
+ Topic names passed to `pub`, `sub`, `action`, and `serve` are isolated per plugin.
88
+
89
+
90
+ All methods support Generic Types:
91
+
92
+ ```tsx
93
+ const status = await rb.action<{ ok: boolean }, { id: number }>(
94
+ "get-status",
95
+ { id: 1 },
96
+ );
97
+
98
+ const sub = await rb.sub<{ value: number }>("sensor/data", (data) => {
99
+ console.log(data.value);
100
+ });
101
+ sub.undeclare(); // cleanup
102
+
103
+ const ok = await rb.confirm({
104
+ title: "Run",
105
+ message: "Start the backend action?",
106
+ });
107
+ if (ok) {
108
+ await rb.action("run");
109
+ }
110
+ ```
111
+
112
+ ### Saving and restoring your program command step (`useRbFormData` / `useRbSave`)
113
+
114
+ Use these only in your `program.panel` UI, where the user edits your program
115
+ command step: `useRbFormData` gives back the values saved in the step, and
116
+ `useRbSave` hands the current values to Muscat when the step is saved.
117
+
118
+ ```tsx
119
+ import { useState } from "react";
120
+ import { useRbFormData, useRbSave } from "@rainbow-robotics/plugin-ui/react";
121
+
122
+ type GripArgs = { targetName: string; width: number };
123
+ const DEFAULTS: GripArgs = { targetName: "", width: 20 };
124
+
125
+ export default function App() {
126
+ // Previously-saved values, or undefined for a brand-new instance.
127
+ const saved = useRbFormData<GripArgs>();
128
+ const [args, setArgs] = useState<GripArgs>({ ...DEFAULTS, ...saved });
129
+
130
+ // Registers a getter the host calls when its Save button is clicked.
131
+ // Always returns the CURRENT values — re-registering on every render keeps
132
+ // the closure fresh, which useRbSave already handles for you.
133
+ useRbSave(() => ({
134
+ args,
135
+ summary: `${args.targetName || "target"} · ${args.width}mm`,
136
+ }));
137
+
138
+ return (/* ...form UI reading/writing `args`... */);
139
+ }
140
+ ```
141
+
142
+ The value returned from `useRbSave` must be JSON-serialisable — the host
143
+ persists it as-is. Not every host embeds plugins in a save/restore form; if
144
+ yours doesn't, you can skip these hooks entirely.
145
+
146
+ ### UI components
147
+
148
+ Import Muscat components from `@rainbow-robotics/plugin-ui/react` and export a
149
+ React component as the default export. The host mounts that component directly
150
+ and provides `useRb()` through context.
151
+
152
+ ```tsx
153
+ import { useState } from "react";
154
+ import { RBLabelButton, RBSlider, RBSwitch } from "@rainbow-robotics/plugin-ui/react";
155
+ import { useRb } from "@rainbow-robotics/plugin-ui/sdk";
156
+
157
+ export default function App() {
158
+ const rb = useRb();
159
+ const [width, setWidth] = useState(20);
160
+ const [enabled, setEnabled] = useState(true);
161
+
162
+ return (
163
+ <div className="flex flex-col gap-4">
164
+ <RBSlider
165
+ value={[width]}
166
+ min={0}
167
+ max={100}
168
+ onValueChange={(values: number[]) => setWidth(values[0] ?? 0)}
169
+ />
170
+ <RBSwitch checked={enabled} onCheckedChange={setEnabled} />
171
+ <RBLabelButton
172
+ disabled={!enabled}
173
+ onClick={() => rb.action("grip", { width })}
174
+ >
175
+ Grip
176
+ </RBLabelButton>
177
+ </div>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Developing directly on the robot PC
185
+
186
+ If you're running `muscat-pk dev` **on the robot PC itself**, Muscat already serves the
187
+ plugin gateway on port 10000. The CLI sees that, does not start its own local Muscat, and
188
+ your plugin backend connects to the robot's gateway — exactly what you want when
189
+ developing on the robot.
190
+
191
+ ---
192
+
193
+ ## Several developers on one robot
194
+
195
+ Against a remote robot, `muscat-pk dev` / `dev:back` run your plugin under a dev id of its own,
196
+ `<manifest id>.dev-<user>-<random>` (printed at start; the random part is kept in `~/.muscat-pk/dev-id`).
197
+ Two machines developing the same plugin on one robot would otherwise register the same topics on its
198
+ gateway, and a button in one UI would silently run the other machine's backend. The UI, the backend,
199
+ the ROS namespace and external routes (`common/plugin/<dev id>/<path>`) all use the dev id.
200
+
201
+ Pass `--keep-id` to run under the manifest id instead, e.g. to test program `execute` or an external
202
+ client that uses the real id. Then only one machine may be connected to that robot.
203
+
204
+ ---
205
+
206
+ ## Backend runtimes
207
+
208
+ `manifest.json` chooses one backend runtime. `muscat-pk dev` runs backend code in
209
+ a Docker container matching the robot environment. Backend code should contain
210
+ plugin logic only; it should not start its own copy of the plugin gateway.
211
+
212
+ ### Which backend pattern? (depends on the contribution point)
213
+
214
+ **How you control the robot depends on what your plugin contributes** (see
215
+ `manifest.json` → `contributes[].point`). Don't mix these up:
216
+
217
+ | Contribution point | What it is | Backend pattern |
218
+ | --- | --- | --- |
219
+ | `program.panel` (with `commandInfo`) | Defines a **custom command** the plugin adds to the Program page's rule-based sequence builder. The operator places it into the program tree like any built-in command (Move J, Point, Wait, …); it runs when the program reaches that step. | **`PluginProgramContext`** — integrated with the program's stop/pause and variables. See _Program command backend_ below. |
220
+ | `toolbar` / `lnb` / `move.panel` | A live UI panel/popover the operator interacts with directly | Call the robot **SDK directly** (`RBManipulateSDK`) from `cpp/main.cpp`, or `rb.action(...)` → your backend → SDK. **Not** a program step — there is no stop/pause/variable context. |
221
+
222
+ Rule of thumb: use `PluginProgramContext` **only** for a `program.panel` command
223
+ that runs as part of a program. To move the robot live from a toolbar/move panel,
224
+ use the SDK — a program-step context does not apply.
225
+
226
+ ### Frontend-only (`none`)
227
+
228
+ Use this when the plugin only has UI and calls host services:
229
+
230
+ ```json
231
+ { "runtime": { "type": "none" } }
232
+ ```
233
+
234
+ ### C++ (`cpp`)
235
+
236
+ C++ plugins build `cpp/` with CMake and run the executable named by
237
+ `manifest.json` → `runtime.entry`. The Rainbow Robotics C++ SDK
238
+ (`rainbow-rb-sdk-dev`) is pre-installed in the `muscat-pk` dev container — you
239
+ don't install anything locally.
240
+
241
+ ```json
242
+ { "runtime": { "type": "cpp", "entry": "__PLUGIN_SLUG__" } }
243
+ ```
244
+
245
+ ```cmake
246
+ # cpp/CMakeLists.txt — the version is the SDK the dev container installs (muscat-pk sdk update <version>)
247
+ find_package(rb_sdk 0.10.4 EXACT REQUIRED)
248
+ add_executable(__PLUGIN_SLUG__ main.cpp) # name must equal runtime.entry
249
+ target_link_libraries(__PLUGIN_SLUG__ PRIVATE rb_sdk::rb_sdk)
250
+ ```
251
+
252
+ Editor support: the SDK is installed only inside the dev container, so `muscat-pk new` (and later
253
+ `dev` / `build` / `type change`) copies its headers to `.muscat-dev/sysroot` and writes a `Muscat`
254
+ configuration to `.vscode/c_cpp_properties.json`. VS Code's C/C++ extension then resolves `rb_sdk`
255
+ names. The copies follow the SDK version above; the build itself never uses them. ROS 2 headers
256
+ (`rclcpp`) are not copied.
257
+
258
+ Headers live under `<rainbow/rb_sdk/...>`:
259
+
260
+ | Header | Class | Use |
261
+ | --- | --- | --- |
262
+ | `plugin/rb_plugin.hpp` | `rb_plugin::RBPluginCall` | plugin UI ↔ backend, external endpoints (`router()`) |
263
+ | `manipulate/rb_manipulate_sdk.hpp` | `RBManipulateSDK` | control a manipulator |
264
+ | `program/rb_program_sdk.hpp` | `RBProgramSDK` | program-side helpers |
265
+ | `program/plugin_program_context.hpp` | `rb_plugin::PluginProgramContext` | `program.panel` commands |
266
+ | `zenoh_client.hpp` | `ZenohClient` | low-level pub/sub/query |
267
+
268
+ Payloads are `nlohmann::json` (`#include <nlohmann/json.hpp>`, installed with the SDK).
269
+
270
+ #### Talking to the plugin UI (`RBPluginCall`)
271
+
272
+ ```cpp
273
+ #include <rainbow/rb_sdk/plugin/rb_plugin.hpp>
274
+ #include <nlohmann/json.hpp>
275
+
276
+ using nlohmann::json;
277
+ using rb_plugin::RBPluginCall;
278
+
279
+ int main() {
280
+ // plugin id comes from env RB_PLUGIN_ID (set by muscat-pk dev and by the robot)
281
+ auto plugin = RBPluginCall::get_instance(RBPluginCall::resolve_plugin_id(std::nullopt));
282
+
283
+ plugin->publish("state", {{"ready", true}}); // UI: rb.sub("state", ...)
284
+
285
+ auto sub = plugin->subscribe("event", // UI: rb.pub("event", ...)
286
+ [](const std::string& topic, const json& payload) { /* ... */ });
287
+
288
+ plugin->serve("tool/echo", [](const json& payload) { // UI: await rb.action("tool/echo", ...)
289
+ return json{{"echo", payload}};
290
+ });
291
+
292
+ json res = plugin->call("tool/ping", {{"hello", "plugin"}}, 5.0); // UI answers with rb.serve("tool/ping", ...)
293
+
294
+ for (;;) std::this_thread::sleep_for(std::chrono::seconds(1)); // stay resident
295
+ }
296
+ ```
297
+
298
+ | C++ | UI (`useRb()`) |
299
+ | --- | --- |
300
+ | `plugin->serve(topic, fn)` | `await rb.action(topic, payload)` |
301
+ | `plugin->subscribe(topic, fn)` | `rb.pub(topic, payload)` |
302
+ | `plugin->publish(topic, payload)` | `rb.sub(topic, handler)` |
303
+ | `plugin->call(topic, payload, timeout_sec)` | `rb.serve(topic, handler)` |
304
+
305
+ - Keep the `Subscription` returned by `subscribe`; pass it to `plugin->unsubscribe(std::nullopt, sub)` to remove just that one.
306
+ - `call` throws `rb_plugin::RBPluginGatewayError` when the gateway is down or the UI doesn't answer in time.
307
+
308
+ #### External endpoints (`router()`)
309
+
310
+ `serve` / `subscribe` / `publish` are **internal only** (plugin UI ↔ backend).
311
+ To expose an endpoint to the outside world — REST, Socket.IO, TCP, MQTT,
312
+ WebRTC — use `plugin->router()`. The running Muscat (`common`) picks it up
313
+ without a restart, and it disappears when the plugin stops.
314
+
315
+ ```cpp
316
+ auto& router = plugin->router();
317
+
318
+ router.on("/hello", [](const json& payload, const json& meta) { // all 5 transports
319
+ return json{{"msg", "hi " + payload.value("name", std::string("there"))}};
320
+ });
321
+
322
+ router.on("/admin", [](const json& payload, const json& meta) {
323
+ if (!meta.value("principal", json::object()).value("is_admin", false))
324
+ throw rb_plugin::PluginRouteError(403, "admin only"); // becomes the external status
325
+ return json::object();
326
+ }, std::vector<std::string>{"api"}); // REST only
327
+
328
+ router.subscribe("/cmd", [](const json& payload) { /* ... */ },
329
+ std::vector<std::string>{"tcp"}); // receive external TCP publish
330
+
331
+ router.emit("/state", {{"busy", true}}); // push to external subscribers
332
+ ```
333
+
334
+ ```bash
335
+ curl -s -X POST http://<robot>/common/plugin/<plugin_id>/hello \
336
+ -H 'Content-Type: application/json' -d '{"name":"kim"}'
337
+ # → {"msg":"hi kim"}
338
+ ```
339
+
340
+ | API | Meaning | External address |
341
+ | --- | --- | --- |
342
+ | `router.on(path, handler, explicit_routes)` | request/response handler | `common/plugin/{plugin_id}/{path}` (REST is always `POST`) |
343
+ | `router.subscribe(topic, handler, explicit_events)` | receive external publishes | external side publishes to `common/plugin/{plugin_id}/{topic}` |
344
+ | `router.emit(topic, payload)` | plugin → external push | external side subscribes to `common/plugin/{plugin_id}/{topic}` |
345
+
346
+ - `meta` = `{"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": object | null}`. **Role checks are the plugin's job.**
347
+ - Transport names: `api | socket | tcp | mqtt | webrtc`; `subscribe` accepts only `tcp | mqtt | webrtc`. Unknown names or duplicate paths throw `std::invalid_argument`.
348
+ - Throw `rb_plugin::PluginRouteError(code, message)` to set the status; any other exception → 500.
349
+
350
+ | Situation | Status |
351
+ | --- | --- |
352
+ | No plugin declares that path / plugin is down / transport not allowed | 404 |
353
+ | Plugin did not answer within 10 s | 504 |
354
+ | `common` ↔ gateway not connected | 503 |
355
+
356
+ During `muscat-pk dev` the local Muscat runtime takes `router()` registrations on its
357
+ gateway and its `common` routes external calls to them like on the robot
358
+ (`curl http://localhost:3000/common/plugin/<plugin-id>/<path>`). After
359
+ restarting the plugin, external calls may return 504 for about a minute, then 404.
360
+
361
+ #### Program command backend (`program.panel`)
362
+
363
+ A `program.panel` contribution adds a **custom command** to the Program page.
364
+ Each time the program reaches that step, the host calls your backend through
365
+ `PluginProgramContext` with the step args (what the UI saved with `useRbSave`).
366
+
367
+ ```cpp
368
+ #include <rainbow/rb_sdk/program/plugin_program_context.hpp>
369
+
370
+ using rb_plugin::PluginProgramContext;
371
+
372
+ int main() {
373
+ PluginProgramContext ctx("__PLUGIN_ID__");
374
+
375
+ ctx.set_execute_handler([&](const nlohmann::json& args) {
376
+ auto count = ctx.get_variable("counter"); // json; null if no program runs
377
+ ctx.set_variable("counter", count.is_number() ? count.get<int>() + 1 : 1);
378
+ ctx.set_variable("RBP___PLUGIN_SLUG___result", 42); // NEW variables must start with RBP_
379
+
380
+ try {
381
+ for (int i = 0; i < 100; ++i) {
382
+ ctx.check_stop(); // pause → blocks, stop → throws PluginStopped
383
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
384
+ }
385
+ } catch (const rb_plugin::PluginStopped&) {
386
+ return; // program was stopped
387
+ }
388
+ ctx.done(); // always call — the step ends here
389
+ });
390
+
391
+ ctx.start(); // does not block
392
+ for (;;) std::this_thread::sleep_for(std::chrono::seconds(1));
393
+ }
394
+ ```
395
+
396
+ - The step does not finish until you call `ctx.done()`.
397
+ - `set_execute_handler` must be called before `start()`.
398
+ - **New** variables need the `RBP_` prefix (`RBP_{plugin_id}_{name}` recommended); existing ones pass as-is.
399
+ - `get_variable(name, robot_model, plugin_id)` — optional filters: a specific robot, or the value a given plugin last wrote.
400
+ - Executor state: `ctx.executor_state_for("C500920")`, `ctx.executor_states()`, `ctx.is_paused()`, `ctx.is_stopped()`,
401
+ or `ctx.set_state_change_handler([](const std::string& model, const std::string& state) { ... })`.
402
+ - A long-running handler must call `check_stop()` every iteration, or it keeps running after the operator presses Stop.
403
+
404
+ Variables the Program page variable picker should offer must be declared in
405
+ `manifest.json` under `"variables"`.
406
+
407
+ #### Controlling the robot
408
+
409
+ ```cpp
410
+ #include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
411
+
412
+ RBManipulateSDK manipulate; // on the robot: connects to the local Zenoh
413
+ manipulate.state.call_servocontrol("C500920", 1);
414
+
415
+ IPC::MoveInput_TargetT t;
416
+ t.tar_values = {0, 0, 90, 0, 90, 0, 0}; // joint degrees, always 7 values (7th = 0 on 6-axis arms)
417
+ t.tar_frame = -1; // -1 = joint space
418
+ t.tar_unit = 0; // 0 = mm/deg
419
+ manipulate.move.call_move_j("C500920", t, std::nullopt, MoveSpeedArg{1, 60.f, 120.f});
420
+ manipulate.move.call_move_wait("C500920", -1);
421
+ ```
422
+
423
+ Remote dev (`muscat-pk dev --robot-ip <ip> [--robot-token <token>]`) sets
424
+ `MUSCAT_ROBOT_HOST` / `MUSCAT_ROBOT_TOKEN`; connect **before** registering
425
+ subscriptions, because `connect()` reopens the Zenoh session. The token is
426
+ optional — it is needed only when the robot's login policy is on:
427
+
428
+ ```cpp
429
+ if (const char* host = std::getenv("MUSCAT_ROBOT_HOST")) {
430
+ const char* token = std::getenv("MUSCAT_ROBOT_TOKEN");
431
+ manipulate.connect({.token = token ? token : "", .host = host});
432
+ }
433
+ ```
434
+
435
+ AMR and RB-Y1 SDKs are Python-only for now.
436
+
437
+ ### ROS2 (rclcpp)
438
+
439
+ ROS2 C++ plugins use `ros2_ws/` with `runtime.rosClient: "rclcpp"`. The node
440
+ links the C++ SDK the same way (`find_package(rb_sdk)` + `rb_sdk::rb_sdk`); relative topic names are placed under the plugin's ROS namespace — the plugin id
441
+ with "." → "/" and "-" → "_" (`com.example.my-plugin` → `/com/example/my_plugin`), in dev
442
+ and on the robot. Use an absolute name (`/cmd_vel`) for topics shared with other nodes.
443
+
444
+ ```cpp
445
+ #include "rclcpp/rclcpp.hpp"
446
+ #include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
447
+
448
+ class __PLUGIN_CLASS__Node : public rclcpp::Node {
449
+ public:
450
+ __PLUGIN_CLASS__Node() : Node("__PLUGIN_SLUG___node") {
451
+ RCLCPP_INFO(this->get_logger(), "__PLUGIN_NAME__ node started");
452
+ }
453
+ };
454
+
455
+ int main(int argc, char** argv) {
456
+ rclcpp::init(argc, argv);
457
+ rclcpp::spin(std::make_shared<__PLUGIN_CLASS__Node>());
458
+ rclcpp::shutdown();
459
+ return 0;
460
+ }
461
+ ```
462
+
463
+ `RBPluginCall` and `PluginProgramContext` work in an rclcpp node too — create
464
+ them in `main()` before `rclcpp::spin`.
465
+
466
+
467
+ #### Talking to an external PC over ROS 2
468
+
469
+ ROS 2 discovery stays on the robot (`ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST`; `muscat-pk dev`
470
+ runs the same), so robots on one LAN don't mix topics. Allow specific PCs from the plugin UI —
471
+ Muscat keeps the list per plugin, starts your backend with `ROS_STATIC_PEERS` set to it, and
472
+ restarts the plugin when it changes. Your ROS code needs no changes; the PC needs no extra settings.
473
+
474
+ ```tsx
475
+ const rb = useRb();
476
+ const peers = await rb.rosPeers.get(); // ["192.168.0.50"]
477
+ await rb.rosPeers.set(["192.168.0.50", "192.168.0.51"]); // saves + restarts the plugin; rejects on a bad IP
478
+ ```
479
+
480
+ - Per plugin: plugin B keeps its allowed PCs when plugin A (allowing the same PC) is stopped or removed.
481
+ - It opens communication; it is not access control — a PC that knows the robot's IP can still
482
+ add the robot to its own `ROS_STATIC_PEERS`.
483
+ - In `muscat-pk dev` the list is kept in `.muscat-dev/ros-peers.json` (ROS 2 backends).
484
+
485
+ ---
486
+
487
+ ## Video streaming (MediaMTX)
488
+
489
+ Muscat runs a MediaMTX media server. The backend publishes a stream over RTSP and
490
+ the UI plays it over WebRTC. `muscat-pk dev` runs the same server (or uses a local
491
+ Muscat's), so the addresses are identical in dev and on the robot.
492
+
493
+ | | Address |
494
+ | --- | --- |
495
+ | Publish (backend) | `rtsp://127.0.0.1:8554/<plugin_id>/<name>` |
496
+ | Play (UI) | `http://<host>:8889/<plugin_id>/<name>/` |
497
+
498
+ - Prefix the path with your plugin id — paths are shared with robot cameras and other plugins.
499
+ - The robot runtime has no ffmpeg, GStreamer or OpenCV; bring your own encoder (below).
500
+
501
+ **Backend (C++)** — publish RTSP with an encoder library you bring (e.g. FFmpeg's
502
+ libavformat / libx264); the robot has none installed. Ship it with the plugin — see
503
+ _Bundling extra shared libraries_.
504
+
505
+ **UI** — embed MediaMTX's WebRTC player. `location.hostname` is `localhost` in the
506
+ preview and the robot's address on the robot:
507
+
508
+ ```tsx
509
+ const STREAM = "__PLUGIN_ID__/cam"; // the path the backend publishes
510
+
511
+ export default function Camera() {
512
+ return (
513
+ <iframe
514
+ src={`${location.protocol}//${location.hostname}:8889/${STREAM}/`}
515
+ allow="autoplay"
516
+ className="aspect-video w-full border-0"
517
+ />
518
+ );
519
+ }
520
+ ```
521
+
522
+ ---
523
+
524
+ ## manifest.json
525
+
526
+ ```jsonc
527
+ {
528
+ // unique plugin ID — reverse-domain format recommended
529
+ "id": "com.example.my-plugin",
530
+ "name": "My Plugin",
531
+ "version": "1.0.0",
532
+
533
+ // minimum Muscat version required to load this plugin
534
+ "minMuscatVersion": "1.0.0",
535
+
536
+ // plugin author (empty string is fine)
537
+ "author": "Your Name",
538
+
539
+ // displayed in the Muscat plugin store / info panel
540
+ // string or { en (required), ko, ja, zh, vi, th, es }
541
+ "description": { "en": "Plugin description", "ko": "플러그인 설명" },
542
+
543
+ // SVG string (single file) or PNG/JPG size map (all three sizes required)
544
+ "icons": "assets/icon.svg",
545
+ // "icons": { "16": "assets/icon-16.png", "48": "assets/icon-48.png", "128": "assets/icon-128.png" },
546
+
547
+ // targetModels and/or targetCategorys — at least one is required
548
+ // "all" = every target, or list specific values
549
+ "targetModels": "all",
550
+ // "targetModels": ["C500920", "C500930"],
551
+ "targetCategorys": "all",
552
+ // "targetCategorys": ["manipulate", "amr", "rby1", "vision"],
553
+
554
+ "contributes": [
555
+ // program.panel — defines a custom command for the Program page sequence
556
+ // builder; runs via PluginProgramContext when the program reaches it
557
+ {
558
+ "point": "program.panel",
559
+ "icon": "assets/icon.svg",
560
+ "label": { "ko": "내 플러그인", "en": "My Plugin" },
561
+ "ui": "program", // must be unique across all contributions
562
+ "commandInfo": {
563
+ "name": "My Plugin",
564
+ "icon": "assets/icon.svg",
565
+ "funcName": "rb_program_sdk.plugin_execute",
566
+ "args": {}
567
+ }
568
+ },
569
+ // toolbar — adds a button to the top toolbar (required)
570
+ {
571
+ "point": "toolbar",
572
+ "icon": "assets/icon.svg",
573
+ "label": { "ko": "내 플러그인", "en": "My Plugin" },
574
+ "ui": "content", // must be unique across all contributions
575
+ "opens": "popover" // popover | panel | page
576
+ },
577
+ // move.panel — adds a panel to the Move screen
578
+ {
579
+ "point": "move.panel",
580
+ "icon": "assets/icon.svg",
581
+ "label": { "ko": "내 플러그인", "en": "My Plugin" },
582
+ "ui": "move" // must be unique across all contributions
583
+ }
584
+ ],
585
+
586
+ // optional: variables this plugin exposes to pyfm programs — the Program
587
+ // page variable picker lists ONLY declared variables. name must start with
588
+ // RBP_; description is optional but without it the UI can't explain the
589
+ // variable, so write one.
590
+ "variables": [
591
+ { "name": "RBP_grip_force", "type": "number", "init": 0.5, "description": { "ko": "그리퍼 힘", "en": "Gripper force" } },
592
+ { "name": "RBP_target_pose", "type": "array", "init": [0, 0, 0] }
593
+ ],
594
+
595
+ // runtime — one of:
596
+ // { "type": "none" }
597
+ // { "type": "python", "entry": "main.py" }
598
+ // { "type": "cpp", "entry": "<executable name>" }
599
+ // { "type": "ros2", "rosClient": "rclpy" | "rclcpp", "package": "...", "launch": "..._node" }
600
+ "runtime": { "type": "cpp", "entry": "__PLUGIN_SLUG__" },
601
+
602
+ // optional: TCP ports opened by this plugin backend
603
+ // only number[] is allowed; ranges/objects are not accepted
604
+ // external plugin ports must be within 28600-28999
605
+ "ports": [28600]
606
+ }
607
+ ```
608
+
609
+ **Backend types:**
610
+
611
+ | type | source dir | build / run | entry |
612
+ |---|---|---|---|
613
+ | `python` | `py/` | pip + run in Docker | `main.py` |
614
+ | `cpp` | `cpp/` | CMake in Docker | built executable name |
615
+ | `ros2` (rclpy) | `ros2_ws/` | colcon (ament_python) in Docker | — |
616
+ | `ros2` (rclcpp) | `ros2_ws/` | colcon (ament_cmake) in Docker | — |
617
+
618
+ > Every back end runs in a Docker container matching the robot (Ubuntu 24.04 + Python 3.12), so dev behaves like production and compiled binaries are ABI-compatible. **Docker is required** for all backend types (only a frontend-only `none` plugin skips it). No local Python/toolchain setup is needed.
619
+
620
+ **Contribution points:**
621
+
622
+ | point | Where it appears | Backend | `commandInfo` |
623
+ |---|---|---|:---:|
624
+ | `toolbar` | Top toolbar button (must have at least one) | SDK / `rb.action` | — |
625
+ | `lnb` | Left navigation entry | SDK / `rb.action` | — |
626
+ | `move.panel` | Move screen panel | SDK / `rb.action` | — |
627
+ | `program.panel` | Custom command in the Program page sequence builder | `PluginProgramContext` | ✓ |
628
+
629
+ **`commandInfo` fields** (for `program.panel`):
630
+
631
+ | field | type | description |
632
+ |---|---|---|
633
+ | `name` | `string` | Display name of the command in the program tree |
634
+ | `icon` | `string` | Icon path |
635
+ | `iconClassName` | `string?` | Optional CSS class for the icon |
636
+ | `funcName` | `string?` | Backend function name to call |
637
+ | `args` | `Record<string, any>?` | Default arguments |
638
+ | `disabled` | `boolean?` | Whether the node is disabled |
639
+ | `allowChildren` | `string[]?` | Allowed child commands |
640
+ | `allowParents` | `string[]?` | Allowed parent commands |
641
+ | `notAllowChildren` | `string[]?` | Disallowed child commands |
642
+ | `syncGroupId` | `boolean?` | Sync group ID across children |
643
+ | `disableCopy` | `boolean?` | Prevent copy |
644
+ | `disableDelete` | `boolean?` | Prevent delete |
645
+ | `requireChildren` | `boolean?` | Whether a child command is required |
646
+ | `requireParent` | `boolean?` | Whether a parent command is required |
647
+ | `childrenCommand` | `string?` | Default command type for children |
648
+
649
+ **Validation rules:**
650
+ - `id`, `name`, `version`, `minMuscatVersion` — required non-empty strings
651
+ - `author` — required non-empty string
652
+ - `description` — required (`string` or `{ en (required), ko?, ja?, zh?, vi?, th?, es? }`)
653
+ - `icons` — SVG: single string path ending in `.svg`; PNG/JPG: object with `"16"`, `"48"`, `"128"` all present
654
+ - `targetModels` — `"all"` or a non-empty array of robot model ID strings (at least one of `targetModels` / `targetCategorys` required)
655
+ - `targetCategorys` — `"all"` or a non-empty array of `"manipulate" | "amr" | "rby1" | "vision"` (at least one of `targetModels` / `targetCategorys` required)
656
+ - `contributes` — the plugin's screens; when present it must contain at least one `toolbar` entry. Leave it out (or `[]`) for a backend-only plugin: no UI is built, and Node.js is not needed to build it
657
+ - `ui` — each contribution must have a unique value
658
+ - `commandInfo` — required when `point` is `"program.panel"`
659
+ - `ports` — optional `number[]`; every port must be within `28600-28999`
660
+ and must not be a reserved robot/industrial/internal port
661
+ - `variables` — optional array of `{ name, type, init, description? }`:
662
+ - `name` — must start with `RBP_`, then letters/digits/underscore; unique per plugin
663
+ - `type` — `"number" | "string" | "array"`
664
+ - `init` — required initial value, must match `type`
665
+ - `description` — optional `string` or i18n object (`en` required in object form)
666
+
667
+ `muscat-pk build` throws on any validation error. `muscat-pk dev` prints
668
+ warnings — except `variables` errors (e.g. a name without the `RBP_` prefix),
669
+ which are hard errors in `dev` / `dev:back` too.
670
+
671
+ **Backend port rules:**
672
+ - Declare a port only if your backend opens a TCP server/listener.
673
+ - **Every listening TCP port must be declared.** `muscat-pk dev` and
674
+ `muscat-pk dev:back` watch the backend and stop it the moment it opens a port
675
+ that is not in `ports`. A port outside `28600-28999` can't be declared, so it
676
+ must not be opened either — picking e.g. `5500` is not a way around the rule.
677
+ It would escape this check *and* the robot's install-time conflict check, and
678
+ only collide once it's running on a robot.
679
+ - Reserved ports include `502`, `2222`, `4840`, `5170-5179`, `8000-8999`,
680
+ `10000`, `30004`, `34964`, `44818`, `50000-50005`, and `50051`.
681
+ - The robot rejects installation (HTTP 409) if a declared port is reserved,
682
+ out of range, or already claimed by another enabled plugin.
683
+
684
+ **Reaching your backend's ports during development:**
685
+ - Declared ports are published to `127.0.0.1` on your machine, so
686
+ `curl http://127.0.0.1:28600` works from the host on macOS, Windows, and Linux.
687
+ - On native Linux the dev container uses host networking, so the ports are on
688
+ the host already.
689
+ - If a declared port is already taken on your machine, `muscat-pk dev` warns and
690
+ skips publishing that one — the backend still listens inside the container.
691
+
692
+ ---
693
+
694
+ ## Localized documentation
695
+
696
+ You can ship per-language README files alongside the main `README.md`. Place them in the project root:
697
+
698
+ ```
699
+ README.md # default / English fallback
700
+ README.ko.md # Korean
701
+ README.en.md # explicit English (optional)
702
+ README.ja.md # Japanese
703
+ ```
704
+
705
+ All `README*.md` files found at the project root are automatically copied to `dist/` on
706
+ `muscat-pk build`. Muscat displays the file matching the user's locale,
707
+ falling back to `README.md` if none matches.
708
+
709
+ ---
710
+
711
+ ## C++ SDK & dependencies
712
+
713
+ The dev container has the C++ SDK (`rainbow-rb-sdk-dev`) pre-installed — no local
714
+ install needed. The SDK version is the one pinned in `CMakeLists.txt` (`find_package(rb_sdk <version> EXACT REQUIRED)`). Pick a different one with:
715
+
716
+ ```bash
717
+ muscat-pk sdk update <version>
718
+ ```
719
+
720
+ `muscat-pk build` copies the matching `librb_sdk.so` next to your binary
721
+ (`dist/<arch>/lib/`, or `install-<arch>/<pkg>/lib/` for rclcpp), so the robot needs no SDK
722
+ install and each plugin keeps the SDK version it was built with.
723
+
724
+ Other libraries: install them in the build (e.g. vendor them under `cpp/` and
725
+ `add_subdirectory`, or link statically) — the robot only has Ubuntu 24.04 base
726
+ libraries.
727
+
728
+ To use the SDK outside a plugin (your own Ubuntu 24.04 machine):
729
+
730
+ ```bash
731
+ curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/rb-sdk-apt.gpg
732
+ echo 'deb [signed-by=/usr/share/keyrings/rb-sdk-apt.gpg] https://rainbowrobotics.github.io/rb-sdk stable main' | sudo tee /etc/apt/sources.list.d/rb-sdk.list
733
+ sudo apt update && sudo apt install rainbow-rb-sdk-dev
734
+ ```
735
+
736
+
737
+ ### Bundling extra shared libraries
738
+
739
+ The robot only has Ubuntu 24.04 base libraries. Copy any other `.so` your executable
740
+ needs into `${CMAKE_BINARY_DIR}/lib` — `muscat-pk build` ships that folder as
741
+ `dist/<arch>/lib/` and the executable finds it via its RPATH (`$ORIGIN/lib`):
742
+
743
+ ```cmake
744
+ # cpp/CMakeLists.txt — third_party/ holds prebuilt libs for amd64 / arm64
745
+ set(ARCH_DIR ${CMAKE_SOURCE_DIR}/third_party/${CMAKE_SYSTEM_PROCESSOR})
746
+ target_link_libraries(__PLUGIN_SLUG__ PRIVATE ${ARCH_DIR}/libfoo.so)
747
+ add_custom_command(TARGET __PLUGIN_SLUG__ POST_BUILD
748
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/lib
749
+ COMMAND ${CMAKE_COMMAND} -E copy ${ARCH_DIR}/libfoo.so ${CMAKE_BINARY_DIR}/lib/)
750
+ ```
751
+
752
+ Build them for both arches (`x86_64`, `aarch64`) on Ubuntu 24.04 so they match the robot.
753
+ Static linking works too and needs no bundling. For an rclcpp node, `install(FILES ... DESTINATION lib)`
754
+ instead — the node finds `install-<arch>/<pkg>/lib/` the same way.
755
+ ---
756
+
757
+ ## Switching backend type
758
+
759
+ ```bash
760
+ muscat-pk type change # interactive
761
+ muscat-pk type change --to cpp # → C++
762
+ muscat-pk type change --to ros2 # → ROS2 (rclpy / Python)
763
+ muscat-pk type change --to ros2-cpp # → ROS2 (rclcpp / C++)
764
+ muscat-pk type change --to python # → Python
765
+ ```
766
+
767
+ > Existing backend dirs (`ros2_ws/`, `cpp/`) are not deleted when switching away. Remove them manually if no longer needed.
768
+
769
+ > Switching between a Python and a C++ backend asks whether to replace this README with the matching one (the old file is kept as `README.md.bak`). Add `--readme` to answer yes without the prompt.
770
+
771
+ ---
772
+
773
+ ## AI coding assistants (MCP)
774
+
775
+ If you build your plugin with an AI assistant (Claude Code, Codex, …), give it
776
+ grounded knowledge of the Muscat UI components, design tokens, and SDK instead of
777
+ letting it guess. `muscat-pk mcp` is an MCP server exposing:
778
+
779
+ - `list_components` / `get_component_props` / `get_component_example` — every
780
+ `@rainbow-robotics/plugin-ui/react` component with its real props and a working
781
+ Storybook example.
782
+ - `list_design_tokens` / `search_design_tokens` — the Tailwind classes to use
783
+ (`bg-bg-*`, `text-fg-*`, `typo-*`, …) so your UI matches the host theme.
784
+ - `search_rb_sdk` — how to use `useRb()`, `rb.*`, `useRbFormData`/`useRbSave`, and
785
+ the backend runtimes.
786
+ - `list_rb_sdk` / `get_rb_sdk_api` — the installed `rainbow-rb-sdk` (Python
787
+ backend) classes and signatures; with `lang: "cpp"`, the C++ SDK headers
788
+ (`rainbow-rb-sdk-dev`, read out of the `muscat-pk` cpp dev image — build it
789
+ once with `muscat-pk dev:back`).
790
+ - `check_project` — what this project must change for the plugin-ui it has
791
+ installed (old APIs such as `connect(App)` or `Slider`, a `vite.config` without
792
+ the `muscat()` preset, `tsc` errors), the same list `muscat-pk update` prints.
793
+ Ask the assistant to fix the project after an update; it re-checks until clean.
794
+
795
+ **Browse the components live** with `muscat-pk storybook`.
796
+
797
+ `muscat-pk new` also writes `AGENTS.md` (read by Codex, Cursor, Gemini CLI, Copilot)
798
+ and a `CLAUDE.md` that imports it: the UI rules (plugin-ui components instead of
799
+ native `<select>`/`<input>`/`<button>`, design tokens for colors) for assistants that
800
+ do not call the MCP tools on their own. Add your own project notes there.
801
+
802
+ ### Register the server
803
+
804
+ **Claude Code** — a `.mcp.json` is already scaffolded at your plugin root, so
805
+ it works out of the box:
806
+
807
+ ```json
808
+ {
809
+ "mcpServers": {
810
+ "muscat": {
811
+ "command": "muscat-pk",
812
+ "args": ["mcp"]
813
+ }
814
+ }
815
+ }
816
+ ```
817
+
818
+ **Codex** — register once with:
819
+
820
+ ```bash
821
+ muscat-pk mcp --install codex
822
+ ```
823
+
824
+ This appends the server to `~/.codex/config.toml` (idempotent — safe to run
825
+ again). Equivalent manual config:
826
+
827
+ ```toml
828
+ [mcp_servers.muscat]
829
+ command = "muscat-pk"
830
+ args = ["mcp"]
831
+ ```
832
+
833
+ Any other MCP client works too — it's a standard stdio MCP server started with
834
+ `muscat-pk mcp`. If `muscat-pk` isn't on the client's PATH, use its absolute path
835
+ (`which muscat-pk`) as `command`.
836
+
837
+ ---
838
+
839
+ ## Shipping
840
+
841
+ ```bash
842
+ muscat-pk build # build + ZIP
843
+ ```
844
+
845
+ > Building the other arch (e.g. arm64 on an amd64 PC) runs in an emulated container for
846
+ > C++ backends. Docker Desktop has it built in; on Linux enable it once with
847
+ > `docker run --privileged --rm tonistiigi/binfmt --install all`, or pass `--arch` for one arch.
848
+
849
+ - Builds `ui/content.tsx` → `dist/content.js` (minified)
850
+ - Compiles `cpp/` (Release) in the Ubuntu 24.04 container per arch → `dist/<arch>/<entry>` + `dist/<arch>/lib/librb_sdk.so.*`
851
+ - ROS2 (rclcpp): runs `colcon build` per arch → `dist/ros2_ws/install-<arch>/`
852
+ - Copies `assets/`, `manifest.json`, and `README*.md` into `dist/`
853
+ - Output: `__PLUGIN_DIR__-{version}.zip`
854
+
855
+ ZIP layout:
856
+
857
+ ```
858
+ __PLUGIN_DIR__-1.0.0.zip
859
+ ├─ dist/
860
+ │ ├─ content.js # built UI
861
+ │ ├─ manifest.json
862
+ │ ├─ assets/
863
+ │ ├─ README.md
864
+ │ ├─ README.ko.md # if present
865
+ │ ├─ amd64/ # cpp: <entry> + lib/librb_sdk.so.*
866
+ │ ├─ arm64/
867
+ │ └─ ros2_ws/ # rclcpp: install-amd64/, install-arm64/
868
+ ```
869
+
870
+ ---
871
+
872
+ ## All commands
873
+
874
+ ```bash
875
+ muscat-pk dev # UI + backend
876
+ muscat-pk dev:ui # UI preview only (preview-host)
877
+ muscat-pk dev:back # backend only (C++ rebuild + restart in Docker)
878
+ muscat-pk build # build → dist/ + distributable ZIP
879
+ muscat-pk storybook # browse the rb-components UI library in a browser
880
+ muscat-pk mcp # MCP server for AI assistants (components/tokens/SDK docs)
881
+ ```
882
+
883
+ ```bash
884
+ muscat-pk type change # switch backend type
885
+ muscat-pk update # update muscat-plugin-kit itself
886
+ muscat-pk uninstall # remove muscat-pk itself
887
+ ```
888
+
889
+ ---
890
+
891
+ ## Styles
892
+
893
+ `muscat()` automatically loads `ui/style.{css,scss,sass,less}` (or
894
+ `ui/styles.{css,scss,sass,less}`) for every UI entry, plus
895
+ `ui/{entry}.{css,scss,sass,less}` for a matching entry-specific stylesheet. You
896
+ do not need to import CSS from every `.tsx` file.
897
+
898
+ The scaffold uses Tailwind's theme/utilities layers only, so plugin utilities
899
+ are generated from files under `ui/` without applying Tailwind preflight resets
900
+ to the host page.
901
+
902
+ Built CSS selectors are scoped under the current plugin id
903
+ (`[data-muscat-plugin-id="..."]`). CSS-in-JS libraries such as Emotion work
904
+ normally, but global styles should be scoped by the author.
905
+
906
+ ### Tailwind
907
+
908
+ This template enables Tailwind in `vite.config.ts`:
909
+
910
+ ```ts
911
+ import { defineConfig } from "vite";
912
+ import tailwindcss from "@tailwindcss/vite";
913
+ import { muscat } from "@rainbow-robotics/muscat-plugin-kit/vite";
914
+
915
+ export default defineConfig({
916
+ plugins: [tailwindcss(), muscat()],
917
+ });
918
+ ```
919
+
920
+ The default `ui/style.css` uses Tailwind theme/utilities only, not preflight:
921
+
922
+ ```css
923
+ @layer theme, utilities;
924
+
925
+ @import "tailwindcss/theme.css" layer(theme);
926
+ @import "tailwindcss/utilities.css" layer(utilities);
927
+
928
+ @source "./**/*.{ts,tsx}";
929
+ ```
930
+
931
+ ### SCSS, Sass, and Less
932
+
933
+ Install the preprocessor package, then create one of the auto-loaded style files.
934
+
935
+ ```bash
936
+ npm i -D sass
937
+ ```
938
+
939
+ ```scss
940
+ /* ui/style.scss */
941
+ $accent: #3b82f6;
942
+
943
+ .panel {
944
+ border: 1px solid color-mix(in srgb, $accent 40%, transparent);
945
+
946
+ &__title {
947
+ color: $accent;
948
+ }
949
+ }
950
+ ```
951
+
952
+ For Less, install `less` and use `ui/style.less`.
953
+
954
+ ### Emotion
955
+
956
+ Emotion dependencies may be in `dependencies` or `devDependencies`; Vite can
957
+ bundle them when they are installed and imported by the plugin.
958
+
959
+ For `styled`, no Vite config change is needed:
960
+
961
+ ```bash
962
+ npm i @emotion/react @emotion/styled
963
+ ```
964
+
965
+ ```tsx
966
+ import styled from "@emotion/styled";
967
+
968
+ const Panel = styled.div({
969
+ display: "grid",
970
+ gap: 12,
971
+ });
972
+ ```
973
+
974
+ For Emotion's `css` prop, pass React plugin options through `muscat()` and add
975
+ `jsxImportSource` to `tsconfig.json`:
976
+
977
+ ```bash
978
+ npm i @emotion/react @emotion/styled
979
+ npm i -D @emotion/babel-plugin
980
+ ```
981
+
982
+ ```ts
983
+ // vite.config.ts
984
+ import { defineConfig } from "vite";
985
+ import tailwindcss from "@tailwindcss/vite";
986
+ import { muscat } from "@rainbow-robotics/muscat-plugin-kit/vite";
987
+
988
+ export default defineConfig({
989
+ plugins: [
990
+ tailwindcss(),
991
+ muscat({
992
+ react: {
993
+ jsxImportSource: "@emotion/react",
994
+ babel: {
995
+ plugins: ["@emotion/babel-plugin"],
996
+ },
997
+ },
998
+ }),
999
+ ],
1000
+ });
1001
+ ```
1002
+
1003
+ ```json
1004
+ {
1005
+ "compilerOptions": {
1006
+ "jsxImportSource": "@emotion/react"
1007
+ }
1008
+ }
1009
+ ```
1010
+
1011
+ Emotion `Global` writes global CSS at runtime, so scope it manually or avoid it
1012
+ inside plugins.