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

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