@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.5 → 0.1.2-dev.50

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 (269) hide show
  1. package/README.md +82 -49
  2. package/cli/README.md +191 -5
  3. package/cli/bin/create-muscat-plugin.mjs +1 -1
  4. package/cli/bin/muscat-pk.mjs +25 -10
  5. package/cli/bundle-templates.mjs +28 -0
  6. package/cli/docker/Dockerfile.bridge +17 -0
  7. package/cli/docker/Dockerfile.cpp +22 -0
  8. package/cli/docker/Dockerfile.dev +1 -2
  9. package/cli/docker/Dockerfile.python +20 -0
  10. package/cli/docker/bridge-package.json +8 -0
  11. package/cli/prepack.mjs +10 -0
  12. package/cli/src/bridge.mjs +90 -0
  13. package/cli/src/build.mjs +377 -85
  14. package/cli/src/check-package.mjs +8 -23
  15. package/cli/src/cpp.mjs +61 -0
  16. package/cli/src/dev.mjs +40 -4
  17. package/cli/src/devBack.mjs +274 -49
  18. package/cli/src/devUi.mjs +198 -19
  19. package/cli/src/esbuild.mjs +27 -10
  20. package/cli/src/manifest.mjs +208 -0
  21. package/cli/src/mcp.mjs +592 -0
  22. package/cli/src/new.mjs +143 -43
  23. package/cli/src/pluginDevServer.mjs +112 -0
  24. package/cli/src/ports.mjs +160 -0
  25. package/cli/src/python.mjs +109 -0
  26. package/cli/src/ros2.mjs +43 -18
  27. package/cli/src/sdk.mjs +11 -9
  28. package/cli/src/socketBridge.mjs +129 -0
  29. package/cli/src/storybook.mjs +168 -0
  30. package/cli/src/templates-embedded.mjs +3 -0
  31. package/cli/src/type.mjs +52 -29
  32. package/cli/src/uninstall.mjs +98 -0
  33. package/cli/src/update.mjs +259 -30
  34. package/cli/src/util.mjs +114 -7
  35. package/cli/storybook-dist/_design-tokens.css +4957 -0
  36. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  37. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  38. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  39. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  40. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  41. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  42. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  43. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  44. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  45. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  46. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  47. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  48. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +44 -0
  49. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  50. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  51. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  52. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  53. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  54. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  55. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  56. package/cli/storybook-dist/_stories-src/RBIcons/RBMenuTreeIcons.stories.tsx +108 -0
  57. package/cli/storybook-dist/_stories-src/RBIcons/RBProgramIcons.stories.tsx +116 -0
  58. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  59. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  60. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  61. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  62. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +119 -0
  63. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  64. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  65. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  66. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  67. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  68. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  69. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  70. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  71. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  72. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  73. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  74. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  75. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  76. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  77. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  78. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  79. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  84. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  85. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  86. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  87. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  88. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  89. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  90. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  91. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  92. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  93. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  94. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  95. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  96. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  97. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  98. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  99. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  100. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  101. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  102. package/cli/storybook-dist/assets/Color-YHDXOIA2-BjuzF3Je.js +1 -0
  103. package/cli/storybook-dist/assets/Docs-BAFZYTMR.js +12 -0
  104. package/cli/storybook-dist/assets/Docs-BQesYW-Q.js +8 -0
  105. package/cli/storybook-dist/assets/Docs-BsMEe093.js +7 -0
  106. package/cli/storybook-dist/assets/Docs-By6k6vcM.js +20 -0
  107. package/cli/storybook-dist/assets/Docs-D1J63cJc.js +7 -0
  108. package/cli/storybook-dist/assets/Docs-GoNbntAU.js +8 -0
  109. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-BwY_WiM7.js +2 -0
  110. package/cli/storybook-dist/assets/NxAvatar.stories-CvzBHfYa.js +13 -0
  111. package/cli/storybook-dist/assets/NxButton.stories-B83PesgN.js +20 -0
  112. package/cli/storybook-dist/assets/NxIcons.stories-Ca7qpAaO.js +3 -0
  113. package/cli/storybook-dist/assets/NxNumberInput.stories-DpNcCG6B.js +26 -0
  114. package/cli/storybook-dist/assets/NxPagination.stories-Xnvd3xhH.js +11 -0
  115. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-C3YSIpM0.js +14 -0
  116. package/cli/storybook-dist/assets/NxRangeInput.stories-DIhaNZig.js +17 -0
  117. package/cli/storybook-dist/assets/NxSelectBox.stories-BGwO2mYJ.js +17 -0
  118. package/cli/storybook-dist/assets/NxSlideRange.stories-BXlt5Bgt.js +14 -0
  119. package/cli/storybook-dist/assets/NxSwitch.stories-C5ZqIRtw.js +11 -0
  120. package/cli/storybook-dist/assets/NxTextArea.stories-hm_ycALo.js +11 -0
  121. package/cli/storybook-dist/assets/NxTextInput.stories-DkZAySr_.js +11 -0
  122. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  123. package/cli/storybook-dist/assets/RBAccordion.stories--bY36GZY.js +17 -0
  124. package/cli/storybook-dist/assets/RBAccordionGroup.stories-BVr1ccI4.js +33 -0
  125. package/cli/storybook-dist/assets/RBAlert.stories-CkjFBEkv.js +17 -0
  126. package/cli/storybook-dist/assets/RBBadge.stories-CNXILDPh.js +1 -0
  127. package/cli/storybook-dist/assets/RBBattery.stories-AokMv8ZY.js +10 -0
  128. package/cli/storybook-dist/assets/RBCard.stories-CX1sLKB2.js +45 -0
  129. package/cli/storybook-dist/assets/RBCheckBox.stories-By3QHBFm.js +1 -0
  130. package/cli/storybook-dist/assets/RBCheckIconButton.stories-Dud1uK3X.js +1 -0
  131. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-C8iBkZxY.js +24 -0
  132. package/cli/storybook-dist/assets/RBChipTab.stories-DsgbiS2U.js +21 -0
  133. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-C3DZGmq0.js +1 -0
  134. package/cli/storybook-dist/assets/RBColorIcons.stories-CusR20qk.js +3 -0
  135. package/cli/storybook-dist/assets/RBCommand.stories-DhJNgaLZ.js +73 -0
  136. package/cli/storybook-dist/assets/RBConfirm.stories-B6LKpCDa.js +20 -0
  137. package/cli/storybook-dist/assets/RBDateInput.stories-PEEhQ1wJ.js +15 -0
  138. package/cli/storybook-dist/assets/RBDialog.stories-CAtOdRen.js +38 -0
  139. package/cli/storybook-dist/assets/RBDivider.stories-0zpIUlXT.js +1 -0
  140. package/cli/storybook-dist/assets/RBDrawer.stories-CWMG4glY.js +14 -0
  141. package/cli/storybook-dist/assets/RBDropdownMenu.stories-6fbEYZfk.js +86 -0
  142. package/cli/storybook-dist/assets/RBEmptyView.stories-BRDZHjn4.js +12 -0
  143. package/cli/storybook-dist/assets/RBIconButton.stories-sYrRLYob.js +1 -0
  144. package/cli/storybook-dist/assets/RBIcons.stories-DFe8O1_g.js +3 -0
  145. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-DX4VdUfk.js +1 -0
  146. package/cli/storybook-dist/assets/RBJoyStick.stories-SA2Foelf.js +11 -0
  147. package/cli/storybook-dist/assets/RBLabelButton.stories-CicBns_Y.js +1 -0
  148. package/cli/storybook-dist/assets/RBLight.stories-WlP3oEmY.js +1 -0
  149. package/cli/storybook-dist/assets/RBMenuTreeIcons.stories-CrWjPfxj.js +3 -0
  150. package/cli/storybook-dist/assets/RBMenubar.stories-Di4phWw7.js +3 -0
  151. package/cli/storybook-dist/assets/RBMultiRangeInput.stories-DS3wrR3l.js +14 -0
  152. package/cli/storybook-dist/assets/RBPagination.stories-BDVPAUVD.js +17 -0
  153. package/cli/storybook-dist/assets/RBPopover.stories-DL9b_iX9.js +11 -0
  154. package/cli/storybook-dist/assets/RBProgramIcons.stories-ByRVx-on.js +3 -0
  155. package/cli/storybook-dist/assets/RBPrompt.stories-vTmKWyA5.js +20 -0
  156. package/cli/storybook-dist/assets/RBRadioBox.stories-C9WHt4jb.js +1 -0
  157. package/cli/storybook-dist/assets/RBRadioButton.stories-D8KYq63j.js +11 -0
  158. package/cli/storybook-dist/assets/RBRangeDateInput.stories-Bvcf4Su7.js +15 -0
  159. package/cli/storybook-dist/assets/RBResizable.stories-3O2OMuH_.js +20 -0
  160. package/cli/storybook-dist/assets/RBSelectBox.stories-CKZLmPzX.js +6 -0
  161. package/cli/storybook-dist/assets/RBSheet.stories-Dko0It1W.js +39 -0
  162. package/cli/storybook-dist/assets/RBSideNumberInput.stories-CUvqVtId.js +5 -0
  163. package/cli/storybook-dist/assets/RBSlider.stories-CcbaKRYo.js +5 -0
  164. package/cli/storybook-dist/assets/RBSpinner.stories-D15e_JlE.js +14 -0
  165. package/cli/storybook-dist/assets/RBSwitch.stories-C4JRsYJ6.js +65 -0
  166. package/cli/storybook-dist/assets/RBTab.stories-BPvzQSM-.js +1 -0
  167. package/cli/storybook-dist/assets/RBTable.stories-nr8OS1mV.js +62 -0
  168. package/cli/storybook-dist/assets/RBTableRow.stories-rI0rK8Ci.js +11 -0
  169. package/cli/storybook-dist/assets/RBTabs.stories-CxMtHGWA.js +21 -0
  170. package/cli/storybook-dist/assets/RBTextArea.stories-Bru4AhJ3.js +5 -0
  171. package/cli/storybook-dist/assets/RBTextField.stories-Ah3D9lto.js +45 -0
  172. package/cli/storybook-dist/assets/RBTextInput.stories-C3qAVngJ.js +5 -0
  173. package/cli/storybook-dist/assets/RBToast.stories-C1occsvf.js +11 -0
  174. package/cli/storybook-dist/assets/RBToggle.stories-BNrNERc7.js +12 -0
  175. package/cli/storybook-dist/assets/RBToggleGroup.stories-D_RRBwxY.js +17 -0
  176. package/cli/storybook-dist/assets/RBTooltip.stories-DuyOQqrB.js +9 -0
  177. package/cli/storybook-dist/assets/RainbowUiProvider-BYlbsa69.css +1 -0
  178. package/cli/storybook-dist/assets/RainbowUiProvider-B_piZBLb.js +31 -0
  179. package/cli/storybook-dist/assets/browserUtils-DeopUpDy.js +1 -0
  180. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  181. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  182. package/cli/storybook-dist/assets/createConveyorBeltLayer-Cn41pFRH.js +6210 -0
  183. package/cli/storybook-dist/assets/createConveyorBeltLayer-Ds5AOper.css +21 -0
  184. package/cli/storybook-dist/assets/entry-preview-D-mLV0cS.js +2 -0
  185. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  186. package/cli/storybook-dist/assets/iframe-DNru2eN7.js +211 -0
  187. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  188. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  189. package/cli/storybook-dist/assets/index-C1OzuK6-.js +240 -0
  190. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  191. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  192. package/cli/storybook-dist/assets/index-Cwr2bQOB.js +575 -0
  193. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  194. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  195. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  196. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  197. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  198. package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +1 -0
  199. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  200. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  201. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  202. package/cli/storybook-dist/assets/preview-CILRoSnB.js +1 -0
  203. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  204. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  205. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  206. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  207. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  208. package/cli/storybook-dist/assets/preview-DUTcCPBe.css +1 -0
  209. package/cli/storybook-dist/assets/preview-QAsoD6Sa.js +2 -0
  210. package/cli/storybook-dist/assets/preview-rcRMbKPr.js +3 -0
  211. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  212. package/cli/storybook-dist/assets/react-resizable-panels.browser-CvAJUcrm.js +72 -0
  213. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  214. package/cli/storybook-dist/favicon.svg +1 -0
  215. package/cli/storybook-dist/iframe.html +666 -0
  216. package/cli/storybook-dist/index.html +190 -0
  217. package/cli/storybook-dist/index.json +1 -0
  218. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  219. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  220. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  221. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  222. package/cli/storybook-dist/project.json +1 -0
  223. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  224. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  225. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  226. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  227. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  228. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  229. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  230. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  231. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  232. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  233. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  234. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  235. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  236. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  237. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  238. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  239. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  240. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  241. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  242. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  243. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  244. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  245. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  246. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  247. package/cli/templates/common/.mcp.json +8 -0
  248. package/cli/templates/common/README.md +817 -9
  249. package/cli/templates/common/assets/icon.svg +44 -2
  250. package/cli/templates/common/manifest.json +13 -13
  251. package/cli/templates/common/package.json +9 -11
  252. package/cli/templates/common/py/main.py +98 -0
  253. package/cli/templates/common/requirements.txt +4 -3
  254. package/cli/templates/common/tsconfig.json +1 -1
  255. package/cli/templates/common/ui/content.tsx +216 -0
  256. package/cli/templates/common/ui/style.css +6 -0
  257. package/cli/templates/common/vite.config.ts +6 -25
  258. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  259. package/cli/templates/cpp/cpp/main.cpp +26 -0
  260. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +5 -0
  261. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
  262. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  263. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
  264. package/cli/vite.d.mts +25 -0
  265. package/cli/vite.mjs +281 -0
  266. package/package.json +32 -5
  267. package/cli/src/pack.mjs +0 -61
  268. package/cli/templates/common/main.py +0 -21
  269. package/cli/templates/common/src/content.tsx +0 -32
@@ -0,0 +1,61 @@
1
+ // Shared C++ Docker helpers (Ubuntu 24.04 + build-essential + cmake).
2
+ // Matches the robot OS so the compiled binary is relocatable to the robot.
3
+ // Used by dev:back (interactive dev loop) and build (export the binary into the ZIP).
4
+ import { spawnSync } from "node:child_process";
5
+ import { dirname } from "node:path";
6
+ import { hasBin, log, pruneMuskatImages, resolveDockerfile } from "./util.mjs";
7
+
8
+ export const CPP_DEV_IMAGE = "muscat-pk-cpp-dev";
9
+
10
+ export function assertDockerReady() {
11
+ if (!hasBin("docker")) {
12
+ throw new Error("Docker not found. C++ builds need Docker (Ubuntu 24.04 + cmake container).");
13
+ }
14
+ }
15
+
16
+ /** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
17
+ export async function ensureImage(image = CPP_DEV_IMAGE, { platform = null } = {}) {
18
+ const dockerfile = await resolveDockerfile("Dockerfile.cpp");
19
+ log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
20
+ const platformFlags = platform ? ["--platform", platform] : [];
21
+ const r = spawnSync(
22
+ "docker",
23
+ ["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
24
+ { stdio: "inherit" },
25
+ );
26
+ if (r.status !== 0) throw new Error("docker build failed.");
27
+ pruneMuskatImages();
28
+ }
29
+
30
+ // Docker platform token for each target arch
31
+ export const ARCH_DOCKER_PLATFORM = {
32
+ amd64: "linux/amd64",
33
+ arm64: "linux/arm64",
34
+ };
35
+
36
+ /**
37
+ * Configure + build cpp/ inside the container, producing cpp/build-{arch}/<entry>
38
+ * on the host (the workspace is mounted).
39
+ * - arch null → native (dev mode, uses cpp/build/)
40
+ * - arch "x86_64" / "aarch64" → cross via --platform (Release builds for ZIP)
41
+ * buildType "Debug" for dev, "Release" for export builds that ship in the ZIP.
42
+ */
43
+ export function cmakeBuild(root, { image = CPP_DEV_IMAGE, buildType = "Debug", arch = null } = {}) {
44
+ const buildDir = arch ? `cpp/build-${arch}` : "cpp/build";
45
+ const dockerPlatform = arch ? ARCH_DOCKER_PLATFORM[arch] : null;
46
+ const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
47
+ // arch 빌드 시 arch별 이미지 사용 (로컬 이미지와 platform 불일치 방지)
48
+ const actualImage = arch ? `${CPP_DEV_IMAGE}-${arch}` : image;
49
+ const script = [
50
+ `cmake -S cpp -B ${buildDir} -DCMAKE_BUILD_TYPE=${buildType}`,
51
+ `cmake --build ${buildDir} -j`,
52
+ ].join(" && ");
53
+ const label = arch ? `cmake build (${buildType}, ${arch}, in container)…` : `cmake build (${buildType}, in container)…`;
54
+ log.step(label);
55
+ const r = spawnSync(
56
+ "docker",
57
+ ["run", "--rm", ...platformFlags, "-v", `${root}:/plugin`, "-w", "/plugin", actualImage, "bash", "-lc", script],
58
+ { cwd: root, stdio: "inherit" },
59
+ );
60
+ if (r.status !== 0) throw new Error(`cmake build failed${arch ? ` (${arch})` : ""}.`);
61
+ }
package/cli/src/dev.mjs CHANGED
@@ -1,10 +1,46 @@
1
- // muscat-pk dev — run dev:ui and dev:back together.
1
+ // muscat-pk dev — run the Socket.IO bridge (Docker), dev:ui, and dev:back together.
2
2
  import { runDevUi } from "./devUi.mjs";
3
- import { runDevBack } from "./devBack.mjs";
4
- import { log } from "./util.mjs";
3
+ import { runDevBack, stopDevBackContainers } from "./devBack.mjs";
4
+ import { startBridge, stopBridge, BRIDGE_CONTAINER, BRIDGE_PORT } from "./bridge.mjs";
5
+ import { assertManifestPorts } from "./manifest.mjs";
6
+ import { formatPortRanges } from "./ports.mjs";
7
+ import { ensureDocker, findPluginRoot, log, prompt, promptRobotConnection } from "./util.mjs";
5
8
 
6
9
  export async function runDev(args = []) {
7
- log.step("starting dev:ui + dev:back");
10
+ const root = findPluginRoot();
11
+ const portRanges = assertManifestPorts(root);
12
+ if (portRanges.length > 0) {
13
+ log.step(`plugin backend ports declared: ${formatPortRanges(portRanges)}`);
14
+ process.env.MUSCAT_PK_PORTS_PRECHECKED = "1";
15
+ }
16
+ ensureDocker();
17
+ const { robotIp, robotToken } = await promptRobotConnection(args);
18
+ process.env.MUSCAT_ROBOT_HOST = robotIp;
19
+ if (robotToken) process.env.MUSCAT_ROBOT_TOKEN = robotToken;
20
+ log.step("starting Socket.IO bridge (Docker) + dev:ui + dev:back");
21
+ const bridgeStarted = await startBridge();
22
+
23
+ if (!bridgeStarted) {
24
+ log.warn(`port ${BRIDGE_PORT} is already in use — can't start Socket.IO bridge.`);
25
+ const answer = await prompt(" Proceed without Socket.IO bridge? [y/N]: ");
26
+ if (answer.toLowerCase() !== "y") return;
27
+ log.info("proceeding without bridge — backend will skip network sharing.");
28
+ } else {
29
+ // Tell dev:back which bridge container to share a netns with, so the backend
30
+ // reaches the bridge at localhost:10000 (no host.docker.internal).
31
+ process.env.MUSCAT_PK_BRIDGE_CONTAINER = BRIDGE_CONTAINER;
32
+ }
33
+
34
+ process.env.MUSCAT_PK_MANAGED_DEV = "1";
35
+
36
+ const cleanup = () => {
37
+ stopDevBackContainers();
38
+ if (bridgeStarted) stopBridge();
39
+ };
40
+ process.once("SIGINT", cleanup);
41
+ process.once("SIGTERM", cleanup);
42
+ process.once("exit", cleanup);
43
+
8
44
  // dev:back runs to completion / detaches on its own; dev:ui owns the process.
9
45
  runDevBack().catch((e) => log.warn(`dev:back: ${e.message}`));
10
46
  await runDevUi(args);
@@ -1,71 +1,201 @@
1
1
  // muscat-pk dev:back — run the plugin back end during development.
2
- // python : create/refresh .venv, then run main.py with watchfiles hot-reload
3
- // (uvicorn's reloader). Watches ALL .py files, not just main.py.
4
- // ros2 : keep a Jazzy + Python 3.12 container warm, colcon build once with
5
- // --symlink-install, run the node, and restart on source changes —
6
- // i.e. develop as if inside the container.
2
+ // Every backend runs in a warm Docker container that matches the robot
3
+ // (Ubuntu 24.04), so dev behaves like production.
4
+ // python : Python 3.12 container, pip install, run main.py with watchfiles
5
+ // hot-reload. Shares the bridge container's netns (localhost:10000).
6
+ // cpp : Ubuntu + cmake container, build cpp/, run the binary, rebuild +
7
+ // restart on source changes.
8
+ // ros2 : Jazzy + Python 3.12 container, colcon build, run the node, restart
9
+ // on source changes (rclcpp rebuilds on change).
7
10
  import { spawn, spawnSync } from "node:child_process";
8
- import { existsSync, readFileSync, watch } from "node:fs";
11
+ import { existsSync, watch } from "node:fs";
9
12
  import { resolve } from "node:path";
10
- import { detectPythonCmd, findPluginRoot, log, venvPython } from "./util.mjs";
13
+ import { ensureDocker, ensureIdeVenv, findPluginRoot, log, promptRobotConnection } from "./util.mjs";
14
+ import { assertManifestPorts, manifestRuntime, readManifest, validateManifest } from "./manifest.mjs";
15
+ import {
16
+ formatPortRanges,
17
+ portEnvFlags,
18
+ startPortContractMonitor,
19
+ } from "./ports.mjs";
20
+ import {
21
+ PYTHON_DEV_IMAGE,
22
+ assertDockerReady as pyAssertDockerReady,
23
+ ensureImage as pyEnsureImage,
24
+ } from "./python.mjs";
11
25
  import {
12
26
  ROS2_DEV_IMAGE,
13
27
  assertDockerReady,
14
28
  ensureImage,
15
29
  ros2PackageName,
16
30
  } from "./ros2.mjs";
31
+ import {
32
+ CPP_DEV_IMAGE,
33
+ assertDockerReady as cppAssertDockerReady,
34
+ ensureImage as cppEnsureImage,
35
+ } from "./cpp.mjs";
17
36
 
18
- function manifestRuntime(root) {
19
- try {
20
- const m = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
21
- return m.runtime?.type ?? "python";
22
- } catch {
23
- return "python";
37
+ const MANAGED_DEV_ENV = "MUSCAT_PK_MANAGED_DEV";
38
+
39
+ function exitAfterCleanup(code = 0) {
40
+ if (process.env[MANAGED_DEV_ENV] !== "1") process.exit(code ?? 0);
41
+ }
42
+
43
+ export function stopDevBackContainers() {
44
+ for (const container of [
45
+ `${PYTHON_DEV_IMAGE}-run`,
46
+ `${ROS2_DEV_IMAGE}-run`,
47
+ `${CPP_DEV_IMAGE}-run`,
48
+ ]) {
49
+ spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
24
50
  }
25
51
  }
26
52
 
53
+ // ── IDE tooling venv (best-effort, non-blocking) ────────────────────────────
54
+ // `muscat-pk new` already creates this up front (see new.mjs); this just
55
+ // refreshes it on every `dev`/`dev:back` in case requirements.txt changed.
56
+ // Docker remains the actual execution environment — this never affects it.
57
+ // Runs in the background so it never delays `dev:back` starting, and any
58
+ // failure here (no local Python toolchain, offline, ...) is silent.
59
+ function syncIdeVenv(root) {
60
+ setImmediate(() => {
61
+ try {
62
+ if (ensureIdeVenv(root)) {
63
+ log.info("IDE venv ready → .venv (point your editor's Python interpreter there for pylint/pyright)");
64
+ }
65
+ } catch {
66
+ /* best-effort only */
67
+ }
68
+ });
69
+ }
70
+
27
71
  // ── python ────────────────────────────────────────────────────────────────────
28
- function runPython(root) {
29
- const venv = resolve(root, ".venv");
30
- const py = venvPython(venv);
31
-
32
- if (!existsSync(py)) {
33
- const { cmd, is312 } = detectPythonCmd();
34
- if (!is312) log.warn("python3.12 not found; using fallback (robot targets 3.12).");
35
- log.step("creating .venv…");
36
- spawnSync(cmd[0], [...cmd.slice(1), "-m", "venv", venv], { cwd: root, stdio: "inherit" });
37
- }
72
+ // Runs in a warm Ubuntu 24.04 + Python 3.12 container (matches the robot).
73
+ // Under `muscat-pk dev`, it shares the bridge container's netns, so the SDK
74
+ // reaches the Socket.IO bridge at the default localhost:10000.
75
+ async function runPython(root, portRanges) {
76
+ // Independent of Docker readiness — must still happen if Docker isn't
77
+ // installed/running, since that's exactly when local IDE tooling matters most.
78
+ syncIdeVenv(root);
79
+ pyAssertDockerReady();
80
+ await pyEnsureImage();
38
81
 
39
- const reqs = resolve(root, "requirements.txt");
40
- if (existsSync(reqs)) {
41
- log.step("pip install -r requirements.txt…");
42
- spawnSync(py, ["-m", "pip", "install", "-q", "-r", reqs], { cwd: root, stdio: "inherit" });
43
- }
82
+ const manifest = readManifest(root);
83
+ const pluginId = manifest.id ?? "";
84
+ const container = `${PYTHON_DEV_IMAGE}-run`;
85
+ const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
86
+ const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
87
+ // Share the bridge container's network namespace so localhost:10000 is the
88
+ // bridge (set by `muscat-pk dev`). Standalone dev:back has no bridge to join.
89
+ const bridgeContainer = process.env.MUSCAT_PK_BRIDGE_CONTAINER;
90
+ const netFlags = bridgeContainer ? ["--network", `container:${bridgeContainer}`] : [];
91
+ const hostGatewayFlags = bridgeContainer
92
+ ? []
93
+ : ["--add-host", "host.docker.internal:host-gateway"];
94
+ // Without the bridge's netns, this container is isolated — its own
95
+ // 127.0.0.1 (rainbow.rb_sdk's gateway default) is itself, not the host, so
96
+ // it can never see a gateway already running on the host (e.g. the user's
97
+ // own robot stack occupying port 10000, the reason the bridge start above
98
+ // was skipped in the first place). host.docker.internal reaches the host.
99
+ const gatewayFlags = bridgeContainer
100
+ ? []
101
+ : ["-e", "RB_PLUGIN_GATEWAY_URL=http://host.docker.internal:10000"];
102
+ const envFlags = [
103
+ // docker exec below has no -t (no pseudo-TTY), so Python defaults to
104
+ // block-buffered stdout — print() output wouldn't show up until the
105
+ // buffer fills or the process exits. Force unbuffered.
106
+ "-e", "PYTHONUNBUFFERED=1",
107
+ ...gatewayFlags,
108
+ ...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
109
+ ...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
110
+ ...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
111
+ ...portEnvFlags(portRanges),
112
+ ];
113
+ const docker = (args, opts = {}) =>
114
+ spawnSync("docker", args, { cwd: root, stdio: "inherit", ...opts });
44
115
 
45
- // watchfiles = uvicorn's reloader. --filter python watches every *.py in the
46
- // project (and ignores .venv/__pycache__/node_modules), so interdependent
47
- // modules trigger reloads — not just main.py.
48
- log.step("pip install -q watchfiles (dev reloader)…");
49
- spawnSync(py, ["-m", "pip", "install", "-q", "watchfiles"], { cwd: root, stdio: "inherit" });
116
+ // start a warm, long-lived container with the plugin mounted
117
+ docker(["rm", "-f", container], { stdio: "ignore" });
118
+ log.step("starting Python dev container…");
119
+ const up = docker(
120
+ ["run", "-d", "--name", container, ...netFlags,
121
+ // host.docker.internal is automatic on Docker Desktop (macOS/Windows);
122
+ // this makes it resolve on Linux too, for the no-bridge gateway fallback above.
123
+ ...hostGatewayFlags,
124
+ "-e", "PIP_ROOT_USER_ACTION=ignore",
125
+ "-v", `${root}:/plugin`, "-w", "/plugin", PYTHON_DEV_IMAGE, "sleep", "infinity"],
126
+ { stdio: "inherit" },
127
+ );
128
+ if (up.status !== 0) throw new Error("failed to start dev container.");
129
+ const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log });
130
+
131
+ log.step("pip install requirements + watchfiles (in container)…");
132
+ docker(["exec", container, "bash", "-lc",
133
+ "pip install --break-system-packages --ignore-installed -q watchfiles" +
134
+ " && { [ -f requirements.txt ] && pip install --break-system-packages --ignore-installed -q -r requirements.txt || true; }"]);
50
135
 
51
- log.step("watchfiles → python main.py (reload on any .py change)");
136
+ if (pluginId) log.step(`RB_PLUGIN_ID=${pluginId}`);
137
+ log.step("watchfiles → py/main.py (reload on any .py change in py/)");
52
138
  const child = spawn(
53
- py,
54
- ["-m", "watchfiles", "--filter", "python", "--target-type", "command", "python main.py", "."],
55
- { cwd: root, stdio: "inherit" },
139
+ "docker",
140
+ ["exec", ...envFlags, container, "bash", "-lc",
141
+ "python3 -m watchfiles --filter python --target-type command 'python3 py/main.py' py"],
142
+ { cwd: root, stdio: ["inherit", "inherit", "pipe"] },
56
143
  );
57
- child.on("exit", (code) => process.exit(code ?? 0));
144
+ // watchfiles prints a KeyboardInterrupt traceback on SIGTERM — suppress it
145
+ child.stderr.on("data", (buf) => {
146
+ const text = buf.toString();
147
+ if (!text.includes("KeyboardInterrupt") && !text.includes("raise_keyboard_interrupt")) {
148
+ process.stderr.write(buf);
149
+ }
150
+ });
151
+
152
+ let cleaned = false;
153
+ const cleanupResources = () => {
154
+ if (cleaned) return;
155
+ cleaned = true;
156
+ stopPortMonitor();
157
+ if (!child.killed && child.exitCode === null) child.kill();
158
+ spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
159
+ };
160
+ const cleanup = () => {
161
+ cleanupResources();
162
+ exitAfterCleanup(0);
163
+ };
164
+ child.on("exit", (code) => {
165
+ cleanupResources();
166
+ exitAfterCleanup(code ?? 0);
167
+ });
168
+ process.once("SIGINT", cleanup);
169
+ process.once("SIGTERM", cleanup);
58
170
  }
59
171
 
60
172
  // ── ros2 ───────────────────────────────────────────────────────────────────────
61
- function runRos2(root) {
173
+ async function runRos2(root, portRanges) {
174
+ const manifest = readManifest(root);
175
+ const isRclcpp = manifest.runtime?.rosClient === "rclcpp";
176
+ // Independent of Docker readiness — must still happen if Docker isn't
177
+ // installed/running, since that's exactly when local IDE tooling matters most.
178
+ if (!isRclcpp) syncIdeVenv(root); // rclpy is plain Python — rclcpp is C++, no venv needed
179
+
62
180
  assertDockerReady();
63
181
  const pkg = ros2PackageName(root);
64
182
  if (!pkg) throw new Error("no ROS2 package found under ros2_ws/src.");
65
183
 
66
- ensureImage();
184
+ await ensureImage();
67
185
 
186
+ const pluginId = manifest.id ?? "";
68
187
  const container = `${ROS2_DEV_IMAGE}-run`;
188
+ const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
189
+ const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
190
+ const envFlags = [
191
+ // Same rationale as runPython(): no -t on docker exec below means
192
+ // rclpy's Python process stdout is block-buffered, not line-buffered.
193
+ "-e", "PYTHONUNBUFFERED=1",
194
+ ...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
195
+ ...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
196
+ ...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
197
+ ...portEnvFlags(portRanges),
198
+ ];
69
199
  const docker = (args, opts = {}) =>
70
200
  spawnSync("docker", args, { cwd: root, stdio: "inherit", ...opts });
71
201
 
@@ -73,10 +203,11 @@ function runRos2(root) {
73
203
  docker(["rm", "-f", container], { stdio: "ignore" });
74
204
  log.step("starting ROS2 dev container…");
75
205
  const up = docker(
76
- ["run", "-d", "--name", container, "-v", `${root}:/plugin`, "-w", "/plugin", ROS2_DEV_IMAGE, "sleep", "infinity"],
206
+ ["run", "-d", "--name", container, "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", ROS2_DEV_IMAGE, "sleep", "infinity"],
77
207
  { stdio: "inherit" },
78
208
  );
79
209
  if (up.status !== 0) throw new Error("failed to start dev container.");
210
+ const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log });
80
211
 
81
212
  // one-time deps + symlink build (edits to .py reflect without rebuilding)
82
213
  log.step("pip install + colcon build --symlink-install (once)…");
@@ -89,21 +220,22 @@ function runRos2(root) {
89
220
  log.step(`ros2 run ${pkg} ${pkg}_node`);
90
221
  child = spawn(
91
222
  "docker",
92
- ["exec", container, "bash", "-lc",
223
+ ["exec", ...envFlags, container, "bash", "-lc",
93
224
  `cd ros2_ws && source install/setup.bash && ros2 run ${pkg} ${pkg}_node`],
94
225
  { cwd: root, stdio: "inherit" },
95
226
  );
96
227
  };
97
228
  startNode();
98
229
 
99
- // restart node on source change; rebuild only when package metadata changes
230
+ // restart node on source change. rclpy reflects .py edits live (symlink-install);
231
+ // rclcpp (C++) and package-metadata changes require a colcon rebuild.
100
232
  let timer = null;
101
233
  const onChange = (_event, filename) => {
102
234
  clearTimeout(timer);
103
235
  timer = setTimeout(() => {
104
236
  const structural = filename && /(setup\.py|package\.xml|CMakeLists\.txt)$/.test(String(filename));
105
- if (structural) {
106
- log.info("package metadata changed — colcon rebuild…");
237
+ if (structural || isRclcpp) {
238
+ log.info(structural ? "package metadata changed — colcon rebuild…" : "source changed — colcon rebuild…");
107
239
  docker(["exec", container, "bash", "-lc", "cd ros2_ws && colcon build --symlink-install"]);
108
240
  } else {
109
241
  log.info("source changed — restarting node…");
@@ -117,17 +249,110 @@ function runRos2(root) {
117
249
  }
118
250
 
119
251
  const cleanup = () => {
252
+ stopPortMonitor();
120
253
  if (child) child.kill();
121
254
  spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
122
- process.exit(0);
255
+ exitAfterCleanup(0);
123
256
  };
124
257
  process.on("SIGINT", cleanup);
125
258
  process.on("SIGTERM", cleanup);
126
259
  }
127
260
 
128
- export async function runDevBack() {
261
+ // ── cpp ──────────────────────────────────────────────────────────────────────
262
+ async function runCpp(root, portRanges) {
263
+ cppAssertDockerReady();
264
+ const manifest = readManifest(root);
265
+ const entry = manifest.runtime?.entry;
266
+ if (!entry) throw new Error("runtime.entry (the built executable name) is required for cpp.");
267
+
268
+ await cppEnsureImage();
269
+
270
+ const pluginId = manifest.id ?? "";
271
+ const container = `${CPP_DEV_IMAGE}-run`;
272
+ const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
273
+ const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
274
+ const envFlags = [
275
+ ...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
276
+ ...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
277
+ ...(robotToken ? ["-e", `MUSCAT_ROBOT_TOKEN=${robotToken}`] : []),
278
+ ...portEnvFlags(portRanges),
279
+ ];
280
+ const docker = (args, opts = {}) =>
281
+ spawnSync("docker", args, { cwd: root, stdio: "inherit", ...opts });
282
+
283
+ // start a warm, long-lived container with the plugin mounted
284
+ docker(["rm", "-f", container], { stdio: "ignore" });
285
+ log.step("starting C++ dev container…");
286
+ const up = docker(
287
+ ["run", "-d", "--name", container, "-v", `${root}:/plugin`, "-w", "/plugin", CPP_DEV_IMAGE, "sleep", "infinity"],
288
+ { stdio: "inherit" },
289
+ );
290
+ if (up.status !== 0) throw new Error("failed to start dev container.");
291
+ const stopPortMonitor = startPortContractMonitor({ container, ranges: portRanges, log });
292
+
293
+ const build = () => {
294
+ log.step("cmake build…");
295
+ return docker(["exec", container, "bash", "-lc",
296
+ "cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Debug && cmake --build cpp/build -j"]);
297
+ };
298
+ build();
299
+
300
+ let child = null;
301
+ const startBin = () => {
302
+ if (child) child.kill();
303
+ log.step(`run cpp/build/${entry}`);
304
+ child = spawn(
305
+ "docker",
306
+ ["exec", ...envFlags, container, "bash", "-lc", `./cpp/build/${entry}`],
307
+ { cwd: root, stdio: "inherit" },
308
+ );
309
+ };
310
+ startBin();
311
+
312
+ // rebuild + restart on C++ source changes; ignore the build/ output dir.
313
+ let timer = null;
314
+ const onChange = (_event, filename) => {
315
+ if (filename && String(filename).startsWith("build")) return;
316
+ clearTimeout(timer);
317
+ timer = setTimeout(() => {
318
+ log.info("source changed — rebuilding…");
319
+ const r = build();
320
+ if (r.status === 0) startBin();
321
+ else log.warn("build failed — keeping previous binary");
322
+ }, 300);
323
+ };
324
+ const cppDir = resolve(root, "cpp");
325
+ if (existsSync(cppDir)) watch(cppDir, { recursive: true }, onChange);
326
+
327
+ const cleanup = () => {
328
+ stopPortMonitor();
329
+ if (child) child.kill();
330
+ spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
331
+ exitAfterCleanup(0);
332
+ };
333
+ process.on("SIGINT", cleanup);
334
+ process.on("SIGTERM", cleanup);
335
+ }
336
+
337
+ function warnManifest(root) {
338
+ const manifest = readManifest(root);
339
+ if (!manifest || Object.keys(manifest).length === 0) return;
340
+ const errors = validateManifest(manifest);
341
+ for (const e of errors) log.warn(`manifest.json: ${e}`);
342
+ }
343
+
344
+ export async function runDevBack(args = []) {
129
345
  const root = findPluginRoot();
346
+ warnManifest(root);
347
+ const portRanges = assertManifestPorts(root);
348
+ if (portRanges.length > 0 && process.env.MUSCAT_PK_PORTS_PRECHECKED !== "1")
349
+ log.step(`plugin backend ports declared: ${formatPortRanges(portRanges)}`);
350
+ ensureDocker();
351
+ const { robotIp, robotToken } = await promptRobotConnection(args);
352
+ if (robotIp) process.env.MUSCAT_ROBOT_HOST = robotIp;
353
+ if (robotToken) process.env.MUSCAT_ROBOT_TOKEN = robotToken;
130
354
  const type = manifestRuntime(root);
131
- if (type === "ros2") return runRos2(root);
132
- return runPython(root);
355
+ if (type === "ros2") return await runRos2(root, portRanges);
356
+ if (type === "cpp") return await runCpp(root, portRanges);
357
+ return await runPython(root, portRanges);
133
358
  }