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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/README.ko.md +1188 -0
  2. package/README.md +1139 -57
  3. package/cli/README.md +239 -7
  4. package/cli/bin/create-muscat-plugin.mjs +1 -1
  5. package/cli/bin/muscat-pk.mjs +123 -27
  6. package/cli/bundle-templates.mjs +31 -0
  7. package/cli/docker/Dockerfile.cpp +28 -0
  8. package/cli/docker/Dockerfile.dev +17 -0
  9. package/cli/docker/Dockerfile.python +21 -0
  10. package/cli/prepack.mjs +12 -0
  11. package/cli/program-commands.json +2079 -0
  12. package/cli/src/__pycache__/host_agent.cpython-37.pyc +0 -0
  13. package/cli/src/ai-rules.mjs +108 -0
  14. package/cli/src/build.mjs +333 -93
  15. package/cli/src/check-package.mjs +8 -23
  16. package/cli/src/completion.mjs +168 -0
  17. package/cli/src/cpp.mjs +78 -0
  18. package/cli/src/cppIde.mjs +76 -0
  19. package/cli/src/dev.mjs +195 -6
  20. package/cli/src/devBack.mjs +671 -60
  21. package/cli/src/devId.mjs +54 -0
  22. package/cli/src/devSession.mjs +67 -0
  23. package/cli/src/devUi.mjs +218 -25
  24. package/cli/src/esbuild.mjs +27 -10
  25. package/cli/src/host_agent.py +223 -0
  26. package/cli/src/manifest.mjs +288 -0
  27. package/cli/src/mcp.mjs +1232 -0
  28. package/cli/src/mcpProxy.mjs +110 -0
  29. package/cli/src/muscatLocal.mjs +292 -0
  30. package/cli/src/new.mjs +205 -68
  31. package/cli/src/pluginDevServer.mjs +111 -0
  32. package/cli/src/ports.mjs +193 -0
  33. package/cli/src/programCommands.mjs +327 -0
  34. package/cli/src/projectCheck.mjs +102 -0
  35. package/cli/src/python.mjs +112 -0
  36. package/cli/src/ros2.mjs +62 -18
  37. package/cli/src/sdk.mjs +74 -24
  38. package/cli/src/sim.mjs +177 -0
  39. package/cli/src/storybook.mjs +150 -0
  40. package/cli/src/templates-embedded.mjs +4 -0
  41. package/cli/src/type.mjs +93 -34
  42. package/cli/src/uninstall.mjs +81 -0
  43. package/cli/src/update.mjs +364 -35
  44. package/cli/src/util.mjs +341 -11
  45. package/cli/src/zenohDev.mjs +83 -0
  46. package/cli/src/zip.mjs +121 -0
  47. package/cli/storybook-dist/_design-tokens.css +4957 -0
  48. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  49. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  50. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  51. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  52. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  53. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  54. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  55. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  56. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  57. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  58. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  59. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  60. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -0
  61. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  62. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  63. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  64. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  65. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  66. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  67. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  68. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  69. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  70. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  71. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  72. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -0
  73. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  74. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  75. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  76. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  77. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  78. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  79. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  84. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  85. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  86. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  87. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  88. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  89. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  90. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  91. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  92. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  93. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  94. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  95. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  96. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  97. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  98. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  99. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  100. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  101. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  102. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  103. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  104. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  105. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  106. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  107. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  108. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  109. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  110. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  111. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  112. package/cli/storybook-dist/assets/Color-YHDXOIA2-pCKrjUal.js +1 -0
  113. package/cli/storybook-dist/assets/Docs-B-Y7I1GA.js +7 -0
  114. package/cli/storybook-dist/assets/Docs-BFJcE_VS.js +8 -0
  115. package/cli/storybook-dist/assets/Docs-B_QaTtz8.js +8 -0
  116. package/cli/storybook-dist/assets/Docs-CryDGmfh.js +20 -0
  117. package/cli/storybook-dist/assets/Docs-Cw2AVNKx.js +7 -0
  118. package/cli/storybook-dist/assets/Docs-Q6r1fBeL.js +12 -0
  119. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-_OmZBCn8.js +2 -0
  120. package/cli/storybook-dist/assets/NxAvatar.stories-Ctf5G2FQ.js +13 -0
  121. package/cli/storybook-dist/assets/NxButton.stories-DeLObVSj.js +20 -0
  122. package/cli/storybook-dist/assets/NxIcons.stories-DeqrVzVs.js +3 -0
  123. package/cli/storybook-dist/assets/NxNumberInput.stories-LqoY0ed1.js +26 -0
  124. package/cli/storybook-dist/assets/NxPagination.stories-DI6bXuJM.js +11 -0
  125. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-3HSV1XmN.js +14 -0
  126. package/cli/storybook-dist/assets/NxRangeInput.stories-1pjgQVaG.js +17 -0
  127. package/cli/storybook-dist/assets/NxSelectBox.stories-bcQPZEuF.js +17 -0
  128. package/cli/storybook-dist/assets/NxSlideRange.stories-BosX4axk.js +14 -0
  129. package/cli/storybook-dist/assets/NxSwitch.stories-DxV6dS33.js +11 -0
  130. package/cli/storybook-dist/assets/NxTextArea.stories-CO545-LP.js +11 -0
  131. package/cli/storybook-dist/assets/NxTextInput.stories-BP0npai1.js +11 -0
  132. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  133. package/cli/storybook-dist/assets/RBAccordion.stories-fVR4Va2a.js +17 -0
  134. package/cli/storybook-dist/assets/RBAccordionGroup.stories-Du5bsckD.js +33 -0
  135. package/cli/storybook-dist/assets/RBAlert.stories-DNkMXUu6.js +17 -0
  136. package/cli/storybook-dist/assets/RBBadge.stories-C44pXt8D.js +1 -0
  137. package/cli/storybook-dist/assets/RBBattery.stories-DE2JHvfL.js +10 -0
  138. package/cli/storybook-dist/assets/RBCard.stories-CuIda7NI.js +45 -0
  139. package/cli/storybook-dist/assets/RBCheckBox.stories-D1qM1ayr.js +1 -0
  140. package/cli/storybook-dist/assets/RBCheckIconButton.stories-e4gkliBO.js +1 -0
  141. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-CDzCtsMI.js +24 -0
  142. package/cli/storybook-dist/assets/RBChipTab.stories-DBrc_3Mp.js +21 -0
  143. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-hlrsHjKY.js +1 -0
  144. package/cli/storybook-dist/assets/RBColorIcons.stories-BHUvpkdu.js +3 -0
  145. package/cli/storybook-dist/assets/RBCommand.stories-azAF41NP.js +73 -0
  146. package/cli/storybook-dist/assets/RBConfirm.stories-BMNjwbmT.js +20 -0
  147. package/cli/storybook-dist/assets/RBDateInput.stories-D0VezDzv.js +15 -0
  148. package/cli/storybook-dist/assets/RBDialog.stories-AOJ975Fy.js +38 -0
  149. package/cli/storybook-dist/assets/RBDivider.stories-NSvnj43Z.js +1 -0
  150. package/cli/storybook-dist/assets/RBDrawer.stories-Du0oTvD1.js +14 -0
  151. package/cli/storybook-dist/assets/RBDropdownMenu.stories-Hsxve4PW.js +86 -0
  152. package/cli/storybook-dist/assets/RBEmptyView.stories-BlwHx3zp.js +12 -0
  153. package/cli/storybook-dist/assets/RBIconButton.stories-DAPDY63x.js +1 -0
  154. package/cli/storybook-dist/assets/RBIcons.stories-W5ZUV2L1.js +3 -0
  155. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-C99j0Y3g.js +1 -0
  156. package/cli/storybook-dist/assets/RBJoyStick.stories-Du0H1iAS.js +11 -0
  157. package/cli/storybook-dist/assets/RBLabelButton.stories-BlvHaU_Z.js +1 -0
  158. package/cli/storybook-dist/assets/RBLight.stories-BY6DKrre.js +1 -0
  159. package/cli/storybook-dist/assets/RBMenubar.stories-D2PEGpM9.js +3 -0
  160. package/cli/storybook-dist/assets/RBMultiRangeInput.stories-Bm2qSmjq.js +14 -0
  161. package/cli/storybook-dist/assets/RBPagination.stories-DDleJa5N.js +17 -0
  162. package/cli/storybook-dist/assets/RBPopover.stories-27YxPSFG.js +11 -0
  163. package/cli/storybook-dist/assets/RBPrompt.stories-BFaIOctr.js +20 -0
  164. package/cli/storybook-dist/assets/RBRadioBox.stories-BroDar0t.js +1 -0
  165. package/cli/storybook-dist/assets/RBRadioButton.stories-M3O-wX7V.js +11 -0
  166. package/cli/storybook-dist/assets/RBRangeDateInput.stories-CWlM_D5X.js +15 -0
  167. package/cli/storybook-dist/assets/RBResizable.stories-ui8X1qnc.js +20 -0
  168. package/cli/storybook-dist/assets/RBSelectBox.stories-C8T6cfA0.js +74 -0
  169. package/cli/storybook-dist/assets/RBSheet.stories-BCOo_Zcc.js +39 -0
  170. package/cli/storybook-dist/assets/RBSideNumberInput.stories-CU2_MVlJ.js +5 -0
  171. package/cli/storybook-dist/assets/RBSlider.stories-DgOp1TFc.js +5 -0
  172. package/cli/storybook-dist/assets/RBSpinner.stories-qLScY3zZ.js +14 -0
  173. package/cli/storybook-dist/assets/RBSwitch.stories-SXSY1QMb.js +65 -0
  174. package/cli/storybook-dist/assets/RBTab.stories-BzXXTsXP.js +1 -0
  175. package/cli/storybook-dist/assets/RBTable.stories-DHTeRNVk.js +62 -0
  176. package/cli/storybook-dist/assets/RBTableRow.stories-DRNRsd15.js +11 -0
  177. package/cli/storybook-dist/assets/RBTabs.stories-DP0noBLp.js +21 -0
  178. package/cli/storybook-dist/assets/RBTextArea.stories-TkaejfxJ.js +5 -0
  179. package/cli/storybook-dist/assets/RBTextField.stories-B6VpCGyt.js +45 -0
  180. package/cli/storybook-dist/assets/RBTextInput.stories-D3hZ08e-.js +5 -0
  181. package/cli/storybook-dist/assets/RBToast.stories-BQ2wS2U0.js +11 -0
  182. package/cli/storybook-dist/assets/RBToggle.stories-GVOjGqo3.js +12 -0
  183. package/cli/storybook-dist/assets/RBToggleGroup.stories-pzx0btOD.js +17 -0
  184. package/cli/storybook-dist/assets/RBTooltip.stories-jufB6fa1.js +9 -0
  185. package/cli/storybook-dist/assets/SideNumberInput-BYlmNHEw.js +1109 -0
  186. package/cli/storybook-dist/assets/SideNumberInput-SAkhydsn.css +21 -0
  187. package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
  188. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  189. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  190. package/cli/storybook-dist/assets/entry-preview-CAkU-e0D.js +2 -0
  191. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  192. package/cli/storybook-dist/assets/iframe-B-BmTsxV.js +211 -0
  193. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  194. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  195. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  196. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  197. package/cli/storybook-dist/assets/index-CkB0GfaI.js +575 -0
  198. package/cli/storybook-dist/assets/index-ClAc6FBW.js +240 -0
  199. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  200. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  201. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  202. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  203. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  204. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  205. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  206. package/cli/storybook-dist/assets/preview-BVJwlGUF.js +1 -0
  207. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  208. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  209. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  210. package/cli/storybook-dist/assets/preview-CznQwwbo.js +3 -0
  211. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  212. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  213. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  214. package/cli/storybook-dist/assets/preview-M77JRg1A.js +2 -0
  215. package/cli/storybook-dist/assets/preview-kTZPgP6D.css +1 -0
  216. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  217. package/cli/storybook-dist/assets/react-resizable-panels-HsFbxPbn.css +1 -0
  218. package/cli/storybook-dist/assets/react-resizable-panels.browser-DuxpZLkF.js +120 -0
  219. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  220. package/cli/storybook-dist/favicon.svg +1 -0
  221. package/cli/storybook-dist/iframe.html +666 -0
  222. package/cli/storybook-dist/index.html +190 -0
  223. package/cli/storybook-dist/index.json +1 -0
  224. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  225. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  226. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  227. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  228. package/cli/storybook-dist/project.json +1 -0
  229. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  230. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  231. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  232. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  233. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  234. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  235. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  236. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  237. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  238. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  239. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  240. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  241. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  242. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  243. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  244. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  245. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  246. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  247. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  248. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  249. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  250. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  251. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  252. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  253. package/cli/templates/common/README.md +1070 -83
  254. package/cli/templates/common/assets/icon.svg +44 -2
  255. package/cli/templates/common/manifest.json +14 -13
  256. package/cli/templates/common/package.json +11 -4
  257. package/cli/templates/common/py/main.py +125 -0
  258. package/cli/templates/common/requirements.txt +4 -3
  259. package/cli/templates/common/tsconfig.json +1 -1
  260. package/cli/templates/common/ui/content.tsx +216 -0
  261. package/cli/templates/common/ui/style.css +6 -0
  262. package/cli/templates/common/vite.config.ts +6 -25
  263. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  264. package/cli/templates/cpp/cpp/main.cpp +73 -0
  265. package/cli/templates/readme-cpp/README.md +1012 -0
  266. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +12 -1
  267. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +22 -0
  268. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  269. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +41 -0
  270. package/cli/vite.d.mts +25 -0
  271. package/cli/vite.mjs +292 -0
  272. package/package.json +34 -7
  273. package/cli/src/pack.mjs +0 -61
  274. package/cli/src/preview.mjs +0 -91
  275. package/cli/templates/common/main.py +0 -114
  276. package/cli/templates/common/src/content.tsx +0 -33
@@ -0,0 +1,112 @@
1
+ // Shared Python Docker helpers (Ubuntu 24.04 + Python 3.12 — matches the robot).
2
+ // Used by dev:back (hot-reload) and build (lock + wheel vendor).
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, resolve } from "node:path";
6
+ import { canSkipImageBuild, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile, buildCacheFlags } from "./util.mjs";
7
+
8
+ export const PYTHON_DEV_IMAGE = "muscat-pk-python-dev";
9
+
10
+ export function assertDockerReady() {
11
+ if (!hasBin("docker")) {
12
+ throw new Error("Docker not found. The Python backend runs in a container (Ubuntu 24.04 + Python 3.12).");
13
+ }
14
+ }
15
+
16
+ /** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
17
+ export async function ensureImage(image = PYTHON_DEV_IMAGE, { platform = null } = {}) {
18
+ const dockerfile = await resolveDockerfile("Dockerfile.python");
19
+ const key = imageKey(dockerfile);
20
+ if (canSkipImageBuild(image, key)) {
21
+ return;
22
+ }
23
+ log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
24
+ const platformFlags = platform ? ["--platform", platform] : [];
25
+ const r = spawnSync(
26
+ "docker",
27
+ ["build", ...buildCacheFlags(), ...platformFlags, ...imageKeyLabel(key), "-t", image, "-f", dockerfile, dirname(dockerfile)],
28
+ { stdio: "inherit" },
29
+ );
30
+ if (r.status !== 0) throw new Error("docker build failed.");
31
+ pruneMuskatImages();
32
+ }
33
+
34
+ // pip --platform does NOT auto-expand manylinux tags (unlike sys_tags()) — exact match only.
35
+ // Specifying all common glibc milestones ensures packages built for any Ubuntu era are found.
36
+ const PIP_PLATFORM = {
37
+ amd64: [
38
+ "manylinux_2_5_x86_64",
39
+ "manylinux_2_17_x86_64",
40
+ "manylinux_2_27_x86_64",
41
+ "manylinux_2_28_x86_64",
42
+ "manylinux_2_34_x86_64",
43
+ "manylinux_2_35_x86_64",
44
+ "manylinux_2_39_x86_64",
45
+ "linux_x86_64",
46
+ ],
47
+ arm64: [
48
+ "manylinux_2_17_aarch64",
49
+ "manylinux_2_27_aarch64",
50
+ "manylinux_2_28_aarch64",
51
+ "manylinux_2_34_aarch64",
52
+ "manylinux_2_35_aarch64",
53
+ "manylinux_2_39_aarch64",
54
+ "linux_aarch64",
55
+ ],
56
+ };
57
+
58
+ export async function lockAndVendor(pluginRoot, archs = ["amd64", "arm64"]) {
59
+ // ponytail: pip --platform used intentionally (not QEMU) — downloads wheels without running target arch code.
60
+ const reqTxt = resolve(pluginRoot, "requirements.txt");
61
+ if (!existsSync(reqTxt)) {
62
+ log.warn("requirements.txt not found — lock/vendor skipped");
63
+ return;
64
+ }
65
+
66
+ await ensureImage();
67
+
68
+ // A pinned pre-release (e.g. rainbow-rb-sdk==0.1.4.dev1) pins its sibling rainbow-rb-*
69
+ // pre-releases too; uv rejects those transitive pre-releases unless allowed.
70
+ const reqText = readFileSync(resolve(pluginRoot, "requirements.txt"), "utf8");
71
+ const prerelease = /==\s*\S*\d(a|b|rc|\.dev)\d*\s*$/im.test(reqText) ? " --prerelease=allow" : "";
72
+ log.step(`generating lock (uv pip compile${prerelease})…`);
73
+ const lockResult = spawnSync(
74
+ "docker",
75
+ [
76
+ "run", "--rm",
77
+ ...dockerUserFlags(),
78
+ "-v", `${pluginRoot}:/plugin`,
79
+ PYTHON_DEV_IMAGE,
80
+ "bash", "-c",
81
+ "set -e && cd /plugin && mkdir -p dist && uv pip compile requirements.txt --python-version 3.12 -o dist/requirements.lock" + prerelease,
82
+ ],
83
+ { stdio: "inherit" },
84
+ );
85
+ if (lockResult.status !== 0) throw new Error("lock generation failed (uv pip compile)");
86
+ log.ok("dist/requirements.lock ready");
87
+
88
+ for (const arch of archs) {
89
+ const pipPlatforms = PIP_PLATFORM[arch];
90
+ if (!pipPlatforms) throw new Error(`unknown arch '${arch}'`);
91
+ log.step(`vendoring wheels for ${arch}…`);
92
+ const r = spawnSync(
93
+ "docker",
94
+ [
95
+ "run", "--rm",
96
+ ...dockerUserFlags(),
97
+ "-e", "PIP_ROOT_USER_ACTION=ignore",
98
+ "-v", `${pluginRoot}:/plugin`,
99
+ "-w", "/plugin",
100
+ PYTHON_DEV_IMAGE,
101
+ "bash", "-c",
102
+ "mkdir -p dist/wheels && pip3 download -r dist/requirements.lock --no-deps --only-binary=:all: -d dist/wheels -q " +
103
+ "--python-version 312 --implementation cp --abi cp312 " +
104
+ pipPlatforms.map((p) => `--platform ${p}`).join(" "),
105
+ ],
106
+ { stdio: "inherit" },
107
+ );
108
+ // A ZIP missing wheels fails to import on the robot — stop instead.
109
+ if (r.status !== 0) throw new Error(`wheel download failed for ${arch} (see pip output above)`);
110
+ log.ok(`dist/wheels/ ready (${arch})`);
111
+ }
112
+ }
package/cli/src/ros2.mjs CHANGED
@@ -3,14 +3,11 @@
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { existsSync, readdirSync } from "node:fs";
5
5
  import { dirname, resolve } from "node:path";
6
- import { hasBin, kitRoot, log } from "./util.mjs";
6
+ import { canSkipImageBuild, cppSdkVersion, pluginRootOrNull, imageKey, imageKeyLabel, dockerUserFlags, hasBin, log, pruneMuskatImages, resolveDockerfile, buildCacheFlags } from "./util.mjs";
7
+ import { syncCppIde } from "./cppIde.mjs";
7
8
 
8
9
  export const ROS2_DEV_IMAGE = "muscat-pk-ros2-dev";
9
10
 
10
- // Dockerfile lives inside the kit — plugin authors never see or edit it.
11
- // It has no COPY/ADD, so the build context is irrelevant (its own dir is used).
12
- const DOCKERFILE = resolve(kitRoot, "cli/docker/Dockerfile.dev");
13
-
14
11
  /** First package directory under ros2_ws/src (the colcon package name). */
15
12
  export function ros2PackageName(root) {
16
13
  const srcDir = resolve(root, "ros2_ws/src");
@@ -25,20 +22,38 @@ export function assertDockerReady() {
25
22
  if (!hasBin("docker")) {
26
23
  throw new Error("Docker not found. ROS2 needs Docker (Jazzy + Python 3.12 container).");
27
24
  }
28
- if (!existsSync(DOCKERFILE)) {
29
- throw new Error(`kit Dockerfile missing at ${DOCKERFILE} (reinstall muscat-plugin-kit).`);
30
- }
31
25
  }
32
26
 
27
+ export const ARCH_DOCKER_PLATFORM = {
28
+ amd64: "linux/amd64",
29
+ arm64: "linux/arm64",
30
+ };
31
+
33
32
  /** Build the dev image from the kit's bundled Dockerfile (Docker layer-caches). */
34
- export function ensureImage(image = ROS2_DEV_IMAGE) {
35
- log.step(`docker build -t ${image} (first run is slow, then cached)…`);
33
+ export async function ensureImage(image = ROS2_DEV_IMAGE, { platform = null, root = pluginRootOrNull() } = {}) {
34
+ await buildImage(image, platform, root);
35
+ // Dev images feed the editor's SDK headers: the native one (dev) or the host-arch one (build).
36
+ const hostArch = process.arch === "arm64" ? "arm64" : "amd64";
37
+ if (root && (image === ROS2_DEV_IMAGE || image === `${ROS2_DEV_IMAGE}-${hostArch}`)) syncCppIde(root, image);
38
+ }
39
+
40
+ async function buildImage(image, platform, root) {
41
+ const dockerfile = await resolveDockerfile("Dockerfile.dev");
42
+ const sdkVer = cppSdkVersion(root);
43
+ const key = imageKey(dockerfile, sdkVer ?? "");
44
+ if (canSkipImageBuild(image, key)) {
45
+ return;
46
+ }
47
+ log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
48
+ const platformFlags = platform ? ["--platform", platform] : [];
49
+ const buildArgs = [...(sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : []), ...imageKeyLabel(key)];
36
50
  const r = spawnSync(
37
51
  "docker",
38
- ["build", "-t", image, "-f", DOCKERFILE, dirname(DOCKERFILE)],
52
+ ["build", ...buildCacheFlags(), ...platformFlags, ...buildArgs, "-t", image, "-f", dockerfile, dirname(dockerfile)],
39
53
  { stdio: "inherit" },
40
54
  );
41
55
  if (r.status !== 0) throw new Error("docker build failed.");
56
+ pruneMuskatImages();
42
57
  }
43
58
 
44
59
  /**
@@ -46,20 +61,49 @@ export function ensureImage(image = ROS2_DEV_IMAGE) {
46
61
  * (the workspace is mounted). symlink=true for dev (fast, edit-in-place);
47
62
  * symlink=false for export builds that ship in the ZIP (real copies, relocatable).
48
63
  */
49
- export function colconBuild(root, { image = ROS2_DEV_IMAGE, symlink = false } = {}) {
64
+ /**
65
+ * arch=null → dev mode, builds to ros2_ws/install/ (native)
66
+ * arch="amd64"|"arm64" → export mode, builds to ros2_ws/install-{arch}/ using arch-specific image
67
+ */
68
+ /**
69
+ * symlink=true (dev): ros2_ws/install/ — local dev workspace, not for ZIP
70
+ * symlink=false, arch=null (rclpy export): dist/ros2_ws/install/
71
+ * symlink=false, arch="amd64"|"arm64" (rclcpp export): dist/ros2_ws/install-{arch}/
72
+ */
73
+ export function colconBuild(root, { image = ROS2_DEV_IMAGE, symlink = false, arch = null } = {}) {
74
+ // paths are relative to ros2_ws/ inside the container
75
+ let installBase;
76
+ if (symlink) {
77
+ installBase = "install";
78
+ } else if (arch) {
79
+ installBase = `../dist/ros2_ws/install-${arch}`;
80
+ } else {
81
+ installBase = "../dist/ros2_ws/install";
82
+ }
50
83
  const flag = symlink ? "--symlink-install" : "";
84
+ const buildBase = symlink ? "build" : arch ? `build-${arch}` : "build-dist";
51
85
  const script = [
52
- // --ignore-installed: don't try to uninstall debian-managed packages
53
- // (e.g. psutil), which fails with "RECORD file not found".
54
86
  "pip install -r requirements.txt --break-system-packages --ignore-installed -q || true",
55
87
  "cd ros2_ws",
56
- `colcon build ${flag}`.trim(),
88
+ // Export builds use their own build cache: sharing build/ with dev's --symlink-install
89
+ // (or another arch) breaks the next colcon build.
90
+ // rclcpp export: ship librb_sdk.so in <pkg>/lib/ (robot has no SDK) when the node links it.
91
+ `colcon build ${flag} --build-base ${buildBase} --install-base ${installBase}`.replace(" ", " ") +
92
+ (arch ? ` --cmake-args '-DCMAKE_INSTALL_RPATH=$ORIGIN/..'` : ""),
93
+ ...(arch ? [`for d in ${installBase}/*/lib; do if readelf -d $(find "$d" -mindepth 2 -type f -executable) 2>/dev/null | grep -q librb_sdk; then cp -a $(dpkg -L rainbow-rb-sdk | grep -E '/librb_sdk[.]so[.][0-9]') "$d"/; fi; done`] : []),
57
94
  ].join(" && ");
58
- log.step(`colcon build${symlink ? " --symlink-install" : ""} (in container)…`);
95
+ const label = arch
96
+ ? `colcon build (${arch}) → dist/ros2_ws/install-${arch}/…`
97
+ : `colcon build${symlink ? " --symlink-install" : ""} (in container)…`;
98
+ log.step(label);
99
+ const actualImage = arch ? `${ROS2_DEV_IMAGE}-${arch}` : image;
100
+ const dockerPlatform = arch ? ARCH_DOCKER_PLATFORM[arch] : null;
101
+ const platformFlags = dockerPlatform ? ["--platform", dockerPlatform] : [];
59
102
  const r = spawnSync(
60
103
  "docker",
61
- ["run", "--rm", "-v", `${root}:/plugin`, "-w", "/plugin", image, "bash", "-lc", script],
104
+ // python/cpp 와 같이 호스트 uid 로 돌린다 — root 소유 산출물이 남으면 사용자가 지우지 못한다.
105
+ ["run", "--rm", ...platformFlags, ...dockerUserFlags(), "-e", "PIP_ROOT_USER_ACTION=ignore", "-v", `${root}:/plugin`, "-w", "/plugin", actualImage, "bash", "-lc", script],
62
106
  { cwd: root, stdio: "inherit" },
63
107
  );
64
- if (r.status !== 0) throw new Error("colcon build failed.");
108
+ if (r.status !== 0) throw new Error(`colcon build failed${arch ? ` (${arch})` : ""}.`);
65
109
  }
package/cli/src/sdk.mjs CHANGED
@@ -1,27 +1,78 @@
1
- // muscat-pk sdk <list|update [version]> — manage the python rainbow-rb-sdk.
2
- import { spawnSync } from "node:child_process";
1
+ // muscat-pk sdk <list|update [version]> — manage the rainbow-rb-sdk version.
2
+ //
3
+ // C++ backends (cpp, ros2 rclcpp) pin it in CMakeLists.txt (find_package(rb_sdk <ver> EXACT)); the
4
+ // versions come from the rb-sdk apt repository and the dev image installs the pinned one.
5
+ //
6
+ // Python backends:
7
+ // Pins requirements.txt — the real install happens in the `dev`/`dev:back`
8
+ // Docker container. If a local .venv exists (see devBack.mjs's syncIdeVenv,
9
+ // for editor/linter support), also update it in place so pylint/pyright see
10
+ // the new version immediately; this is best-effort only.
3
11
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
12
  import { resolve } from "node:path";
5
- import { findPluginRoot, log } from "./util.mjs";
13
+ import { cppSdkFiles, cppSdkPin, ensureIdeVenv, findPluginRoot, log, setCppSdkPin, venvPython } from "./util.mjs";
14
+ import { readManifest } from "./manifest.mjs";
6
15
 
7
16
  const PKG = "rainbow-rb-sdk";
8
17
 
9
- async function fetchVersions() {
18
+ async function fetchPypi() {
10
19
  try {
11
20
  const res = await fetch(`https://pypi.org/pypi/${PKG}/json`);
21
+ return res.ok ? await res.json() : null;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ async function fetchVersions() {
28
+ const data = await fetchPypi();
29
+ return Object.keys(data?.releases || {}).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
30
+ }
31
+
32
+ // Stable releases of rainbow-rb-sdk-dev, newest first. Pre-releases (0.1.4~dev1) are skipped: CMake
33
+ // versions are numeric only.
34
+ const APT_PACKAGES = "https://rainbowrobotics.github.io/rb-sdk/dists/stable/main/binary-amd64/Packages";
35
+
36
+ export async function fetchAptVersions() {
37
+ try {
38
+ const res = await fetch(APT_PACKAGES);
12
39
  if (!res.ok) return [];
13
- const data = await res.json();
14
- return Object.keys(data.releases || {}).sort((a, b) =>
15
- b.localeCompare(a, undefined, { numeric: true }),
16
- );
40
+ const text = await res.text();
41
+ const versions = [...text.matchAll(/^Package: rainbow-rb-sdk-dev\n(?:.*\n)*?Version: (\S+)/gm)].map((m) => m[1]);
42
+ return [...new Set(versions)].filter((v) => /^[0-9]+(\.[0-9]+)*$/.test(v)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
17
43
  } catch {
18
44
  return [];
19
45
  }
20
46
  }
21
47
 
48
+ async function runCppSdk(sub, version, root) {
49
+ if (sub === "list") {
50
+ const versions = await fetchAptVersions();
51
+ if (!versions.length) return log.warn("could not fetch versions (offline?).");
52
+ log.info(`rainbow-rb-sdk-dev versions:\n ${versions.slice(0, 12).join("\n ")}`);
53
+ return;
54
+ }
55
+ if (sub === "update") {
56
+ if (!cppSdkFiles(root).length) throw new Error("no find_package(rb_sdk ...) in cpp/CMakeLists.txt or ros2_ws/src/*/CMakeLists.txt");
57
+ if (!version) {
58
+ version = (await fetchAptVersions())[0];
59
+ if (!version) throw new Error("could not fetch the latest version (offline?). Pass one: muscat-pk sdk update <version>");
60
+ }
61
+ if (!/^[0-9]+(\.[0-9]+)*$/.test(version)) throw new Error(`"${version}" is not a CMake version (digits and dots only)`);
62
+ const before = cppSdkPin(root);
63
+ const files = setCppSdkPin(root, version);
64
+ log.ok(`rb_sdk ${before ?? "(unpinned)"} → ${version} in ${files.map((f) => f.slice(root.length + 1)).join(", ")}`);
65
+ log.info("The dev image is rebuilt with this SDK on the next `muscat-pk build` / `dev:back`.");
66
+ return;
67
+ }
68
+ log.info("Usage: muscat-pk sdk <list|update [version]>");
69
+ }
70
+
22
71
  export async function runSdk(args = []) {
23
72
  const sub = args[0];
24
73
  const root = findPluginRoot();
74
+ const runtime = readManifest(root)?.runtime ?? {};
75
+ if (runtime.type === "cpp" || (runtime.type === "ros2" && runtime.rosClient === "rclcpp")) return runCppSdk(sub, args[1], root);
25
76
  const reqs = resolve(root, "requirements.txt");
26
77
 
27
78
  if (sub === "list") {
@@ -37,28 +88,27 @@ export async function runSdk(args = []) {
37
88
  if (sub === "update") {
38
89
  let version = args[1];
39
90
  if (!version) {
40
- const versions = await fetchVersions();
41
- version = versions[0] || "latest";
91
+ // info.version = latest stable
92
+ version = (await fetchPypi())?.info?.version;
93
+ if (!version) throw new Error("could not fetch the latest version (offline?). Pass one: muscat-pk sdk update <version>");
42
94
  }
95
+ // rainbow-rb-* pin each other exactly — bump them together.
43
96
  let content = existsSync(reqs) ? readFileSync(reqs, "utf8") : "";
44
- const line = `${PKG}==${version}`;
45
- if (new RegExp(`^${PKG}==`, "m").test(content)) {
46
- content = content.replace(new RegExp(`^${PKG}==.*$`, "m"), line);
47
- } else {
48
- content = (content ? content.trimEnd() + "\n" : "") + line + "\n";
97
+ const pinned = /^(rainbow-rb-[\w-]+)\s*==.*$/gim;
98
+ const bumped = [...content.matchAll(pinned)].map((m) => m[1]);
99
+ content = content.replace(pinned, (_m, name) => `${name}==${version}`);
100
+ if (!bumped.some((n) => n.toLowerCase() === PKG)) {
101
+ content = (content ? content.trimEnd() + "\n" : "") + `${PKG}==${version}\n`;
102
+ bumped.push(PKG);
49
103
  }
50
104
  writeFileSync(reqs, content);
51
- log.ok(`requirements.txt → ${line}`);
105
+ log.ok(`requirements.txt → ${bumped.map((n) => `${n}==${version}`).join(", ")}`);
52
106
 
53
- const py = resolve(root, ".venv", "bin", "python");
54
- if (existsSync(py)) {
55
- log.step("pip install -r requirements.txt…");
56
- spawnSync(py, ["-m", "pip", "install", "-q", "-r", reqs], {
57
- cwd: root,
58
- stdio: "inherit",
59
- });
107
+ if (existsSync(venvPython(resolve(root, ".venv")))) {
108
+ log.step("refreshing .venv (for your editor)…");
109
+ if (!ensureIdeVenv(root)) log.warn(".venv refresh failed — the Docker dev container is unaffected.");
60
110
  } else {
61
- log.warn(".venv not found; run `muscat-pk dev:back` to install.");
111
+ log.info("Installed automatically on the next `muscat-pk dev` / `dev:back` (Docker container).");
62
112
  }
63
113
  return;
64
114
  }
@@ -0,0 +1,177 @@
1
+ // `dev --sim <model>`: robot simulators next to the local Muscat, for developing without a robot.
2
+ // Cobot: coreSW (rainbowroboticsofficial/cobot-sim) per component, joined to common's Zenoh.
3
+ // RB-Y1: rby1-sim + Xvfb built locally (it needs a GLFW window; there is no headless mode yet).
4
+ import { spawnSync } from "node:child_process";
5
+ import { canSkipImageBuild, log } from "./util.mjs";
6
+ import { MUSCAT_NETWORK } from "./muscatLocal.mjs";
7
+
8
+ const COBOT_IMAGE = "rainbowroboticsofficial/cobot-sim";
9
+ const RBY1_IMAGE = "rainbowroboticsofficial/rby1-sim";
10
+ const RBY1_LOCAL = "muscat-pk-rby1-sim";
11
+ const RBY1_CONTAINER = "muscat-pk-sim-rby1";
12
+ const SIM_LABEL = "com.rainbow-robotics.muscat-pk.sim";
13
+
14
+ // ponytail: model table in code; move to S3 version.json when sims are versioned with Muscat (amr: no sim yet)
15
+ // sim name → coreSW RB_MODEL per component, and the Muscat robot model it registers as.
16
+ const COBOT_MODELS = {
17
+ "500920": { robotModel: "C500920", models: ["500920"] },
18
+ RBY2: { robotModel: "RBY2", models: ["C500880", "C501880", "C502880"] },
19
+ };
20
+ const RBY1_TYPES = ["a", "m", "ub"];
21
+
22
+ // rby1-sim renders MuJoCo with software GL every frame; one GL thread and a tiny screen cut it from
23
+ // ~3.5 cores to ~1. The cap keeps a busy Mac usable; the image already skips RT-loop timeouts.
24
+ const RBY1_DOCKERFILE = `ARG BASE=0.10.6-m_v1.3
25
+ FROM ${RBY1_IMAGE}:\${BASE}
26
+ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends xvfb && rm -rf /var/lib/apt/lists/*
27
+ ENV LP_NUM_THREADS=0 DISPLAY=:99
28
+ CMD ["sh", "-c", "Xvfb :99 -screen 0 320x240x16 -nolisten tcp & sleep 1; exec ./app_main"]
29
+ `;
30
+
31
+ export const SIM_NAMES = [...Object.keys(COBOT_MODELS), ...RBY1_TYPES.map((t) => `rby1-${t}`)];
32
+
33
+ /** `muscat-pk sim list`: "<name>\t<what runs>" per line, for scripts and the VS Code Dev picker. */
34
+ export function runSimList() {
35
+ for (const [name, { robotModel, models }] of Object.entries(COBOT_MODELS))
36
+ console.log(`${name}\tcobot · ${robotModel}${models.length > 1 ? ` (${models.join(", ")})` : ""}`);
37
+ for (const t of RBY1_TYPES) console.log(`rby1-${t}\tRB-Y1 ${t.toUpperCase()}`);
38
+ }
39
+
40
+ /** "500920" | "RBY2" | "rby1-m" | "rby1-m_v1.2" → { services, env, ... } or null. */
41
+ export function parseSim(name) {
42
+ if (COBOT_MODELS[name]) return { name, kind: "cobot", services: ["log", "manipulate"], env: {}, ...COBOT_MODELS[name] };
43
+ const m = /^rby1-(a|m|ub)(?:_(v[\d.]+))?$/.exec(name ?? "");
44
+ if (m) return { name, kind: "rby1", services: ["log", "rby1"], env: { RBY1_HOST: RBY1_CONTAINER }, robotModel: m[1], type: m[1], version: m[2] };
45
+ return null;
46
+ }
47
+
48
+ /** Only an explicit --sim <name> runs a simulator. */
49
+ export function resolveSim(simArg, manifest) {
50
+ if (simArg === undefined) return null;
51
+ const sim = typeof simArg === "string" ? parseSim(simArg) : null;
52
+ if (!sim) throw new Error(`--sim takes one of: ${SIM_NAMES.join(", ")} (rby1 also takes rby1-m_v1.2)`);
53
+ const models = Array.isArray(manifest.targetModels) ? manifest.targetModels : null;
54
+ if (models && !models.includes(simArg)) log.warn(`--sim ${simArg} is not in manifest targetModels (${models.join(", ")})`);
55
+ return sim;
56
+ }
57
+
58
+ async function hubTags(repo) {
59
+ const res = await fetch(`https://hub.docker.com/v2/repositories/${repo}/tags?page_size=100&ordering=last_updated`, {
60
+ signal: AbortSignal.timeout(10_000),
61
+ });
62
+ if (!res.ok) throw new Error(`Docker Hub ${repo}: HTTP ${res.status}`);
63
+ return (await res.json()).results.map((t) => t.name);
64
+ }
65
+
66
+ // "0.10.6-m_v1.3" → [0, 10, 6, 1, 3]
67
+ const semver = (v) => v.split(/[._v-]/).map(Number).filter((n) => !Number.isNaN(n));
68
+ const newer = (a, b) => {
69
+ const [x, y] = [semver(a), semver(b)];
70
+ for (let i = 0; i < Math.max(x.length, y.length); i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) - (y[i] ?? 0);
71
+ return 0;
72
+ };
73
+
74
+ // Latest published tag; offline falls back to whatever was pulled before.
75
+ async function latestTag(repo, pattern) {
76
+ try {
77
+ const tags = (await hubTags(repo)).filter((t) => pattern.test(t));
78
+ if (tags.length) return tags.sort(newer).at(-1);
79
+ } catch (e) {
80
+ log.warn(`${e.message} — using a local image`);
81
+ }
82
+ const tags = localTags(repo).filter((t) => pattern.test(t));
83
+ if (!tags.length) throw new Error(`no ${repo} image matching ${pattern} (offline and nothing pulled yet)`);
84
+ return tags.sort(newer).at(-1);
85
+ }
86
+
87
+ function localTags(repo) {
88
+ const out = spawnSync("docker", ["images", repo, "--format", "{{.Tag}}"], { encoding: "utf8" }).stdout ?? "";
89
+ return out.split("\n").map((t) => t.trim()).filter(Boolean);
90
+ }
91
+
92
+ // A new sim release replaces the previous one (100–700MB each); other model types' tags stay.
93
+ function removeOldTags(repo, pattern, keep) {
94
+ for (const tag of localTags(repo).filter((t) => t !== keep && pattern.test(t)))
95
+ spawnSync("docker", ["rmi", `${repo}:${tag}`], { stdio: "ignore" });
96
+ }
97
+
98
+ function pull(image) {
99
+ if (spawnSync("docker", ["image", "inspect", image], { stdio: "ignore" }).status === 0) return;
100
+ log.step(`docker pull ${image}…`);
101
+ if (spawnSync("docker", ["pull", image], { stdio: "inherit" }).status !== 0) throw new Error(`docker pull ${image} failed`);
102
+ }
103
+
104
+ function run(name, args) {
105
+ spawnSync("docker", ["rm", "-f", name], { stdio: "ignore" });
106
+ const r = spawnSync("docker", [
107
+ // --ipc host like the robot: the cobot coreSWs share one shm segment (RB_MESSAGE_SHM, a slot per model).
108
+ "run", "-d", "--name", name, "--network", MUSCAT_NETWORK, "--ipc", "host",
109
+ "--label", "com.rainbow-robotics.muscat-pk=true", "--label", `${SIM_LABEL}=true`,
110
+ ...args,
111
+ ], { stdio: ["ignore", "ignore", "inherit"] });
112
+ if (r.status !== 0) throw new Error(`failed to start ${name}`);
113
+ }
114
+
115
+ // The Muscat UI (robot info, components, connection state) follows the registered robot model.
116
+ async function registerRobot(robotModel) {
117
+ const res = await fetch("http://127.0.0.1:3000/common/upsert-robot-info", {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify({ robotModel }),
121
+ });
122
+ if (!res.ok) throw new Error(`upsert-robot-info ${robotModel}: HTTP ${res.status}`);
123
+ log.step(`robot model ${robotModel} registered in the local Muscat`);
124
+ }
125
+
126
+ /** Start the simulator containers and register the robot model; returns the container names. */
127
+ export async function startSim(sim) {
128
+ const names = await startContainers(sim);
129
+ await registerRobot(sim.robotModel);
130
+ return names;
131
+ }
132
+
133
+ // Needs the local Muscat on MUSCAT_NETWORK.
134
+ async function startContainers(sim) {
135
+ stopSims();
136
+ if (sim.kind === "cobot") {
137
+ const pattern = /^\d+\.\d+\.\d+$/;
138
+ const tag = await latestTag(COBOT_IMAGE, pattern);
139
+ const image = `${COBOT_IMAGE}:${tag}`;
140
+ pull(image);
141
+ removeOldTags(COBOT_IMAGE, pattern, tag);
142
+ const names = sim.models.map((model) => `muscat-pk-sim-${model}`);
143
+ sim.models.forEach((model, i) => run(names[i], [
144
+ "--cap-add", "SYS_NICE", "--ulimit", "rtprio=99",
145
+ "-e", `RB_MODEL=${model}`,
146
+ "-e", "RB_ZENOH_CONNECT=tcp/muscat-pk-muscat:7447",
147
+ image,
148
+ ]));
149
+ log.step(`cobot sim ${sim.name} (${image}) → ${sim.models.join(", ")}`);
150
+ return names;
151
+ }
152
+
153
+ const pattern = sim.version ? new RegExp(`^\\d+\\.\\d+\\.\\d+-${sim.type}_${sim.version.replace(/\./g, "\\.")}$`) : new RegExp(`^\\d+\\.\\d+\\.\\d+-${sim.type}_v[\\d.]+$`);
154
+ const base = await latestTag(RBY1_IMAGE, pattern);
155
+ const image = `${RBY1_LOCAL}:${base}`;
156
+ if (!canSkipImageBuild(image)) {
157
+ log.step(`building ${image} (rby1-sim + Xvfb, first run pulls ~150MB)…`);
158
+ const r = spawnSync("docker", ["build", "-t", image, "--build-arg", `BASE=${base}`, "--label", "com.rainbow-robotics.muscat-pk=true", "-"], {
159
+ input: RBY1_DOCKERFILE,
160
+ stdio: ["pipe", "inherit", "inherit"],
161
+ });
162
+ if (r.status !== 0) throw new Error(`docker build ${image} failed`);
163
+ }
164
+ removeOldTags(RBY1_LOCAL, pattern, base);
165
+ removeOldTags(RBY1_IMAGE, pattern, base);
166
+ run(RBY1_CONTAINER, ["--cpus", process.env.MUSCAT_PK_RBY1_CPUS || "1.5", image]);
167
+ log.step(`rby1 sim ${base} → ${RBY1_CONTAINER}:50051`);
168
+ return [RBY1_CONTAINER];
169
+ }
170
+
171
+ export function stopSims() {
172
+ const ids = (spawnSync("docker", ["ps", "-aq", "--filter", `label=${SIM_LABEL}=true`], { encoding: "utf8" }).stdout ?? "").split("\n").filter(Boolean);
173
+ if (!ids.length) return;
174
+ // The cobot coreSWs share these under --ipc host; drop them so the next run starts from a fresh segment.
175
+ spawnSync("docker", ["exec", ids[0], "sh", "-c", "rm -f /dev/shm/RB_COBOT_SHM /dev/shm/RB_MESSAGE_SHM"], { stdio: "ignore" });
176
+ spawnSync("docker", ["rm", "-f", ...ids], { stdio: "ignore" });
177
+ }