@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
package/cli/src/util.mjs CHANGED
@@ -1,8 +1,11 @@
1
1
  // Shared helpers for muscat-pk.
2
- import { spawnSync } from "node:child_process";
3
- import { existsSync, readFileSync } from "node:fs";
4
- import { dirname, resolve } from "node:path";
2
+ import { execSync, spawn, spawnSync } from "node:child_process";
3
+ import { createHash } from "node:crypto";
4
+ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
5
+ import { dirname, relative, resolve, sep } from "node:path";
6
+ import { tmpdir } from "node:os";
5
7
  import { fileURLToPath } from "node:url";
8
+ import { createInterface } from "node:readline/promises";
6
9
 
7
10
  /** True if an executable is on PATH (cross-platform). */
8
11
  export function hasBin(bin) {
@@ -37,6 +40,63 @@ export function venvPython(venvDir) {
37
40
  : resolve(venvDir, "bin", "python");
38
41
  }
39
42
 
43
+ /**
44
+ * Create/refresh a local `.venv` with requirements.txt installed, for the
45
+ * editor/linter only (the backend actually runs in Docker). Uses `uv` if
46
+ * present, else the system Python. Returns true if the venv is ready.
47
+ */
48
+ export function ensureIdeVenv(root) {
49
+ const venvDir = resolve(root, ".venv");
50
+ const py = venvPython(venvDir);
51
+
52
+ if (!existsSync(py)) {
53
+ let created;
54
+ if (hasBin("uv")) {
55
+ created = spawnSync("uv", ["venv", venvDir, "--python", "3.12", "--seed"], { cwd: root, stdio: "ignore" });
56
+ } else {
57
+ const { cmd } = detectPythonCmd();
58
+ created = spawnSync(cmd[0], [...cmd.slice(1), "-m", "venv", venvDir], { cwd: root, stdio: "ignore" });
59
+ }
60
+ if (created.status !== 0 || !existsSync(py)) return false;
61
+ }
62
+
63
+ const pip = (pkgs) => spawnSync(py, ["-m", "pip", "install", "-q", ...pkgs], { cwd: root, stdio: "ignore" }).status === 0;
64
+
65
+ // host_agent.py only; not in requirements.txt, so it never ships in the ZIP.
66
+ if (!pip([HOST_AGENT_ZENOH])) return false;
67
+
68
+ const reqs = resolve(root, "requirements.txt");
69
+ if (!existsSync(reqs)) return true;
70
+ if (process.platform !== "darwin") return pip(["-r", reqs]);
71
+
72
+ // No macOS wheels for rainbow-*: install the rest + type stubs.
73
+ const lines = readFileSync(reqs, "utf8").split("\n");
74
+ const isRainbow = (l) => /^\s*rainbow-/i.test(l);
75
+ const sdkPin = lines.map((l) => l.match(/^\s*rainbow-rb-sdk\s*==\s*(\S+)/i)?.[1]).find(Boolean);
76
+ const rest = lines.filter((l) => l.trim() && !l.trim().startsWith("#") && !isRainbow(l)).map((l) => l.trim());
77
+ if (rest.length && !pip(rest)) return false;
78
+ pip([`rainbow-rb-sdk-stubs${sdkPin ? `==${sdkPin}` : ""}`]); // not on PyPI for every SDK version yet
79
+ ensurePyrightMissingSourceOff(root);
80
+ return true;
81
+ }
82
+
83
+ export const HOST_AGENT_ZENOH = "eclipse-zenoh==1.8.0"; // match the robot's zenoh-c minor version
84
+
85
+ function ensurePyrightMissingSourceOff(root) {
86
+ const path = resolve(root, "pyrightconfig.json");
87
+ let cfg = {};
88
+ if (existsSync(path)) {
89
+ try {
90
+ cfg = JSON.parse(readFileSync(path, "utf8"));
91
+ } catch {
92
+ return; // user-edited / JSONC — leave it alone
93
+ }
94
+ }
95
+ if (cfg.reportMissingModuleSource === "none") return;
96
+ cfg.reportMissingModuleSource = "none";
97
+ writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n");
98
+ }
99
+
40
100
  /** Published install/CLI package name. Author imports use @rainbow-robotics/plugin-ui separately. */
41
101
  export const KIT_PUBLISHED_NAME = "@rainbow-robotics/muscat-plugin-kit";
42
102
 
@@ -65,7 +125,96 @@ export function parseArgs(args, { booleans = [] } = {}) {
65
125
  return out;
66
126
  }
67
127
 
128
+ /** Prompt user for input. Returns trimmed answer or fallback (non-TTY or empty). */
129
+ export async function prompt(question, fallback = "") {
130
+ if (!process.stdin.isTTY) return fallback;
131
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
132
+ const answer = (await rl.question(question)).trim();
133
+ rl.close();
134
+ return answer || fallback;
135
+ }
136
+
137
+ const LOCAL_HOSTS = new Set(["", "localhost", "127.0.0.1", "::1"]);
138
+
139
+ /**
140
+ * Ask for robot IP (and optional token if remote). Returns { robotIp, robotToken }.
141
+ * Skips prompts if already set in process.env (from a parent command like `dev`).
142
+ * Supports --robot-ip / --robot-token flags for non-interactive use.
143
+ * An empty token is allowed: the robot API is then called without an Authorization header.
144
+ */
145
+ export async function promptRobotConnection(args = []) {
146
+ const parsed = parseArgs(args);
147
+ // A simulator stands in for the robot.
148
+ if (parsed.sim !== undefined) return { robotIp: "", robotToken: "" };
149
+ const robotIp =
150
+ parsed["robot-ip"] ??
151
+ process.env.MUSCAT_ROBOT_HOST ??
152
+ (await prompt(" Robot IP (skip if no robot): "));
153
+
154
+ if (LOCAL_HOSTS.has(robotIp)) return { robotIp, robotToken: "" };
155
+
156
+ const robotToken =
157
+ parsed["robot-token"] ??
158
+ process.env.MUSCAT_ROBOT_TOKEN ??
159
+ (await prompt(" SDK Token (optional, Enter to skip): "));
160
+
161
+ return { robotIp, robotToken };
162
+ }
163
+
68
164
  /** Walk up from cwd to find the plugin project root (has manifest.json). */
165
+ // C++ backends pin the SDK where they declare it: find_package(rb_sdk <version> EXACT REQUIRED) in
166
+ // cpp/CMakeLists.txt or ros2_ws/src/<pkg>/CMakeLists.txt. The dev image installs that version.
167
+ const RB_SDK_FIND = /find_package\(\s*rb_sdk(\s+[0-9][\w.]*)?/;
168
+
169
+ export function cppSdkFiles(root) {
170
+ const files = [resolve(root, "cpp", "CMakeLists.txt")];
171
+ const src = resolve(root, "ros2_ws", "src");
172
+ if (existsSync(src)) for (const pkg of readdirSync(src)) files.push(resolve(src, pkg, "CMakeLists.txt"));
173
+ return files.filter((f) => existsSync(f) && RB_SDK_FIND.test(readFileSync(f, "utf8")));
174
+ }
175
+
176
+ export function cppSdkPin(root) {
177
+ for (const f of cppSdkFiles(root)) {
178
+ const version = readFileSync(f, "utf8").match(RB_SDK_FIND)[1]?.trim();
179
+ if (version) return version;
180
+ }
181
+ return undefined;
182
+ }
183
+
184
+ /** SDK version for the C++ dev image: MUSCAT_PK_RB_SDK_VERSION overrides the project's CMake pin. */
185
+ export function cppSdkVersion(root = pluginRootOrNull()) {
186
+ if (process.env.MUSCAT_PK_RB_SDK_VERSION) return process.env.MUSCAT_PK_RB_SDK_VERSION;
187
+ return root ? cppSdkPin(root) : undefined; // not in a plugin project: the Dockerfile default
188
+ }
189
+
190
+ export function pluginRootOrNull() {
191
+ try {
192
+ return findPluginRoot();
193
+ } catch {
194
+ return null;
195
+ }
196
+ }
197
+
198
+ /** Rewrite the pinned version in every CMakeLists.txt that finds rb_sdk; returns the files changed. */
199
+ export function setCppSdkPin(root, version) {
200
+ const files = cppSdkFiles(root);
201
+ for (const f of files) {
202
+ const text = readFileSync(f, "utf8").replace(RB_SDK_FIND, (m, old) => `find_package(rb_sdk ${version}${old ? "" : " EXACT"}`);
203
+ writeFileSync(f, text);
204
+ }
205
+ return files;
206
+ }
207
+
208
+ // Per-project dev output (staged UI assets, ROS peers, C++ sysroot). The leading dot hides it on macOS
209
+ // and Linux; Windows needs the hidden attribute, set on every call so folders made by older kits hide too.
210
+ export const DEV_DIR = ".muscat-dev";
211
+ export function ensureDevDir(root) {
212
+ const dir = resolve(root, DEV_DIR);
213
+ mkdirSync(dir, { recursive: true });
214
+ if (process.platform === "win32") spawnSync("attrib", ["+h", dir], { stdio: "ignore" });
215
+ return dir;
216
+ }
217
+
69
218
  export function findPluginRoot(start = process.cwd()) {
70
219
  let dir = resolve(start);
71
220
  for (;;) {
@@ -79,24 +228,175 @@ export function findPluginRoot(start = process.cwd()) {
79
228
  );
80
229
  }
81
230
 
82
- const ORANGE = "\x1b[33m";
83
- const RESET = "\x1b[0m";
231
+ /** Walk up from cwd looking for a project-local muscat-pk install. */
232
+ export function resolveLocalCliBin(start = process.cwd()) {
233
+ let dir = resolve(start);
234
+ for (;;) {
235
+ const candidate = resolve(dir, "node_modules/@rainbow-robotics/muscat-plugin-kit/cli/bin/muscat-pk.mjs");
236
+ if (existsSync(candidate)) return candidate;
237
+ const parent = dirname(dir);
238
+ if (parent === dir) return null;
239
+ dir = parent;
240
+ }
241
+ }
242
+
243
+ /** True if the code currently running was installed with `npm install -g`. */
244
+ export function isNpmGlobalInstall() {
245
+ try {
246
+ const globalRoot = realpathSync(execSync("npm root -g", { encoding: "utf8" }).trim());
247
+ const rel = relative(globalRoot, realpathSync(kitRoot));
248
+ return rel === "" || (!rel.startsWith("..") && !rel.includes(":"));
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+
254
+ /** True if the code currently running is a project's local install (not global/binary). */
255
+ export function isLocalKitInstall() {
256
+ // npm -g 도 node_modules 아래에 놓이므로 global root 는 제외한다.
257
+ return kitRoot.includes(`${sep}node_modules${sep}`) && !isNpmGlobalInstall();
258
+ }
259
+
260
+ const useColor = process.env.NO_COLOR !== "1" && (process.stdout.isTTY || process.env.FORCE_COLOR);
261
+ const ansi = (code) => (s) => useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s);
262
+
263
+ export const color = {
264
+ bold: ansi("1"),
265
+ dim: ansi("2"),
266
+ red: ansi("31"),
267
+ green: ansi("32"),
268
+ yellow: ansi("33"),
269
+ blue: ansi("34"),
270
+ magenta: ansi("35"),
271
+ cyan: ansi("36"),
272
+ };
84
273
 
85
274
  export const log = {
86
- info: (...m) => console.log(...m),
87
- step: (...m) => console.log("›", ...m),
88
- ok: (...m) => console.log("✓", ...m),
89
- warn: (...m) => console.warn(`${ORANGE}⚠${RESET}`, ...m.map(s => `${ORANGE}${s}${RESET}`)),
275
+ info: (...m) => console.log(...m),
276
+ step: (...m) => console.log(color.cyan("›"), ...m.map(s => color.cyan(s))),
277
+ ok: (...m) => console.log(color.green("✓"), ...m),
278
+ warn: (...m) => console.warn(color.yellow("⚠"), ...m.map(s => color.yellow(s))),
279
+ error: (...m) => console.error(color.red("✖"), ...m.map(s => color.red(s))),
90
280
  };
91
281
 
282
+ // dev / dev:back / build run the plugin back end, the local Muscat runtime, and cpp/ros2
283
+ // compilation in Docker. Show a clear install hint when Docker is missing.
284
+ export function ensureDocker() {
285
+ if (!hasBin("docker")) {
286
+ log.warn("Docker not found.");
287
+ log.warn(" muscat-pk runs the plugin back end, the local Muscat runtime, and cpp/ros2 builds in Docker.");
288
+ log.warn(" Install Docker, then retry: https://docs.docker.com/get-docker/");
289
+ throw new Error("Docker is required for dev / dev:back / build.");
290
+ }
291
+ // The binary alone is not enough: with the daemon down every docker step fails one by one
292
+ // (Docker Desktop not started is the usual case on macOS/Windows).
293
+ if (spawnSync("docker", ["info", "--format", "{{.ServerVersion}}"], { stdio: "ignore", timeout: 15_000 }).status === 0) return;
294
+ const hint = process.platform === "linux" ? "start it (e.g. `sudo systemctl start docker`)" : "start Docker Desktop and wait until it reports running";
295
+ throw new Error(`Docker is installed but the daemon is not running — ${hint}, then retry.`);
296
+ }
297
+
298
+ /**
299
+ * Remove dangling (untagged) muscat-pk images left behind after a rebuild.
300
+ * Uses label filter so only our images are touched — other Docker images are safe.
301
+ * A rebuilt image's old layers stay in the build cache, which has no labels: drop cache
302
+ * nobody used for 30 days.
303
+ */
304
+ export function pruneMuskatImages() {
305
+ spawnSync(
306
+ "docker",
307
+ ["image", "prune", "--force", "--filter", "label=com.rainbow-robotics.muscat-pk=true"],
308
+ { stdio: "ignore" },
309
+ );
310
+ spawnSync("docker", ["builder", "prune", "--force", "--filter", "until=720h"], { stdio: "ignore" });
311
+ spawnSync("docker", ["rmi", "muscat-pk-bridge"], { stdio: "ignore" }); // earlier kits' image
312
+ }
313
+
314
+ /**
315
+ * Docker flags that let the backend take a realtime scheduling priority
316
+ * (SCHED_FIFO/RR). Neither CAP_SYS_NICE nor a non-zero RLIMIT_RTPRIO is in
317
+ * Docker's default set, so without these sched_setscheduler() fails with EPERM.
318
+ * Mirrors the robot's `muscat` container (rby2 backend/docker-compose.yml) so
319
+ * dev and the robot behave the same.
320
+ */
321
+ export function realtimeFlags() {
322
+ // memlock is bounded (not unlimited): mlockall'd pages can't be swapped out,
323
+ // so an unbounded limit lets a plugin pin RAM until the robot OOMs.
324
+ return ["--cap-add", "SYS_NICE", "--ulimit", "rtprio=99", "--ulimit", "memlock=536870912"];
325
+ }
326
+
327
+ /** True if the image already exists in the local Docker image store. */
328
+ export function dockerImageExists(image) {
329
+ return spawnSync("docker", ["image", "inspect", image], { stdio: "ignore" }).status === 0;
330
+ }
331
+
332
+ /**
333
+ * True if `docker build` should be skipped because the image is already built.
334
+ * BuildKit resolves the FROM tag over the network on every build (the
335
+ * "load metadata for docker.io/..." step) even when all layers are cached, so
336
+ * rebuilding an image we already have breaks `dev` offline for no benefit.
337
+ * Force a rebuild with MUSCAT_PK_REBUILD_IMAGE=1.
338
+ */
339
+ // Rebuild when the Dockerfile or build args change (image label holds the hash).
340
+ const IMAGE_KEY_LABEL = "com.rainbow-robotics.muscat-pk.key";
341
+
342
+ export function imageKey(dockerfile, extra = "") {
343
+ return createHash("sha256").update(readFileSync(dockerfile)).update(extra).digest("hex").slice(0, 16);
344
+ }
345
+
346
+ // MUSCAT_PK_NO_BUILD_CACHE=1 (CI): build images without Docker's layer cache, so an apt/PyPI/GitHub
347
+ // change that breaks a fresh build fails the test too instead of hiding behind a cached layer.
348
+ export const buildCacheFlags = () => (process.env.MUSCAT_PK_NO_BUILD_CACHE === "1" ? ["--no-cache"] : []);
349
+
350
+ export function imageKeyLabel(key) {
351
+ return ["--label", `${IMAGE_KEY_LABEL}=${key}`];
352
+ }
353
+
354
+ export function canSkipImageBuild(image, key = null) {
355
+ if (process.env.MUSCAT_PK_REBUILD_IMAGE === "1") return false;
356
+ if (!dockerImageExists(image)) return false;
357
+ if (!key) return true;
358
+ const r = spawnSync("docker", ["image", "inspect", "-f", `{{index .Config.Labels "${IMAGE_KEY_LABEL}"}}`, image], { encoding: "utf8" });
359
+ return r.status === 0 && r.stdout.trim() === key;
360
+ }
361
+
362
+ // Linux에서 컨테이너가 마운트에 root 소유 파일을 남기면 다음 빌드의 dist/ 삭제가 EACCES로 막힌다.
363
+ // (macOS/Windows는 Docker Desktop이 소유권을 호스트 유저로 매핑하므로 불필요)
364
+ export function dockerUserFlags() {
365
+ if (process.platform !== "linux" || typeof process.getuid !== "function") return [];
366
+ return [
367
+ "--user", `${process.getuid()}:${process.getgid()}`,
368
+ "-e", "HOME=/tmp",
369
+ "-e", "XDG_CACHE_HOME=/tmp/.cache",
370
+ ];
371
+ }
372
+
92
373
  export function humanSize(bytes) {
93
374
  if (bytes < 1024) return `${bytes} B`;
94
375
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
95
376
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
96
377
  }
97
378
 
379
+ /**
380
+ * Return the on-disk path of a bundled Dockerfile.
381
+ * In npm-install mode the real file exists under cli/docker/.
382
+ * In compiled-binary mode it doesn't — extract from the embedded snapshot to tmpdir.
383
+ */
384
+ export async function resolveDockerfile(name) {
385
+ const real = resolve(kitRoot, "cli/docker", name);
386
+ if (existsSync(real)) return real;
387
+ const { TEMPLATES } = await import("./templates-embedded.mjs");
388
+ const b64 = TEMPLATES[`docker/${name}`];
389
+ if (!b64) throw new Error(`Embedded Dockerfile missing: docker/${name}. Reinstall muscat-plugin-kit.`);
390
+ const dir = resolve(tmpdir(), "muscat-pk-docker");
391
+ mkdirSync(dir, { recursive: true });
392
+ const tmp = resolve(dir, name);
393
+ writeFileSync(tmp, Buffer.from(b64, "base64"));
394
+ return tmp;
395
+ }
396
+
98
397
  /** This kit's installed version (from its package.json). */
99
398
  export function kitVersion() {
399
+ if (process.env.MUSCAT_PK_VERSION) return process.env.MUSCAT_PK_VERSION;
100
400
  try {
101
401
  return JSON.parse(readFileSync(resolve(kitRoot, "package.json"), "utf8"))
102
402
  .version;
@@ -119,6 +419,18 @@ export function isNewer(a, b) {
119
419
  return a3 > b3;
120
420
  }
121
421
 
422
+ /** Compare "x.y.z[-dev.N]" including the dev number (a release sorts after its dev builds). <0, 0, >0. */
423
+ export function compareKitVersions(a, b) {
424
+ const parse = (v) => {
425
+ const [rel, pre] = String(v).split("-");
426
+ const nums = rel.split(".").map((n) => parseInt(n, 10) || 0);
427
+ return [...nums, pre === undefined ? Infinity : parseInt(pre.split(".").pop(), 10) || 0];
428
+ };
429
+ const pa = parse(a), pb = parse(b);
430
+ for (let i = 0; i < 4; i++) if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1;
431
+ return 0;
432
+ }
433
+
122
434
  /**
123
435
  * Non-blocking check for a newer published kit. Prints an update suggestion if
124
436
  * one exists. Fails silently when offline. Honors `RB_REGISTRY` and is skipped
@@ -144,8 +456,8 @@ export async function checkKitUpdate({ timeoutMs = 2000 } = {}) {
144
456
  [
145
457
  "",
146
458
  line,
147
- ` muscat-plugin-kit 새 버전이 있습니다: ${current} → ${latest}`,
148
- ` 업데이트: muscat-pk update`,
459
+ ` muscat-plugin-kit update available: ${current} → ${latest}`,
460
+ ` Update: muscat-pk update`,
149
461
  line,
150
462
  "",
151
463
  ].join("\n"),
@@ -155,3 +467,21 @@ export async function checkKitUpdate({ timeoutMs = 2000 } = {}) {
155
467
  /* offline or registry unreachable — ignore */
156
468
  }
157
469
  }
470
+
471
+ export function openBrowser(url) {
472
+ const cmd =
473
+ process.platform === "darwin"
474
+ ? "open"
475
+ : process.platform === "win32"
476
+ ? "start"
477
+ : "xdg-open";
478
+ try {
479
+ spawn(cmd, [url], {
480
+ stdio: "ignore",
481
+ detached: true,
482
+ shell: process.platform === "win32",
483
+ }).unref();
484
+ } catch {
485
+ /* headless / no browser — the URL is printed anyway */
486
+ }
487
+ }
@@ -0,0 +1,83 @@
1
+ // Remote-robot Zenoh dev networking helpers, shared by `muscat-pk dev` and
2
+ // standalone `muscat-pk dev:back`.
3
+ //
4
+ // The plugin backend runs in a Docker container. When it connects to a REMOTE
5
+ // robot, rainbow.rb_sdk registers this machine as a Zenoh peer and the robot's
6
+ // `common` service connects BACK to the advertised ip:port to form the mesh.
7
+ // Inside a container, the SDK's own _detect_local_ip() returns the container's
8
+ // internal IP (unreachable from the robot) and lets the OS pick a random listen
9
+ // port (unpublished) — so the reverse connection never lands and SDK calls like
10
+ // call_move_j get no reply. We fix both ends:
11
+ // - MUSCAT_ZENOH_ADVERTISE_HOST → the host's LAN IP toward the robot, which
12
+ // devBack's entry script uses to override _detect_local_ip.
13
+ // - MUSCAT_ZENOH_PORT → a fixed, host-published listen port the robot can reach.
14
+ import { createSocket } from "node:dgram";
15
+ import { createServer } from "node:net";
16
+ import { release } from "node:os";
17
+
18
+ export const DEFAULT_ZENOH_DEV_PORT = 53917;
19
+ export const LOCAL_HOSTS = new Set(["", "localhost", "127.0.0.1", "::1"]);
20
+
21
+ // Native Linux (not WSL) has real Docker host networking — dev:back runs with
22
+ // --network host and reaches the robot/bridge directly. macOS/Windows/WSL don't.
23
+ export const isWsl = () => process.platform === "linux" && /microsoft|wsl/i.test(release());
24
+ // MUSCAT_PK_HOST_NETWORK=0 (CI): run the Docker Desktop (bridge) path on native Linux too.
25
+ export const usesHostNetwork = () =>
26
+ process.env.MUSCAT_PK_HOST_NETWORK !== "0" && process.platform === "linux" && !isWsl();
27
+
28
+ export const isRemoteRobot = (robotIp) => !!robotIp && !LOCAL_HOSTS.has(robotIp);
29
+
30
+ /** The local interface IP the OS would use to reach `host` (no packet sent). */
31
+ export function detectRouteAddress(host) {
32
+ return new Promise((resolve) => {
33
+ const socket = createSocket("udp4");
34
+ socket.once("error", () => {
35
+ socket.close();
36
+ resolve("");
37
+ });
38
+ socket.connect(80, host, () => {
39
+ const address = socket.address();
40
+ socket.close();
41
+ resolve(typeof address === "object" ? address.address : "");
42
+ });
43
+ });
44
+ }
45
+
46
+ /** `preferred` if free, otherwise an OS-assigned free port. */
47
+ export function getFreePort(preferred) {
48
+ return new Promise((resolve) => {
49
+ const srv = createServer();
50
+ srv.once("error", () => {
51
+ const fallback = createServer();
52
+ fallback.listen(0, "0.0.0.0", () => {
53
+ const address = fallback.address();
54
+ const port = typeof address === "object" && address ? address.port : preferred;
55
+ fallback.close(() => resolve(port));
56
+ });
57
+ });
58
+ srv.listen(preferred, "0.0.0.0", () => srv.close(() => resolve(preferred)));
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Compute and set MUSCAT_ZENOH_ADVERTISE_HOST / MUSCAT_ZENOH_PORT for a remote
64
+ * robot, unless already set by a parent command (idempotent). No-op for a local
65
+ * robot. Returns the resolved { advertiseHost, zenohPort } (or null if local).
66
+ */
67
+ export async function ensureRemoteZenohEnv(robotIp, { log } = {}) {
68
+ if (!isRemoteRobot(robotIp)) return null;
69
+ // Already set (e.g. `muscat-pk dev` computed it before starting the bridge).
70
+ if (process.env.MUSCAT_ZENOH_PORT) {
71
+ return {
72
+ advertiseHost: process.env.MUSCAT_ZENOH_ADVERTISE_HOST || "",
73
+ zenohPort: Number(process.env.MUSCAT_ZENOH_PORT),
74
+ };
75
+ }
76
+ const advertiseHost =
77
+ process.env.MUSCAT_ZENOH_ADVERTISE_HOST || (await detectRouteAddress(robotIp));
78
+ const zenohPort = await getFreePort(DEFAULT_ZENOH_DEV_PORT);
79
+ if (advertiseHost) process.env.MUSCAT_ZENOH_ADVERTISE_HOST = advertiseHost;
80
+ process.env.MUSCAT_ZENOH_PORT = String(zenohPort);
81
+ log?.step?.(`remote Zenoh dev peer → ${advertiseHost || "(auto)"}:${zenohPort}`);
82
+ return { advertiseHost, zenohPort };
83
+ }
@@ -0,0 +1,121 @@
1
+ // Minimal ZIP writer (deflate) for `muscat-pk build`, instead of the `zip` CLI that Windows lacks.
2
+ //
3
+ // Entries carry Unix modes because the robot is Linux: C++ binaries and ROS 2 node scripts must stay
4
+ // executable. macOS/Linux store each file's real mode. Windows (NTFS) has no executable bit, so there
5
+ // an ELF binary or a `#!` script is stored as 0755 and everything else as 0644.
6
+ // Like `zip -r`, symlinks are followed and their targets' contents stored. No ZIP64: a plugin ZIP over
7
+ // 4 GB or 65535 entries is refused.
8
+ import { closeSync, openSync, readdirSync, readFileSync, readSync, statSync, writeFileSync } from "node:fs";
9
+ import { join, relative, resolve, sep } from "node:path";
10
+ import { deflateRawSync } from "node:zlib";
11
+
12
+ const CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
13
+ let c = n;
14
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
15
+ return c >>> 0;
16
+ });
17
+
18
+ function crc32(buf) {
19
+ let c = 0xffffffff;
20
+ for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
21
+ return (c ^ 0xffffffff) >>> 0;
22
+ }
23
+
24
+ function dosTime(date) {
25
+ const d = date.getFullYear() < 1980 ? new Date(1980, 0, 1) : date;
26
+ return {
27
+ time: (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1),
28
+ date: ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(),
29
+ };
30
+ }
31
+
32
+ function looksExecutable(file) {
33
+ const head = Buffer.alloc(4);
34
+ const fd = openSync(file, "r");
35
+ try {
36
+ readSync(fd, head, 0, 4, 0);
37
+ } finally {
38
+ closeSync(fd);
39
+ }
40
+ return head.equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])) || (head[0] === 0x23 && head[1] === 0x21);
41
+ }
42
+
43
+ function unixMode(file, stat) {
44
+ if (stat.isDirectory()) return 0o40755;
45
+ if (process.platform !== "win32") return 0o100000 | (stat.mode & 0o777);
46
+ return 0o100000 | (looksExecutable(file) ? 0o755 : 0o644);
47
+ }
48
+
49
+ function collect(root, rel, out) {
50
+ const full = resolve(root, rel);
51
+ const stat = statSync(full);
52
+ if (stat.isDirectory()) {
53
+ out.push({ name: `${rel.split(sep).join("/")}/`, full, stat });
54
+ for (const child of readdirSync(full).sort()) collect(root, join(rel, child), out);
55
+ } else {
56
+ out.push({ name: rel.split(sep).join("/"), full, stat });
57
+ }
58
+ }
59
+
60
+ /** Write `entries` (paths relative to `cwd`, files or directories) into a new ZIP at `zipPath`. */
61
+ export function writeZip(zipPath, entries, cwd) {
62
+ const items = [];
63
+ for (const e of entries) collect(cwd, relative(cwd, resolve(cwd, e)), items);
64
+ if (items.length > 0xffff) throw new Error(`too many files for a ZIP (${items.length})`);
65
+
66
+ const chunks = [];
67
+ const central = [];
68
+ let offset = 0;
69
+ for (const { name, full, stat } of items) {
70
+ const isDir = name.endsWith("/");
71
+ const data = isDir ? Buffer.alloc(0) : readFileSync(full);
72
+ const deflated = isDir ? data : deflateRawSync(data);
73
+ const stored = deflated.length >= data.length;
74
+ const body = stored ? data : deflated;
75
+ const crc = crc32(data);
76
+ const nameBuf = Buffer.from(name, "utf8");
77
+ const { time, date } = dosTime(stat.mtime);
78
+ if (offset + body.length > 0xffffffff) throw new Error("plugin ZIP would exceed 4 GB");
79
+
80
+ const local = Buffer.alloc(30);
81
+ local.writeUInt32LE(0x04034b50, 0);
82
+ local.writeUInt16LE(20, 4); // version needed
83
+ local.writeUInt16LE(0x0800, 6); // UTF-8 names
84
+ local.writeUInt16LE(stored ? 0 : 8, 8);
85
+ local.writeUInt16LE(time, 10);
86
+ local.writeUInt16LE(date, 12);
87
+ local.writeUInt32LE(crc, 14);
88
+ local.writeUInt32LE(body.length, 18);
89
+ local.writeUInt32LE(data.length, 22);
90
+ local.writeUInt16LE(nameBuf.length, 26);
91
+ local.writeUInt16LE(0, 28);
92
+
93
+ const cdir = Buffer.alloc(46);
94
+ cdir.writeUInt32LE(0x02014b50, 0);
95
+ cdir.writeUInt16LE((3 << 8) | 20, 4); // made by Unix, so unzip applies the mode below
96
+ cdir.writeUInt16LE(20, 6);
97
+ cdir.writeUInt16LE(0x0800, 8);
98
+ cdir.writeUInt16LE(stored ? 0 : 8, 10);
99
+ cdir.writeUInt16LE(time, 12);
100
+ cdir.writeUInt16LE(date, 14);
101
+ cdir.writeUInt32LE(crc, 16);
102
+ cdir.writeUInt32LE(body.length, 20);
103
+ cdir.writeUInt32LE(data.length, 24);
104
+ cdir.writeUInt16LE(nameBuf.length, 28);
105
+ cdir.writeUInt32LE(((unixMode(full, stat) << 16) | (isDir ? 0x10 : 0)) >>> 0, 38);
106
+ cdir.writeUInt32LE(offset, 42);
107
+
108
+ chunks.push(local, nameBuf, body);
109
+ central.push(cdir, nameBuf);
110
+ offset += local.length + nameBuf.length + body.length;
111
+ }
112
+
113
+ const centralBuf = Buffer.concat(central);
114
+ const end = Buffer.alloc(22);
115
+ end.writeUInt32LE(0x06054b50, 0);
116
+ end.writeUInt16LE(items.length, 8);
117
+ end.writeUInt16LE(items.length, 10);
118
+ end.writeUInt32LE(centralBuf.length, 12);
119
+ end.writeUInt32LE(offset, 16);
120
+ writeFileSync(zipPath, Buffer.concat([...chunks, centralBuf, end]));
121
+ }