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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/README.ko.md +1186 -0
  2. package/README.md +1137 -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 +10 -0
  11. package/cli/src/__pycache__/host_agent.cpython-37.pyc +0 -0
  12. package/cli/src/ai-rules.mjs +107 -0
  13. package/cli/src/build.mjs +333 -93
  14. package/cli/src/check-package.mjs +8 -23
  15. package/cli/src/completion.mjs +168 -0
  16. package/cli/src/cpp.mjs +78 -0
  17. package/cli/src/cppIde.mjs +76 -0
  18. package/cli/src/dev.mjs +195 -6
  19. package/cli/src/devBack.mjs +671 -60
  20. package/cli/src/devId.mjs +54 -0
  21. package/cli/src/devSession.mjs +67 -0
  22. package/cli/src/devUi.mjs +214 -25
  23. package/cli/src/esbuild.mjs +27 -10
  24. package/cli/src/host_agent.py +223 -0
  25. package/cli/src/manifest.mjs +288 -0
  26. package/cli/src/mcp.mjs +1153 -0
  27. package/cli/src/mcpProxy.mjs +110 -0
  28. package/cli/src/muscatLocal.mjs +292 -0
  29. package/cli/src/new.mjs +205 -68
  30. package/cli/src/pluginDevServer.mjs +111 -0
  31. package/cli/src/ports.mjs +193 -0
  32. package/cli/src/projectCheck.mjs +102 -0
  33. package/cli/src/python.mjs +112 -0
  34. package/cli/src/ros2.mjs +62 -18
  35. package/cli/src/sdk.mjs +74 -24
  36. package/cli/src/sim.mjs +177 -0
  37. package/cli/src/storybook.mjs +150 -0
  38. package/cli/src/templates-embedded.mjs +4 -0
  39. package/cli/src/type.mjs +93 -34
  40. package/cli/src/uninstall.mjs +81 -0
  41. package/cli/src/update.mjs +364 -35
  42. package/cli/src/util.mjs +341 -11
  43. package/cli/src/zenohDev.mjs +83 -0
  44. package/cli/src/zip.mjs +121 -0
  45. package/cli/storybook-dist/_design-tokens.css +4957 -0
  46. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  47. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  48. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  49. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  50. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  51. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  52. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  53. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  54. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  55. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  56. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  57. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  58. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -0
  59. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  60. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  61. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  62. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  63. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  64. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  65. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  66. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  67. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  68. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  69. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  70. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -0
  71. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  72. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  73. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  74. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  75. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  76. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  77. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  78. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  79. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  84. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  85. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  86. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  87. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  88. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  89. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  90. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  91. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  92. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  93. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  94. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  95. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  96. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  97. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  98. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  99. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  100. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  101. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  102. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  103. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  104. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  105. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  106. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  107. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  108. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  109. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  110. package/cli/storybook-dist/assets/Color-YHDXOIA2-pCKrjUal.js +1 -0
  111. package/cli/storybook-dist/assets/Docs-B-Y7I1GA.js +7 -0
  112. package/cli/storybook-dist/assets/Docs-BFJcE_VS.js +8 -0
  113. package/cli/storybook-dist/assets/Docs-B_QaTtz8.js +8 -0
  114. package/cli/storybook-dist/assets/Docs-CryDGmfh.js +20 -0
  115. package/cli/storybook-dist/assets/Docs-Cw2AVNKx.js +7 -0
  116. package/cli/storybook-dist/assets/Docs-Q6r1fBeL.js +12 -0
  117. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-_OmZBCn8.js +2 -0
  118. package/cli/storybook-dist/assets/NxAvatar.stories-Ctf5G2FQ.js +13 -0
  119. package/cli/storybook-dist/assets/NxButton.stories-DeLObVSj.js +20 -0
  120. package/cli/storybook-dist/assets/NxIcons.stories-DeqrVzVs.js +3 -0
  121. package/cli/storybook-dist/assets/NxNumberInput.stories-LqoY0ed1.js +26 -0
  122. package/cli/storybook-dist/assets/NxPagination.stories-DI6bXuJM.js +11 -0
  123. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-3HSV1XmN.js +14 -0
  124. package/cli/storybook-dist/assets/NxRangeInput.stories-1pjgQVaG.js +17 -0
  125. package/cli/storybook-dist/assets/NxSelectBox.stories-bcQPZEuF.js +17 -0
  126. package/cli/storybook-dist/assets/NxSlideRange.stories-BosX4axk.js +14 -0
  127. package/cli/storybook-dist/assets/NxSwitch.stories-DxV6dS33.js +11 -0
  128. package/cli/storybook-dist/assets/NxTextArea.stories-CO545-LP.js +11 -0
  129. package/cli/storybook-dist/assets/NxTextInput.stories-BP0npai1.js +11 -0
  130. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  131. package/cli/storybook-dist/assets/RBAccordion.stories-fVR4Va2a.js +17 -0
  132. package/cli/storybook-dist/assets/RBAccordionGroup.stories-Du5bsckD.js +33 -0
  133. package/cli/storybook-dist/assets/RBAlert.stories-DNkMXUu6.js +17 -0
  134. package/cli/storybook-dist/assets/RBBadge.stories-C44pXt8D.js +1 -0
  135. package/cli/storybook-dist/assets/RBBattery.stories-DE2JHvfL.js +10 -0
  136. package/cli/storybook-dist/assets/RBCard.stories-CuIda7NI.js +45 -0
  137. package/cli/storybook-dist/assets/RBCheckBox.stories-D1qM1ayr.js +1 -0
  138. package/cli/storybook-dist/assets/RBCheckIconButton.stories-e4gkliBO.js +1 -0
  139. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-CDzCtsMI.js +24 -0
  140. package/cli/storybook-dist/assets/RBChipTab.stories-DBrc_3Mp.js +21 -0
  141. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-hlrsHjKY.js +1 -0
  142. package/cli/storybook-dist/assets/RBColorIcons.stories-BHUvpkdu.js +3 -0
  143. package/cli/storybook-dist/assets/RBCommand.stories-azAF41NP.js +73 -0
  144. package/cli/storybook-dist/assets/RBConfirm.stories-BMNjwbmT.js +20 -0
  145. package/cli/storybook-dist/assets/RBDateInput.stories-D0VezDzv.js +15 -0
  146. package/cli/storybook-dist/assets/RBDialog.stories-AOJ975Fy.js +38 -0
  147. package/cli/storybook-dist/assets/RBDivider.stories-NSvnj43Z.js +1 -0
  148. package/cli/storybook-dist/assets/RBDrawer.stories-Du0oTvD1.js +14 -0
  149. package/cli/storybook-dist/assets/RBDropdownMenu.stories-Hsxve4PW.js +86 -0
  150. package/cli/storybook-dist/assets/RBEmptyView.stories-BlwHx3zp.js +12 -0
  151. package/cli/storybook-dist/assets/RBIconButton.stories-DAPDY63x.js +1 -0
  152. package/cli/storybook-dist/assets/RBIcons.stories-W5ZUV2L1.js +3 -0
  153. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-C99j0Y3g.js +1 -0
  154. package/cli/storybook-dist/assets/RBJoyStick.stories-Du0H1iAS.js +11 -0
  155. package/cli/storybook-dist/assets/RBLabelButton.stories-BlvHaU_Z.js +1 -0
  156. package/cli/storybook-dist/assets/RBLight.stories-BY6DKrre.js +1 -0
  157. package/cli/storybook-dist/assets/RBMenubar.stories-D2PEGpM9.js +3 -0
  158. package/cli/storybook-dist/assets/RBMultiRangeInput.stories-Bm2qSmjq.js +14 -0
  159. package/cli/storybook-dist/assets/RBPagination.stories-DDleJa5N.js +17 -0
  160. package/cli/storybook-dist/assets/RBPopover.stories-27YxPSFG.js +11 -0
  161. package/cli/storybook-dist/assets/RBPrompt.stories-BFaIOctr.js +20 -0
  162. package/cli/storybook-dist/assets/RBRadioBox.stories-BroDar0t.js +1 -0
  163. package/cli/storybook-dist/assets/RBRadioButton.stories-M3O-wX7V.js +11 -0
  164. package/cli/storybook-dist/assets/RBRangeDateInput.stories-CWlM_D5X.js +15 -0
  165. package/cli/storybook-dist/assets/RBResizable.stories-ui8X1qnc.js +20 -0
  166. package/cli/storybook-dist/assets/RBSelectBox.stories-C8T6cfA0.js +74 -0
  167. package/cli/storybook-dist/assets/RBSheet.stories-BCOo_Zcc.js +39 -0
  168. package/cli/storybook-dist/assets/RBSideNumberInput.stories-CU2_MVlJ.js +5 -0
  169. package/cli/storybook-dist/assets/RBSlider.stories-DgOp1TFc.js +5 -0
  170. package/cli/storybook-dist/assets/RBSpinner.stories-qLScY3zZ.js +14 -0
  171. package/cli/storybook-dist/assets/RBSwitch.stories-SXSY1QMb.js +65 -0
  172. package/cli/storybook-dist/assets/RBTab.stories-BzXXTsXP.js +1 -0
  173. package/cli/storybook-dist/assets/RBTable.stories-DHTeRNVk.js +62 -0
  174. package/cli/storybook-dist/assets/RBTableRow.stories-DRNRsd15.js +11 -0
  175. package/cli/storybook-dist/assets/RBTabs.stories-DP0noBLp.js +21 -0
  176. package/cli/storybook-dist/assets/RBTextArea.stories-TkaejfxJ.js +5 -0
  177. package/cli/storybook-dist/assets/RBTextField.stories-B6VpCGyt.js +45 -0
  178. package/cli/storybook-dist/assets/RBTextInput.stories-D3hZ08e-.js +5 -0
  179. package/cli/storybook-dist/assets/RBToast.stories-BQ2wS2U0.js +11 -0
  180. package/cli/storybook-dist/assets/RBToggle.stories-GVOjGqo3.js +12 -0
  181. package/cli/storybook-dist/assets/RBToggleGroup.stories-pzx0btOD.js +17 -0
  182. package/cli/storybook-dist/assets/RBTooltip.stories-jufB6fa1.js +9 -0
  183. package/cli/storybook-dist/assets/SideNumberInput-BYlmNHEw.js +1109 -0
  184. package/cli/storybook-dist/assets/SideNumberInput-SAkhydsn.css +21 -0
  185. package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
  186. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  187. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  188. package/cli/storybook-dist/assets/entry-preview-CAkU-e0D.js +2 -0
  189. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  190. package/cli/storybook-dist/assets/iframe-B-BmTsxV.js +211 -0
  191. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  192. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  193. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  194. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  195. package/cli/storybook-dist/assets/index-CkB0GfaI.js +575 -0
  196. package/cli/storybook-dist/assets/index-ClAc6FBW.js +240 -0
  197. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  198. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  199. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  200. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  201. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  202. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  203. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  204. package/cli/storybook-dist/assets/preview-BVJwlGUF.js +1 -0
  205. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  206. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  207. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  208. package/cli/storybook-dist/assets/preview-CznQwwbo.js +3 -0
  209. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  210. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  211. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  212. package/cli/storybook-dist/assets/preview-M77JRg1A.js +2 -0
  213. package/cli/storybook-dist/assets/preview-kTZPgP6D.css +1 -0
  214. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  215. package/cli/storybook-dist/assets/react-resizable-panels-HsFbxPbn.css +1 -0
  216. package/cli/storybook-dist/assets/react-resizable-panels.browser-DuxpZLkF.js +120 -0
  217. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  218. package/cli/storybook-dist/favicon.svg +1 -0
  219. package/cli/storybook-dist/iframe.html +666 -0
  220. package/cli/storybook-dist/index.html +190 -0
  221. package/cli/storybook-dist/index.json +1 -0
  222. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  223. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  224. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  225. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  226. package/cli/storybook-dist/project.json +1 -0
  227. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  228. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  229. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  230. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  231. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  232. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  233. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  234. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  235. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  236. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  237. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  238. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  239. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  240. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  241. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  242. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  243. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  244. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  245. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  246. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  247. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  248. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  249. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  250. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  251. package/cli/templates/common/README.md +1070 -83
  252. package/cli/templates/common/assets/icon.svg +44 -2
  253. package/cli/templates/common/manifest.json +14 -13
  254. package/cli/templates/common/package.json +11 -4
  255. package/cli/templates/common/py/main.py +125 -0
  256. package/cli/templates/common/requirements.txt +4 -3
  257. package/cli/templates/common/tsconfig.json +1 -1
  258. package/cli/templates/common/ui/content.tsx +216 -0
  259. package/cli/templates/common/ui/style.css +6 -0
  260. package/cli/templates/common/vite.config.ts +6 -25
  261. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  262. package/cli/templates/cpp/cpp/main.cpp +73 -0
  263. package/cli/templates/readme-cpp/README.md +1012 -0
  264. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +12 -1
  265. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +22 -0
  266. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  267. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +41 -0
  268. package/cli/vite.d.mts +25 -0
  269. package/cli/vite.mjs +292 -0
  270. package/package.json +34 -7
  271. package/cli/src/pack.mjs +0 -61
  272. package/cli/src/preview.mjs +0 -91
  273. package/cli/templates/common/main.py +0 -114
  274. package/cli/templates/common/src/content.tsx +0 -33
@@ -0,0 +1,107 @@
1
+ // Rules for AI coding agents working on a Muscat plugin. One source for both the MCP server's
2
+ // instructions (mcp.mjs) and the AGENTS.md / CLAUDE.md that `muscat-pk new` scaffolds, so the
3
+ // two never drift. Kept free of heavy imports: `new` loads it too.
4
+
5
+ // Native elements and the plugin-ui component to use instead.
6
+ export const NATIVE_TO_RB = [
7
+ ["select / dropdown", "RBSelectBox + Option (children)"],
8
+ ["input type=text", "RBTextInput (RBTextField for label + helper text)"],
9
+ ["input type=number", "RBSideNumberInput"],
10
+ ["input type=range", "RBSlider (RBMultiRangeInput for a range)"],
11
+ ["input type=checkbox", "RBCheckBox"],
12
+ ["input type=radio", "RBRadioButton / RBRadioBox"],
13
+ ["toggle / switch", "RBSwitch (RBSwitchField with a label)"],
14
+ ["input type=date", "RBDateInput / RBRangeDateInput"],
15
+ ["textarea", "RBTextArea"],
16
+ ["button", "RBLabelButton (text), RBIconButton (icon only)"],
17
+ ["label", "RBTextLabel"],
18
+ ["table", "RBTable + RBTableRow + RBTdColumn"],
19
+ ["dialog / modal", "RBDialog… / RBModal; rb.confirm() / rb.alert() / rb.prompt() for simple prompts"],
20
+ ["tabs", "RBTabs + RBTabsList + RBTabsTrigger + RBTabsContent"],
21
+ ["tooltip (title=)", "RBTooltip"],
22
+ ["hr", "RBDivider"],
23
+ ["loading spinner", "RBSpinner"],
24
+ ];
25
+
26
+ const UI_RULES = [
27
+ "Use plugin-ui components, never native form/control elements (<select>, <input>, <button>, <textarea>, <table>, <dialog>) or another UI library. Layout-only elements (<div>, <span>, <p>) are fine; prefer HStack/VStack for stacks.",
28
+ "Before writing a component, call get_component_example (working usage) or get_component_props (its type). Names are exact; when one is not found, use the suggestion returned, not a native element.",
29
+ "Colors and type come from tokens (bg-bg-*, text-fg-*, typo-*), not hex values or arbitrary Tailwind colors.",
30
+ ];
31
+
32
+ // plugin-ui 1.x replaced the remote-dom components and the connect() bootstrap. The project check
33
+ // (update, MCP check_project) flags these; the migration rules below tell an agent how to fix them.
34
+ export const LEGACY_SUBPATHS = {
35
+ connect: "removed — drop `connect(App)` and `export default App` instead (the host renders the entry's default export)",
36
+ elements: "removed — use the RB* components from @rainbow-robotics/plugin-ui/react",
37
+ "dist-browser": "removed — import from @rainbow-robotics/plugin-ui/react",
38
+ };
39
+ export const LEGACY_COMPONENTS = {
40
+ Slider: "RBSlider",
41
+ Button: "RBLabelButton",
42
+ IconButton: "RBIconButton",
43
+ Text: "a plain element or RBTextLabel",
44
+ TextInput: "RBTextInput / RBTextField",
45
+ Switch: "RBSwitch",
46
+ Select: "RBSelectBox",
47
+ };
48
+ const MIGRATION_RULES = [
49
+ "After `muscat-pk update`, or when the UI fails to build or the preview says \"no default export\", call check_project and fix every item it lists. Call it again until it reports no issues.",
50
+ "Each top-level ui/*.tsx is an entry and ends in `export default App`. The old `connect(App)` bootstrap and @rainbow-robotics/plugin-ui/connect are gone.",
51
+ "vite.config.ts uses the muscat() preset (`plugins: [muscat()]` from @rainbow-robotics/muscat-plugin-kit/vite); keep any other plugins the project adds. A hand-written config drops the entry's default export from the build.",
52
+ "rb.toast takes one object: rb.toast({ message, type }).",
53
+ `Old components → new: ${Object.entries(LEGACY_COMPONENTS).map(([o, n]) => `${o} → ${n}`).join(", ")}. Their props differ (no event.detail) — call get_component_example for the new component before rewriting.`,
54
+ ];
55
+
56
+ // Claude Code reads this to start the MCP server; `muscat-pk` hands off to the project's local kit.
57
+ export const MCP_JSON = `${JSON.stringify({ mcpServers: { muscat: { command: "muscat-pk", args: ["mcp"] } } }, null, 2)}\n`;
58
+
59
+ const nativeTable = () => NATIVE_TO_RB.map(([el, rb]) => `- ${el}: ${rb}`).join("\n");
60
+
61
+ export const SERVER_INSTRUCTIONS = `Muscat plugin UI (ui/*.tsx) is built only from @rainbow-robotics/plugin-ui/react, styled with the design-token Tailwind classes (list_design_tokens).
62
+
63
+ Rules for UI work:
64
+ ${UI_RULES.map((r) => `- ${r}`).join("\n")}
65
+
66
+ Native element -> plugin-ui component:
67
+ ${nativeTable()}
68
+
69
+ Upgrading a project made with an older kit:
70
+ ${MIGRATION_RULES.map((r) => `- ${r}`).join("\n")}
71
+
72
+ Backend, manifest and SDK questions: search_rb_sdk, list_rb_sdk, get_rb_sdk_api.`;
73
+
74
+ // AGENTS.md (Codex, Cursor, Gemini CLI, Copilot …). CLAUDE.md imports it with `@AGENTS.md`.
75
+ // The backend type is not named: `muscat-pk type change` would leave it stale.
76
+ export function agentsMd({ pluginName }) {
77
+ return `# ${pluginName} — Muscat plugin
78
+
79
+ A Muscat plugin: \`manifest.json\`, a React UI in \`ui/*.tsx\`, and a backend whose type is \`runtime\` in \`manifest.json\`.
80
+ \`README.md\` is the full plugin-author guide. The \`muscat\` MCP server (\`.mcp.json\`, \`muscat-pk mcp\`) looks up
81
+ components, design tokens and the SDK: use it instead of guessing names or props.
82
+
83
+ ## Commands
84
+
85
+ - \`muscat-pk dev\`: UI preview + backend with hot reload (\`dev:ui\` / \`dev:back\` for one side)
86
+ - \`muscat-pk build\`: validates \`manifest.json\`, builds \`dist/\` and the distributable ZIP
87
+
88
+ ## UI rules
89
+
90
+ UI is built only from \`@rainbow-robotics/plugin-ui/react\`, styled with design-token Tailwind classes.
91
+
92
+ ${UI_RULES.map((r) => `- ${r}`).join("\n")}
93
+
94
+ Native element → plugin-ui component:
95
+
96
+ ${nativeTable()}
97
+
98
+ ## Upgrading from an older kit
99
+
100
+ \`muscat-pk update\` bumps the kit, plugin-ui and preview-host and lists what the project must change.
101
+ The MCP tool \`check_project\` runs the same check (known old APIs + \`tsc --noEmit\`).
102
+
103
+ ${MIGRATION_RULES.map((r) => `- ${r}`).join("\n")}
104
+ `;
105
+ }
106
+
107
+ export const CLAUDE_MD = "@AGENTS.md\n";
package/cli/src/build.mjs CHANGED
@@ -1,5 +1,6 @@
1
- // muscat-pk build — bundle src/content.tsx into dist/content.js and stage
2
- // manifest.json and assets/ for distribution.
1
+ // muscat-pk build — bundle ui/content.tsx into dist/content.js, stage
2
+ // manifest.json and assets/ for distribution, and zip dist/ into a
3
+ // distributable ZIP.
3
4
  //
4
5
  // sandbox.html is generated by the CLI — plugin authors never manage it.
5
6
  // The import map always reflects the current plugin-ui entry set, so users
@@ -8,41 +9,143 @@
8
9
  // The plugin author's `@rainbow-robotics/plugin-ui` and React imports are left external.
9
10
  // Muscat/robot and preview-host provide those modules through a sandbox import
10
11
  // map, so plugin ZIPs do not carry the UI runtime.
11
- import { cpSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
12
- import { resolve } from "node:path";
12
+ import { chmodSync, cpSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
13
+ import { createRequire } from "node:module";
14
+ import { basename, dirname, resolve } from "node:path";
15
+ import { createInterface } from "node:readline/promises";
13
16
  import { spawnSync } from "node:child_process";
14
- import { buildWithFallback } from "./esbuild.mjs";
15
- import { findPluginRoot, log } from "./util.mjs";
17
+ import { ensureDocker, findPluginRoot, hasBin, humanSize, log, parseArgs, prompt } from "./util.mjs";
18
+ import { assertManifest, manifestRuntime } from "./manifest.mjs";
19
+ import { writeZip } from "./zip.mjs";
16
20
 
17
- function isMainBranch() {
18
- const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
19
- const branch = r.stdout?.trim();
20
- return branch === "main" || branch === "master";
21
+ // Find a usable JS runtime for running vite as a subprocess.
22
+ // In binary (Bun) mode, try `bun` first (it can run vite directly), then node.
23
+ // In node mode, use the current process.
24
+ function findJsRuntime() {
25
+ if (typeof Bun === "undefined") return { bin: process.execPath, isBun: false };
26
+ for (const c of ["bun", "node", "nodejs"]) {
27
+ if (hasBin(c)) return { bin: c, isBun: c === "bun" };
28
+ }
29
+ return null;
21
30
  }
22
31
 
23
- function manifestRuntime(root) {
32
+ // Resolve vite bin path from the plugin root, hoisting-aware.
33
+ function findViteBin(pluginRoot) {
24
34
  try {
25
- return JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.type ?? "python";
35
+ const req = createRequire(resolve(pluginRoot, "package.json"));
36
+ const vitePkg = req.resolve("vite/package.json");
37
+ return resolve(dirname(vitePkg), "bin", "vite.js");
26
38
  } catch {
27
- return "python";
39
+ return null;
40
+ }
41
+ }
42
+
43
+ // Run vite build in the plugin project. Without a JS runtime (binary CLI, no Node.js) it asks
44
+ // whether to build the plugin without its UI and returns false on yes; throws on no or if vite is missing.
45
+ async function runViteBuild(pluginRoot, { mode = "production" } = {}) {
46
+ const runtime = findJsRuntime();
47
+ if (!runtime) {
48
+ log.warn("Node.js 20+ (or Bun) not found — the UI cannot be built and will not be in the plugin.");
49
+ const answer = await prompt(" Build the plugin without its UI? [y/N]: ", "n");
50
+ if (answer.toLowerCase() !== "y")
51
+ throw new Error("build cancelled. Install Node.js 20+ (https://nodejs.org), run `npm install` in the plugin, then build again.");
52
+ return false;
53
+ }
54
+ const viteBin = findViteBin(pluginRoot);
55
+ if (!viteBin || !existsSync(viteBin)) {
56
+ throw new Error(
57
+ "vite not found in this plugin's node_modules.\n" +
58
+ ` Run: npm install (inside ${pluginRoot})`,
59
+ );
28
60
  }
61
+
62
+ log.step(`vite build (mode: ${mode})`);
63
+ // bun can run vite directly; node needs the explicit bin path
64
+ const args = runtime.isBun ? ["run", viteBin, "build", "--mode", mode]
65
+ : [viteBin, "build", "--mode", mode];
66
+ const r = spawnSync(runtime.bin, args, { cwd: pluginRoot, stdio: "inherit" });
67
+ if (r.status !== 0) throw new Error("vite build failed");
68
+ return true;
69
+ }
70
+
71
+ // function isMainBranch() {
72
+ // const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
73
+ // const branch = r.stdout?.trim();
74
+ // return branch === "main" || branch === "master";
75
+ // }
76
+
77
+ // For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12 container.
78
+ // rclpy: arch-agnostic (pure Python) → dist/ros2_ws/install/ (built once)
79
+ // rclcpp: arch-specific C++ binaries → dist/ros2_ws/install-{arch}/ per arch
80
+ // Cross-arch C++ builds run inside an emulated container. Docker Desktop ships the
81
+ // emulation; native Linux needs binfmt/QEMU — check up front so the error says so.
82
+ function assertCanBuildArch(arch, dockerPlatform) {
83
+ const info = spawnSync("docker", ["info", "--format", "{{.Architecture}}"], { encoding: "utf8" });
84
+ const host = { x86_64: "amd64", aarch64: "arm64", arm64: "arm64" }[(info.stdout || "").trim()] ?? "";
85
+ if (arch === host) return;
86
+ const r = spawnSync("docker", ["run", "--rm", "--platform", dockerPlatform, "ubuntu:24.04", "true"], { stdio: "ignore" });
87
+ if (r.status === 0) return;
88
+ throw new Error(
89
+ `can't build ${arch} on this ${host || "machine"}: Docker has no ${arch} emulation.\n` +
90
+ ` enable it once: docker run --privileged --rm tonistiigi/binfmt --install ${arch}\n` +
91
+ ` or build only this machine's arch: muscat-pk build --arch ${host || "<arch>"}`,
92
+ );
29
93
  }
30
94
 
31
- // For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12
32
- // container so the shipped ZIP carries ros2_ws/install. Real (non-symlink) build
33
- // → relocatable on the robot. Skipped (with a warning) if Docker is unavailable.
34
- export async function exportRos2Build(root) {
95
+ export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
35
96
  if (manifestRuntime(root) !== "ros2") return;
36
- const { assertDockerReady, ensureImage, colconBuild } = await import("./ros2.mjs");
37
- try {
38
- assertDockerReady();
39
- } catch (e) {
40
- log.warn(`ros2 build skipped: ${e.message}`);
41
- return;
97
+ const { assertDockerReady, ensureImage, colconBuild, ROS2_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./ros2.mjs");
98
+ assertDockerReady();
99
+
100
+ const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
101
+ if (manifest.runtime?.rosClient === "rclcpp") {
102
+ // C++ ROS2: compile per arch so each robot picks the right install-{arch}/
103
+ for (const arch of archs) {
104
+ const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
105
+ if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
106
+ assertCanBuildArch(arch, dockerPlatform);
107
+ await ensureImage(`${ROS2_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
108
+ colconBuild(root, { symlink: false, arch });
109
+ log.ok(`dist/ros2_ws/install-${arch}/ ready`);
110
+ }
111
+ } else {
112
+ // rclpy / pure Python: arch-agnostic, build once
113
+ await ensureImage();
114
+ colconBuild(root, { symlink: false });
115
+ log.ok("dist/ros2_ws/install/ ready");
116
+ }
117
+ }
118
+
119
+ // For cpp plugins, compile cpp/ inside the Ubuntu container (Release) per arch
120
+ // and copy each binary into dist/{arch}/{entry} so the ZIP carries relocatable
121
+ // robot-compatible executables.
122
+ export async function exportCppBuild(root, distDir, archs = ["amd64", "arm64"]) {
123
+ if (manifestRuntime(root) !== "cpp") return;
124
+ const { assertDockerReady, ensureImage, cmakeBuild, CPP_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./cpp.mjs");
125
+ assertDockerReady();
126
+
127
+ const entry = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime?.entry;
128
+ if (!entry) throw new Error("manifest.json runtime.entry is required for cpp (the built executable name)");
129
+
130
+ for (const arch of archs) {
131
+ const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
132
+ if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
133
+ assertCanBuildArch(arch, dockerPlatform);
134
+ await ensureImage(`${CPP_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
135
+ cmakeBuild(root, { buildType: "Release", arch });
136
+ const built = resolve(root, `cpp/build-${arch}`, entry);
137
+ if (!existsSync(built)) {
138
+ throw new Error(`cpp/build-${arch}/${entry} not found — the CMake target name must equal runtime.entry`);
139
+ }
140
+ const archDir = resolve(distDir, arch);
141
+ mkdirSync(archDir, { recursive: true });
142
+ const dest = resolve(archDir, entry);
143
+ copyFileSync(built, dest);
144
+ chmodSync(dest, 0o755);
145
+ const libDir = resolve(root, `cpp/build-${arch}`, "lib");
146
+ if (existsSync(libDir)) cpSync(libDir, resolve(archDir, "lib"), { recursive: true, verbatimSymlinks: true });
147
+ log.ok(`cpp binary → dist/${arch}/${entry}${existsSync(libDir) ? " (+ lib/)" : ""}`);
42
148
  }
43
- ensureImage();
44
- colconBuild(root, { symlink: false });
45
- log.ok("ros2_ws/install ready (will be included in the ZIP)");
46
149
  }
47
150
 
48
151
  const PRERELEASE_RE = /\.(dev|a|b|rc|alpha|beta)\d*/i;
@@ -55,7 +158,7 @@ function isPrerelease(version) {
55
158
  // stableOnly=true → skips pre-release (dev/a/b/rc) versions.
56
159
  async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
57
160
  const res = await fetch(`https://pypi.org/pypi/${pkg}/json`, {
58
- signal: AbortSignal.timeout(5000),
161
+ signal: new AbortController().signal.timeout(5000),
59
162
  });
60
163
  if (!res.ok) throw new Error(`pypi ${pkg}: ${res.status}`);
61
164
  const data = await res.json();
@@ -79,7 +182,7 @@ async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
79
182
  }
80
183
 
81
184
  // Pin unpinned (no version specifier) entries in requirements.txt to the
82
- // latest PyPI release. Runs on explicit `build`/`pack` only — keeps the shipped
185
+ // latest PyPI release. Runs on explicit `build` only — keeps the shipped
83
186
  // plugin reproducible without churning the source file on every dev rebuild.
84
187
  // stableOnly=true is used on main-branch builds to avoid shipping dev versions.
85
188
  export async function pinRequirements(root, { stableOnly = false } = {}) {
@@ -109,90 +212,227 @@ export async function pinRequirements(root, { stableOnly = false } = {}) {
109
212
  if (pinned > 0) writeFileSync(reqPath, out.join("\n"));
110
213
  }
111
214
 
112
- const SANDBOX_HTML = `<!doctype html>
113
- <html lang="en">
114
- <head>
115
- <meta charset="UTF-8" />
116
- <title>plugin sandbox</title>
117
- <script type="importmap">
118
- {
119
- "imports": {
120
- "@rainbow-robotics/plugin-ui/connect": "/plugin-runtime/@rainbow-robotics/plugin-ui/connect.js",
121
- "@rainbow-robotics/plugin-ui/react": "/plugin-runtime/@rainbow-robotics/plugin-ui/react.js",
122
- "@rainbow-robotics/plugin-ui/sdk": "/plugin-runtime/@rainbow-robotics/plugin-ui/sdk.js",
123
- "@rainbow-robotics/plugin-ui/elements": "/plugin-runtime/@rainbow-robotics/plugin-ui/elements.js",
124
- "react": "/plugin-runtime/vendor/react.js",
125
- "react/jsx-runtime": "/plugin-runtime/vendor/react-jsx-runtime.js",
126
- "react/jsx-dev-runtime": "/plugin-runtime/vendor/react-jsx-dev-runtime.js",
127
- "react-dom/client": "/plugin-runtime/vendor/react-dom-client.js"
128
- }
129
- }
130
- </script>
131
- </head>
132
- <body>
133
- <script type="module" src="./content.js"></script>
134
- </body>
135
- </html>
136
- `;
137
-
138
- const HOST_PROVIDED_EXTERNALS = [
139
- "@rainbow-robotics/plugin-ui",
140
- "@rainbow-robotics/plugin-ui/*",
141
- "react",
142
- "react/jsx-runtime",
143
- "react/jsx-dev-runtime",
144
- "react-dom",
145
- "react-dom/*",
146
- ];
147
-
148
215
  export async function buildPlugin({ root, minify = false } = {}) {
149
216
  const pluginRoot = root ?? findPluginRoot();
150
- const entry = resolve(pluginRoot, "src/content.tsx");
151
- if (!existsSync(entry)) {
152
- throw new Error(`missing entry: ${entry}`);
153
- }
217
+ assertManifest(pluginRoot);
154
218
  const distDir = resolve(pluginRoot, "dist");
155
- rmSync(distDir, { recursive: true, force: true });
219
+ try {
220
+ rmSync(distDir, { recursive: true, force: true });
221
+ } catch (err) {
222
+ if (err?.code !== "EACCES" && err?.code !== "EPERM") throw err;
223
+ // 이전 버전이 컨테이너를 root로 돌려 남긴 dist/ — 한 번만 정리하면 된다
224
+ throw new Error(
225
+ `Permission denied when deleting dist/ (${err.path ?? distDir}).\n` +
226
+ `The previous build left files owned by root. Please clean it up once:\n` +
227
+ ` sudo rm -rf ${distDir}`,
228
+ );
229
+ }
156
230
  mkdirSync(distDir, { recursive: true });
157
231
 
158
- log.step("bundling src/content.tsx → dist/content.js");
159
- await buildWithFallback({
160
- entryPoints: [entry],
161
- outfile: resolve(distDir, "content.js"),
162
- bundle: true,
163
- format: "esm",
164
- jsx: "automatic",
165
- minify,
166
- loader: { ".tsx": "tsx", ".ts": "ts", ".svg": "dataurl" },
167
- define: { "process.env.NODE_ENV": JSON.stringify(minify ? "production" : "development") },
168
- external: HOST_PROVIDED_EXTERNALS,
169
- logLevel: "warning",
170
- });
232
+ const manifest = JSON.parse(readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"));
233
+ const uiDir = resolve(pluginRoot, "ui");
234
+ const declaredUiEntries = [...new Set(
235
+ (manifest.contributes ?? []).map((c) => c.ui).filter(Boolean)
236
+ )];
237
+ // Validate each declared UI entry has a matching source file
238
+ for (const entry of declaredUiEntries) {
239
+ const exists = [".tsx", ".ts", ".jsx", ".js"].some(
240
+ (ext) => existsSync(resolve(uiDir, entry + ext))
241
+ );
242
+ if (!exists)
243
+ throw new Error(
244
+ `contributes declares ui "${entry}" but ui/${entry}.tsx (or .ts/.jsx/.js) does not exist.`
245
+ );
246
+ }
247
+ const hasUiFiles = existsSync(uiDir) && readdirSync(uiDir).some((f) => /\.[tj]sx?$/.test(f));
248
+ // A backend-only plugin (no contributes) ships no UI, so leftover ui/ files are not built either.
249
+ let uiBuilt = true;
250
+ if (declaredUiEntries.length > 0 || (hasUiFiles && manifest.contributes?.length > 0)) {
251
+ uiBuilt = await runViteBuild(pluginRoot, { mode: minify ? "production" : "development" });
252
+ }
171
253
 
172
254
  // Stage distribution files.
173
255
  const manifestSrc = resolve(pluginRoot, "manifest.json");
174
256
  if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(distDir, "manifest.json"));
257
+ // Built without the UI: ship it as a backend-only plugin so the host lists no screen it cannot load.
258
+ if (!uiBuilt) {
259
+ const { contributes, ...backendOnly } = manifest;
260
+ writeFileSync(resolve(distDir, "manifest.json"), JSON.stringify(backendOnly, null, 2) + "\n");
261
+ log.warn(`packaged without UI: dropped ${contributes.length} screen area(s) from dist/manifest.json`);
262
+ }
263
+
264
+ // No sandbox.html: the host dynamically imports dist/{ui}.js directly as a
265
+ // plain ESM module (see @rainbow-robotics/plugin-ui/host) — no iframe.
175
266
 
176
- // Generate sandbox.html — plugin authors don't manage this file.
177
- writeFileSync(resolve(distDir, "sandbox.html"), SANDBOX_HTML, "utf8");
178
- log.step("generated dist/sandbox.html");
179
267
  const assetsSrc = resolve(pluginRoot, "assets");
180
268
  if (existsSync(assetsSrc)) {
181
269
  cpSync(assetsSrc, resolve(distDir, "assets"), { recursive: true });
182
270
  }
183
271
 
272
+ // README.md, README.[lang].md, CHANGELOG.md → dist/
273
+ for (const f of readdirSync(pluginRoot)) {
274
+ if (/^README(\.[a-z]{2}(-[A-Z]{2})?)?\.md$/i.test(f) || /^CHANGELOG\.md$/i.test(f)) {
275
+ copyFileSync(resolve(pluginRoot, f), resolve(distDir, f));
276
+ }
277
+ }
278
+
279
+ // Backend runtime files — copy entire py/ into dist/, then requirements.txt from root.
280
+ const pySrc = resolve(pluginRoot, "py");
281
+ if (existsSync(pySrc)) {
282
+ cpSync(pySrc, distDir, { recursive: true });
283
+ }
284
+ const rootReqs = resolve(pluginRoot, "requirements.txt");
285
+ if (existsSync(rootReqs)) {
286
+ copyFileSync(rootReqs, resolve(distDir, "requirements.txt"));
287
+ }
288
+
184
289
  log.ok(`built → ${distDir}`);
185
- return { pluginRoot, distDir };
290
+ return { pluginRoot, distDir, uiBuilt };
291
+ }
292
+
293
+ function dirSize(dir, skip = []) {
294
+ if (!existsSync(dir)) return 0;
295
+ let total = 0;
296
+ for (const entry of readdirSync(dir)) {
297
+ if (skip.includes(entry)) continue;
298
+ const full = resolve(dir, entry);
299
+ const st = statSync(full);
300
+ total += st.isDirectory() ? dirSize(full) : st.size;
301
+ }
302
+ return total;
303
+ }
304
+
305
+ // Read the uncompressed byte total of a .whl (zip) file by parsing its central
306
+ // directory — no external library needed, no actual extraction.
307
+
308
+ // installedSize = "이 플러그인 하나를 지우면 되찾는 용량"
309
+ // = dist/(wheels/ 제외)
310
+ // ros2_ws/install* 은 이제 dist/ 안에 있으므로 별도 합산 불필요.
311
+ // 휠은 로봇 공유 스토어(store/py/)에 언팩되므로 포함하지 않는다.
312
+ function calcInstalledSize(pluginRoot) {
313
+ const distDir = resolve(pluginRoot, "dist");
314
+
315
+ const installedSize = dirSize(distDir, ["wheels"]);
316
+
317
+ // Write to root manifest.json
318
+ const rootManifestPath = resolve(pluginRoot, "manifest.json");
319
+ if (existsSync(rootManifestPath)) {
320
+ const m = JSON.parse(readFileSync(rootManifestPath, "utf8"));
321
+ m.installedSize = installedSize;
322
+ writeFileSync(rootManifestPath, JSON.stringify(m, null, 2) + "\n");
323
+ }
324
+
325
+ // Re-stage dist/manifest.json so the ZIP carries the updated value
326
+ const distManifestPath = resolve(distDir, "manifest.json");
327
+ if (existsSync(distManifestPath)) {
328
+ const m = JSON.parse(readFileSync(distManifestPath, "utf8"));
329
+ m.installedSize = installedSize;
330
+ writeFileSync(distManifestPath, JSON.stringify(m, null, 2) + "\n");
331
+ }
332
+
333
+ log.ok(`installedSize → ${humanSize(installedSize)}`);
334
+ }
335
+
336
+ // Zip dist/ into a distributable ZIP with a size report.
337
+ // ros2_ws/install* is now inside dist/, so only "dist" is needed.
338
+ function packZip(pluginRoot) {
339
+ const manifest = JSON.parse(readFileSync(resolve(pluginRoot, "manifest.json"), "utf8"));
340
+ const zipName = `${basename(pluginRoot)}-${manifest.version || "0.0.0"}.zip`;
341
+ const zipPath = resolve(pluginRoot, zipName);
342
+ rmSync(zipPath, { force: true });
343
+
344
+ const entries = ["dist"];
345
+
346
+ log.step(`packing ${entries.join(", ")} → ${zipName}`);
347
+ // In-process, not the `zip` CLI: Windows has none, and it keeps the robot's executable bits (zip.mjs).
348
+ writeZip(zipPath, entries, pluginRoot);
349
+
350
+ const zipBytes = statSync(zipPath).size;
351
+ log.ok(`${zipName} ${humanSize(zipBytes)}`);
352
+ for (const e of entries) {
353
+ const full = resolve(pluginRoot, e);
354
+ const size = statSync(full).isDirectory() ? dirSize(full) : statSync(full).size;
355
+ log.info(` ├─ ${e} ${humanSize(size)}`);
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Ask which target arch(es) to build for.
361
+ * Non-TTY (CI): both, same as the prompt default.
362
+ * @returns {Promise<string[]>} e.g. ['amd64'] | ['arm64'] | ['amd64','arm64']
363
+ */
364
+ async function promptArch(args = []) {
365
+ const archFlag = parseArgs(args).arch ?? null;
366
+ if (archFlag) {
367
+ if (archFlag === "both") return ["amd64", "arm64"];
368
+ if (archFlag === "amd64" || archFlag === "x86_64") return ["amd64"];
369
+ if (archFlag === "arm64" || archFlag === "aarch64") return ["arm64"];
370
+ throw new Error(`unknown --arch value '${archFlag}' (valid: amd64, arm64, both)`);
371
+ }
372
+ // Same default as the prompt: the target is the robot, not this machine
373
+ // (node's os.arch() is x64 under Rosetta on an arm64 Mac anyway).
374
+ if (!process.stdin.isTTY) {
375
+ log.info("arch auto-selected (non-TTY): both");
376
+ return ["amd64", "arm64"];
377
+ }
378
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
379
+ process.stdout.write(
380
+ "\n Target arch:\n" +
381
+ " 1) amd64\n" +
382
+ " 2) arm64\n" +
383
+ " 3) Both (fat ZIP)\n",
384
+ );
385
+ const answer = (await rl.question(" Select [1/2/3, default: 3]: ")).trim() || "3";
386
+ rl.close();
387
+ if (answer === "1") return ["amd64"];
388
+ if (answer === "2") return ["arm64"];
389
+ return ["amd64", "arm64"];
390
+ }
391
+
392
+ async function promptVersion(root) {
393
+ if (!process.stdin.isTTY) return;
394
+ const manifestPath = resolve(root, "manifest.json");
395
+ if (!existsSync(manifestPath)) return;
396
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
397
+ const current = manifest.version ?? "1.0.0";
398
+
399
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
400
+ const input = (await rl.question(` Version (current: ${current}, leave blank to keep): `)).trim();
401
+ rl.close();
402
+
403
+ if (!input) return;
404
+ manifest.version = input;
405
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
406
+ log.ok(`manifest.json version → ${input}`);
186
407
  }
187
408
 
188
409
  export async function runBuild(args) {
189
- const minify = args.includes("--minify");
190
410
  const root = findPluginRoot();
191
- if (!args.includes("--no-pin")) {
192
- const stableOnly = isMainBranch();
193
- log.step(`pinning requirements.txt to latest ${stableOnly ? "stable " : ""}PyPI versions`);
194
- await pinRequirements(root, { stableOnly });
411
+ const rt = manifestRuntime(root);
412
+
413
+ // All runtimes except "none" need Docker
414
+ if (rt !== "none") ensureDocker();
415
+
416
+ await promptVersion(root);
417
+ const archs = await promptArch(args);
418
+ // Drop a previous ZIP of this version so a failed build can't leave a stale one to upload.
419
+ const version = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).version || "0.0.0";
420
+ rmSync(resolve(root, `${basename(root)}-${version}.zip`), { force: true });
421
+
422
+ const { pluginRoot, distDir } = await buildPlugin({ root, minify: true });
423
+
424
+ // python / rclpy: lock + wheel vendor in Ubuntu 24.04 container
425
+ const manifest = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8")).runtime ?? {};
426
+ const needsLock =
427
+ manifest.type === "python" ||
428
+ (manifest.type === "ros2" && manifest.rosClient === "rclpy");
429
+ if (needsLock) {
430
+ const { lockAndVendor } = await import("./python.mjs");
431
+ await lockAndVendor(pluginRoot, archs);
195
432
  }
196
- await buildPlugin({ root, minify });
197
- await exportRos2Build(root);
433
+
434
+ await exportRos2Build(root, archs);
435
+ await exportCppBuild(root, distDir, archs);
436
+ calcInstalledSize(pluginRoot);
437
+ packZip(pluginRoot);
198
438
  }
@@ -1,28 +1,13 @@
1
- import { execFileSync } from "node:child_process";
2
- import { readdirSync, statSync } from "node:fs";
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
7
- const roots = [resolve(root, "cli"), resolve(root, "preview-host")];
8
-
9
- function* mjsFiles(dir) {
10
- for (const entry of readdirSync(dir)) {
11
- const full = resolve(dir, entry);
12
- const stat = statSync(full);
13
- if (stat.isDirectory()) {
14
- if (entry === "dist" || entry === "runtime" || entry === "templates") {
15
- continue;
16
- }
17
- yield* mjsFiles(full);
18
- } else if (entry.endsWith(".mjs")) {
19
- yield full;
20
- }
21
- }
22
- }
23
-
24
- for (const dir of roots) {
25
- for (const file of mjsFiles(dir)) {
26
- execFileSync(process.execPath, ["--check", file], { stdio: "inherit" });
27
- }
7
+ for (const dir of ["cli", "preview-host"]) {
8
+ if (!existsSync(resolve(root, dir))) continue;
9
+ execSync(
10
+ `find ${dir} -name '*.mjs' ! -path '*/dist/*' ! -path '*/runtime/*' ! -path '*/templates/*' -exec node --check {} +`,
11
+ { cwd: root, stdio: "inherit" },
12
+ );
28
13
  }