@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.9 → 1.0.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 (272) hide show
  1. package/README.md +113 -49
  2. package/cli/README.md +191 -5
  3. package/cli/bin/create-muscat-plugin.mjs +1 -1
  4. package/cli/bin/muscat-pk.mjs +44 -26
  5. package/cli/bundle-templates.mjs +28 -0
  6. package/cli/docker/Dockerfile.bridge +17 -0
  7. package/cli/docker/Dockerfile.cpp +22 -0
  8. package/cli/docker/Dockerfile.dev +1 -0
  9. package/cli/docker/Dockerfile.python +20 -0
  10. package/cli/docker/bridge-package.json +8 -0
  11. package/cli/prepack.mjs +10 -0
  12. package/cli/src/__pycache__/host_agent.cpython-37.pyc +0 -0
  13. package/cli/src/bridge.mjs +95 -0
  14. package/cli/src/build.mjs +345 -85
  15. package/cli/src/check-package.mjs +8 -23
  16. package/cli/src/cpp.mjs +61 -0
  17. package/cli/src/dev.mjs +156 -4
  18. package/cli/src/devBack.mjs +505 -57
  19. package/cli/src/devUi.mjs +167 -22
  20. package/cli/src/esbuild.mjs +27 -10
  21. package/cli/src/host_agent.py +162 -0
  22. package/cli/src/manifest.mjs +208 -0
  23. package/cli/src/mcp.mjs +617 -0
  24. package/cli/src/new.mjs +170 -67
  25. package/cli/src/pluginDevServer.mjs +112 -0
  26. package/cli/src/ports.mjs +160 -0
  27. package/cli/src/python.mjs +109 -0
  28. package/cli/src/ros2.mjs +43 -18
  29. package/cli/src/sdk.mjs +11 -9
  30. package/cli/src/socketBridge.mjs +133 -0
  31. package/cli/src/storybook.mjs +168 -0
  32. package/cli/src/type.mjs +52 -29
  33. package/cli/src/uninstall.mjs +98 -0
  34. package/cli/src/update.mjs +259 -30
  35. package/cli/src/util.mjs +123 -9
  36. package/cli/src/zenohDev.mjs +75 -0
  37. package/cli/storybook-dist/_design-tokens.css +4957 -0
  38. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
  39. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
  40. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
  41. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
  42. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
  43. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
  44. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
  45. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
  46. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
  47. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
  48. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
  49. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
  50. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +44 -0
  51. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
  52. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
  53. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
  54. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
  55. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
  56. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
  57. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
  58. package/cli/storybook-dist/_stories-src/RBIcons/RBMenuTreeIcons.stories.tsx +108 -0
  59. package/cli/storybook-dist/_stories-src/RBIcons/RBProgramIcons.stories.tsx +116 -0
  60. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
  61. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
  62. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
  63. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
  64. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +119 -0
  65. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
  66. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
  67. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
  68. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
  69. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
  70. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
  71. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
  72. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
  73. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
  74. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
  75. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
  76. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
  77. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
  78. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
  79. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
  84. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
  85. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
  86. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
  87. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
  88. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
  89. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
  90. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
  91. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
  92. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
  93. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
  94. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
  95. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
  96. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
  97. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
  98. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
  99. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
  100. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
  101. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
  102. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
  103. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
  104. package/cli/storybook-dist/assets/Color-YHDXOIA2-ttZJDaKn.js +1 -0
  105. package/cli/storybook-dist/assets/Docs-B1jnlXHd.js +12 -0
  106. package/cli/storybook-dist/assets/Docs-B7dRsHh7.js +8 -0
  107. package/cli/storybook-dist/assets/Docs-C68t3mnn.js +20 -0
  108. package/cli/storybook-dist/assets/Docs-D7rPMMhK.js +7 -0
  109. package/cli/storybook-dist/assets/Docs-DM98U-ZG.js +7 -0
  110. package/cli/storybook-dist/assets/Docs-guWsANnn.js +8 -0
  111. package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-t12O9QfY.js +2 -0
  112. package/cli/storybook-dist/assets/NxAvatar.stories-ikdLTtys.js +13 -0
  113. package/cli/storybook-dist/assets/NxButton.stories-DL3S2KEV.js +20 -0
  114. package/cli/storybook-dist/assets/NxIcons.stories-Ca7qpAaO.js +3 -0
  115. package/cli/storybook-dist/assets/NxNumberInput.stories-D6NVMTos.js +26 -0
  116. package/cli/storybook-dist/assets/NxPagination.stories-BDKSlHiQ.js +11 -0
  117. package/cli/storybook-dist/assets/NxPointerHoldButton.stories-BI544zqB.js +14 -0
  118. package/cli/storybook-dist/assets/NxRangeInput.stories-BigBBkE7.js +17 -0
  119. package/cli/storybook-dist/assets/NxSelectBox.stories-Ci0IyUWd.js +17 -0
  120. package/cli/storybook-dist/assets/NxSlideRange.stories-sjnPAjFy.js +14 -0
  121. package/cli/storybook-dist/assets/NxSwitch.stories-CRxPDlJm.js +11 -0
  122. package/cli/storybook-dist/assets/NxTextArea.stories-Jh09n24O.js +11 -0
  123. package/cli/storybook-dist/assets/NxTextInput.stories-BeGCjxES.js +11 -0
  124. package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
  125. package/cli/storybook-dist/assets/RBAccordion.stories-CnC95OwM.js +17 -0
  126. package/cli/storybook-dist/assets/RBAccordionGroup.stories-og-6ePox.js +33 -0
  127. package/cli/storybook-dist/assets/RBAlert.stories-BcE9-UOT.js +17 -0
  128. package/cli/storybook-dist/assets/RBBadge.stories-DYZMXb-e.js +1 -0
  129. package/cli/storybook-dist/assets/RBBattery.stories-B7YAUvlM.js +10 -0
  130. package/cli/storybook-dist/assets/RBCard.stories-DTL_7n6I.js +45 -0
  131. package/cli/storybook-dist/assets/RBCheckBox.stories-D9KC6hTg.js +1 -0
  132. package/cli/storybook-dist/assets/RBCheckIconButton.stories-BR8aIkOC.js +1 -0
  133. package/cli/storybook-dist/assets/RBCheckLabelButton.stories-C1ABYhEr.js +24 -0
  134. package/cli/storybook-dist/assets/RBChipTab.stories-Ceo_vnNi.js +21 -0
  135. package/cli/storybook-dist/assets/RBChipsBoxTab.stories-CB_bfK8M.js +1 -0
  136. package/cli/storybook-dist/assets/RBColorIcons.stories-DHZKezld.js +3 -0
  137. package/cli/storybook-dist/assets/RBCommand.stories-DjuCDn5h.js +73 -0
  138. package/cli/storybook-dist/assets/RBConfirm.stories-HMmUPkB1.js +20 -0
  139. package/cli/storybook-dist/assets/RBDateInput.stories-BRfpYtJ4.js +15 -0
  140. package/cli/storybook-dist/assets/RBDialog.stories-Ln23Gyhk.js +38 -0
  141. package/cli/storybook-dist/assets/RBDivider.stories-Bhc_D5Cm.js +1 -0
  142. package/cli/storybook-dist/assets/RBDrawer.stories-CEEhamr_.js +14 -0
  143. package/cli/storybook-dist/assets/RBDropdownMenu.stories-DBvwDCnf.js +86 -0
  144. package/cli/storybook-dist/assets/RBEmptyView.stories-BY5QtXVc.js +12 -0
  145. package/cli/storybook-dist/assets/RBIconButton.stories-DyMFWpIV.js +1 -0
  146. package/cli/storybook-dist/assets/RBIcons.stories-DisU8G0d.js +3 -0
  147. package/cli/storybook-dist/assets/RBInlineLabelButton.stories-Cx0KJqxD.js +1 -0
  148. package/cli/storybook-dist/assets/RBJoyStick.stories-D1THM1Du.js +11 -0
  149. package/cli/storybook-dist/assets/RBLabelButton.stories-BDktlyzc.js +1 -0
  150. package/cli/storybook-dist/assets/RBLight.stories-C5qpnM6J.js +1 -0
  151. package/cli/storybook-dist/assets/RBMenuTreeIcons.stories-DUCeQPKJ.js +3 -0
  152. package/cli/storybook-dist/assets/RBMenubar.stories-ClyM9v37.js +3 -0
  153. package/cli/storybook-dist/assets/RBMultiRangeInput.stories--xSn64ww.js +14 -0
  154. package/cli/storybook-dist/assets/RBPagination.stories-C_uTj83z.js +17 -0
  155. package/cli/storybook-dist/assets/RBPopover.stories-DWmb9Por.js +11 -0
  156. package/cli/storybook-dist/assets/RBProgramIcons.stories-B8ZjEtvw.js +3 -0
  157. package/cli/storybook-dist/assets/RBPrompt.stories-BFKfyYtl.js +20 -0
  158. package/cli/storybook-dist/assets/RBRadioBox.stories-DUz8a27t.js +1 -0
  159. package/cli/storybook-dist/assets/RBRadioButton.stories-DKF61xvC.js +11 -0
  160. package/cli/storybook-dist/assets/RBRangeDateInput.stories-GhMVDyMr.js +15 -0
  161. package/cli/storybook-dist/assets/RBResizable.stories-V0iisR-x.js +20 -0
  162. package/cli/storybook-dist/assets/RBSelectBox.stories-DhvooAcg.js +6 -0
  163. package/cli/storybook-dist/assets/RBSheet.stories-C93Hpmvn.js +39 -0
  164. package/cli/storybook-dist/assets/RBSideNumberInput.stories-Y547mUsB.js +5 -0
  165. package/cli/storybook-dist/assets/RBSlider.stories-BJZyHiLO.js +5 -0
  166. package/cli/storybook-dist/assets/RBSpinner.stories-6wkbu-bU.js +14 -0
  167. package/cli/storybook-dist/assets/RBSwitch.stories-T9InolEP.js +65 -0
  168. package/cli/storybook-dist/assets/RBTab.stories-CjGzZODx.js +1 -0
  169. package/cli/storybook-dist/assets/RBTable.stories-CaolKsVG.js +62 -0
  170. package/cli/storybook-dist/assets/RBTableRow.stories-D13urGfs.js +11 -0
  171. package/cli/storybook-dist/assets/RBTabs.stories-Cv2iIxvP.js +21 -0
  172. package/cli/storybook-dist/assets/RBTextArea.stories-CmrPeit2.js +5 -0
  173. package/cli/storybook-dist/assets/RBTextField.stories-ByW-EKCD.js +45 -0
  174. package/cli/storybook-dist/assets/RBTextInput.stories-DSPlr3uh.js +5 -0
  175. package/cli/storybook-dist/assets/RBToast.stories-DJNZ9tku.js +11 -0
  176. package/cli/storybook-dist/assets/RBToggle.stories-BDQW2WO1.js +12 -0
  177. package/cli/storybook-dist/assets/RBToggleGroup.stories-CjwH6YbI.js +17 -0
  178. package/cli/storybook-dist/assets/RBTooltip.stories-Cp1oL68L.js +9 -0
  179. package/cli/storybook-dist/assets/RainbowUiProvider-BYlbsa69.css +1 -0
  180. package/cli/storybook-dist/assets/RainbowUiProvider-B_piZBLb.js +31 -0
  181. package/cli/storybook-dist/assets/browserUtils-DeopUpDy.js +1 -0
  182. package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
  183. package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
  184. package/cli/storybook-dist/assets/createConveyorBeltLayer-769P8deX.js +6257 -0
  185. package/cli/storybook-dist/assets/createConveyorBeltLayer-Ds5AOper.css +21 -0
  186. package/cli/storybook-dist/assets/entry-preview-CNdFC92L.js +2 -0
  187. package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
  188. package/cli/storybook-dist/assets/iframe-CLUgOMp4.js +211 -0
  189. package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
  190. package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
  191. package/cli/storybook-dist/assets/index-C1OzuK6-.js +240 -0
  192. package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
  193. package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
  194. package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
  195. package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
  196. package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
  197. package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
  198. package/cli/storybook-dist/assets/index-mT46TdHs.js +575 -0
  199. package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
  200. package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +1 -0
  201. package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
  202. package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
  203. package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
  204. package/cli/storybook-dist/assets/preview-CILRoSnB.js +1 -0
  205. package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
  206. package/cli/storybook-dist/assets/preview-Com4scch.css +1 -0
  207. package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
  208. package/cli/storybook-dist/assets/preview-CvycU0Br.js +2 -0
  209. package/cli/storybook-dist/assets/preview-CzhIqee1.js +3 -0
  210. package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
  211. package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
  212. package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
  213. package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
  214. package/cli/storybook-dist/assets/react-resizable-panels.browser-CvAJUcrm.js +72 -0
  215. package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
  216. package/cli/storybook-dist/favicon.svg +1 -0
  217. package/cli/storybook-dist/iframe.html +666 -0
  218. package/cli/storybook-dist/index.html +190 -0
  219. package/cli/storybook-dist/index.json +1 -0
  220. package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
  221. package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
  222. package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
  223. package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
  224. package/cli/storybook-dist/project.json +1 -0
  225. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
  226. package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
  227. package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  228. package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  229. package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  230. package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  231. package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  232. package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  233. package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  234. package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  235. package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
  236. package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
  237. package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  238. package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
  239. package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
  240. package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
  241. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  242. package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  243. package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  244. package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  245. package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
  246. package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
  247. package/cli/storybook-dist/sb-manager/globals.js +48 -0
  248. package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
  249. package/cli/templates/common/.mcp.json +8 -0
  250. package/cli/templates/common/README.md +644 -81
  251. package/cli/templates/common/assets/icon.svg +44 -2
  252. package/cli/templates/common/manifest.json +13 -13
  253. package/cli/templates/common/package.json +11 -4
  254. package/cli/templates/common/py/main.py +98 -0
  255. package/cli/templates/common/requirements.txt +4 -3
  256. package/cli/templates/common/tsconfig.json +1 -1
  257. package/cli/templates/common/ui/content.tsx +216 -0
  258. package/cli/templates/common/ui/style.css +6 -0
  259. package/cli/templates/common/vite.config.ts +6 -25
  260. package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
  261. package/cli/templates/cpp/cpp/main.cpp +26 -0
  262. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +1 -1
  263. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +17 -0
  264. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
  265. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +22 -0
  266. package/cli/vite.d.mts +25 -0
  267. package/cli/vite.mjs +292 -0
  268. package/package.json +32 -5
  269. package/cli/src/pack.mjs +0 -61
  270. package/cli/src/preview.mjs +0 -91
  271. package/cli/templates/common/main.py +0 -114
  272. package/cli/templates/common/src/content.tsx +0 -33
@@ -0,0 +1,98 @@
1
+ // muscat-pk uninstall - remove the installed CLI, whether npm-global or S3 binary.
2
+ import { execSync, spawn, spawnSync } from "node:child_process";
3
+ import { existsSync, realpathSync, rmSync } from "node:fs";
4
+ import { dirname, join, relative } from "node:path";
5
+ import { KIT_PUBLISHED_NAME, kitRoot, log, parseArgs } from "./util.mjs";
6
+
7
+ const isBinaryMode = typeof Bun !== "undefined";
8
+
9
+ function isInside(child, parent) {
10
+ const rel = relative(parent, child);
11
+ return rel === "" || (!!rel && !rel.startsWith("..") && !rel.includes(":"));
12
+ }
13
+
14
+ function npmGlobalRoot() {
15
+ return execSync("npm root -g", { encoding: "utf8" }).trim();
16
+ }
17
+
18
+ function isNpmGlobalInstall() {
19
+ try {
20
+ return isInside(realpathSync(kitRoot), realpathSync(npmGlobalRoot()));
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ function uninstallNpmGlobal() {
27
+ log.step(`npm uninstall -g ${KIT_PUBLISHED_NAME}`);
28
+ execSync(`npm uninstall -g ${KIT_PUBLISHED_NAME}`, { stdio: "inherit" });
29
+ log.ok("Global npm package removed.");
30
+ }
31
+
32
+ function uninstallBinary() {
33
+ const execPath = process.execPath;
34
+ log.step(`Removing binary: ${execPath}`);
35
+
36
+ if (process.platform === "win32") {
37
+ const ps = `Start-Sleep -Milliseconds 500; Remove-Item -Force '${execPath}'`;
38
+ spawn("powershell", ["-NoProfile", "-Command", ps], {
39
+ detached: true,
40
+ stdio: "ignore",
41
+ }).unref();
42
+ log.ok("Binary will be removed after this process exits.");
43
+ return;
44
+ }
45
+
46
+ try {
47
+ rmSync(execPath, { force: true });
48
+ } catch (err) {
49
+ if (err?.code !== "EACCES" && err?.code !== "EPERM") throw err;
50
+ log.info(" sudo is required...");
51
+ const result = spawnSync("sudo", ["rm", "-f", execPath], { stdio: "inherit" });
52
+ if (result.status !== 0) throw new Error(`sudo rm failed: ${execPath}`);
53
+ }
54
+
55
+ const parent = dirname(execPath);
56
+ for (const shim of ["muscat-pk.cmd", "muscat-pk.ps1"]) {
57
+ rmSync(join(parent, shim), { force: true });
58
+ }
59
+
60
+ log.ok("Binary removed.");
61
+ }
62
+
63
+ export async function runUninstall(args = []) {
64
+ const opts = parseArgs(args, { booleans: ["help"] });
65
+
66
+ if (opts.help) {
67
+ log.info(
68
+ [
69
+ "Usage: muscat-pk uninstall",
70
+ "",
71
+ "Removes the installed muscat-pk CLI.",
72
+ " npm global install -> npm uninstall -g @rainbow-robotics/muscat-plugin-kit",
73
+ " S3 binary install -> remove the muscat-pk executable",
74
+ ].join("\n"),
75
+ );
76
+ return;
77
+ }
78
+
79
+ if (isBinaryMode) {
80
+ uninstallBinary();
81
+ return;
82
+ }
83
+
84
+ if (isNpmGlobalInstall()) {
85
+ uninstallNpmGlobal();
86
+ return;
87
+ }
88
+
89
+ const localBin = process.argv[1] || "";
90
+ if (localBin.includes(`${join("node_modules", ".bin")}`) || existsSync(join(process.cwd(), "package.json"))) {
91
+ log.warn("This muscat-pk appears to be running from a project/local install.");
92
+ log.warn("Remove it from the project package manager instead, for example:");
93
+ log.warn(` npm uninstall ${KIT_PUBLISHED_NAME}`);
94
+ return;
95
+ }
96
+
97
+ throw new Error("Could not determine install mode. Remove muscat-pk manually from your PATH.");
98
+ }
@@ -1,52 +1,281 @@
1
- // muscat-pk update — update the JS plugin kit and host-provided UI contract.
2
- import { execSync } from "node:child_process";
3
- import { existsSync, readFileSync } from "node:fs";
4
- import { resolve } from "node:path";
5
- import { findPluginRoot, KIT_PUBLISHED_NAME, log, parseArgs } from "./util.mjs";
1
+ // muscat-pk update — update the CLI itself AND the plugin project's JS deps.
2
+ //
3
+ // Three install modes are handled:
4
+ // binary : Bun self-contained binary downloaded from S3
5
+ // → re-download from S3, replace process.execPath
6
+ // npm -g : globally installed Node.js package
7
+ // → npm install -g @rainbow-robotics/muscat-plugin-kit@<tag>
8
+ // local : plugin project's node_modules (fallback)
9
+ // → <pm> add @rainbow-robotics/muscat-plugin-kit@<tag>
10
+ //
11
+ // After updating the CLI itself, also bumps the plugin project's local deps
12
+ // and migrates the project structure if it's outdated (src/ → ui/, root main.py → py/).
13
+ import { execSync, spawn } from "node:child_process";
14
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { join, resolve } from "node:path";
17
+ import { findPluginRoot, KIT_PUBLISHED_NAME, kitVersion, log, parseArgs, prompt } from "./util.mjs";
6
18
 
7
19
  const PLUGIN_UI_PACKAGE = "@rainbow-robotics/plugin-ui";
20
+ const S3_BASE = "https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit";
21
+
22
+ const isBinaryMode = typeof Bun !== "undefined";
23
+
24
+ // ── helpers ───────────────────────────────────────────────────────────────────
8
25
 
9
26
  function detectPm(root) {
10
27
  try {
11
28
  const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
12
29
  if (pkg.packageManager) return pkg.packageManager.split("@")[0];
13
- } catch {
14
- /* ignore */
15
- }
30
+ } catch { /* ignore */ }
16
31
  if (existsSync(resolve(root, "pnpm-lock.yaml"))) return "pnpm";
17
32
  if (existsSync(resolve(root, "yarn.lock"))) return "yarn";
18
33
  if (existsSync(resolve(root, "bun.lockb"))) return "bun";
19
34
  return "npm";
20
35
  }
21
36
 
22
- export async function runUpdate(args = []) {
23
- const root = findPluginRoot();
24
- const pm = detectPm(root);
25
- const opts = parseArgs(args);
37
+ async function fetchText(url) {
38
+ const res = await fetch(url);
39
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`);
40
+ return (await res.text()).trim();
41
+ }
42
+
43
+ async function fetchBinary(url) {
44
+ const res = await fetch(url);
45
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${url}`);
46
+ return Buffer.from(await res.arrayBuffer());
47
+ }
48
+
49
+ function s3BinaryName() {
50
+ const platform = process.platform;
51
+ const arch = process.arch;
52
+ const plat = platform === "win32" ? "win" : platform === "darwin" ? "darwin" : "linux";
53
+ const cpu = arch === "arm64" ? "arm64" : "x64";
54
+ const ext = platform === "win32" ? ".exe" : "";
55
+ return `muscat-pk-${plat}-${cpu}${ext}`;
56
+ }
57
+
58
+ async function resolveS3Version(tag) {
59
+ const pointer = tag === "dev" ? "dev-version.txt" : "latest-version.txt";
60
+ return fetchText(`${S3_BASE}/${pointer}`);
61
+ }
62
+
63
+ // ── self-update: binary ───────────────────────────────────────────────────────
64
+
65
+ async function updateBinary(tag) {
66
+ const version = await resolveS3Version(tag);
67
+ const current = kitVersion();
68
+ if (version === current) {
69
+ log.ok(`Already up to date (v${current})`);
70
+ return;
71
+ }
26
72
 
27
- const range =
28
- opts._[0] ||
29
- opts.version ||
30
- opts["muscat-plugin-kit"] ||
31
- opts["plugin-ui"] ||
32
- "latest";
33
- const list = [
34
- `${KIT_PUBLISHED_NAME}@${range}`,
35
- `${PLUGIN_UI_PACKAGE}@${range}`,
36
- ];
73
+ const binaryName = s3BinaryName();
74
+ const url = `${S3_BASE}/v${version}/${binaryName}`;
75
+ log.step(`Downloading binary: v${current} → v${version}`);
76
+
77
+ const data = await fetchBinary(url);
78
+ const execPath = process.execPath;
79
+
80
+ if (process.platform === "win32") {
81
+ // Windows: cannot overwrite a running .exe — write .new then schedule replacement
82
+ const newPath = execPath + ".new";
83
+ writeFileSync(newPath, data);
84
+
85
+ // PowerShell snippet that waits for this process to exit, then renames
86
+ const ps = `Start-Sleep -Milliseconds 500; Move-Item -Force '${newPath}' '${execPath}'`;
87
+ spawn("powershell", ["-NoProfile", "-Command", ps], {
88
+ detached: true,
89
+ stdio: "ignore",
90
+ }).unref();
91
+
92
+ log.ok(`v${version} will be applied on next run.`);
93
+ } else {
94
+ // macOS / Linux: can replace the file while it's mapped into memory
95
+ const tmp = join(tmpdir(), `muscat-pk-${version}`);
96
+ writeFileSync(tmp, data);
97
+ chmodSync(tmp, 0o755);
98
+ copyFileSync(tmp, execPath);
99
+ log.ok(`Updated to v${version}.`);
100
+ }
101
+ }
102
+
103
+ // ── self-update: npm global ───────────────────────────────────────────────────
104
+
105
+ function updateNpmGlobal(tag) {
106
+ log.step(`npm install -g ${KIT_PUBLISHED_NAME}@${tag}`);
107
+ execSync(`npm install -g ${KIT_PUBLISHED_NAME}@${tag}`, { stdio: "inherit" });
108
+ log.ok("Global CLI updated.");
109
+ }
110
+
111
+ // ── project structure migration ───────────────────────────────────────────────
112
+
113
+ function detectOldStructure(root) {
114
+ const issues = [];
115
+
116
+ // src/ exists (old UI dir) or vite.config references src/content.tsx
117
+ const hasSrcDir = existsSync(resolve(root, "src"));
118
+ const hasUiDir = existsSync(resolve(root, "ui"));
119
+ const viteRef = (() => {
120
+ try {
121
+ return readFileSync(resolve(root, "vite.config.ts"), "utf8").includes("src/content.tsx");
122
+ } catch { return false; }
123
+ })();
124
+ if ((hasSrcDir || viteRef) && !hasUiDir) {
125
+ issues.push({ kind: "src→ui", desc: "src/ → ui/ (+ vite.config.ts, tsconfig.json)" });
126
+ }
127
+
128
+ if (existsSync(resolve(root, "main.py")) && !existsSync(resolve(root, "py"))) {
129
+ issues.push({ kind: "main→py", desc: "main.py → py/main.py" });
130
+ }
131
+ if (existsSync(resolve(root, "backend"))) {
132
+ issues.push({ kind: "backend→py", desc: "backend/ → py/" });
133
+ }
134
+ return issues;
135
+ }
37
136
 
137
+ async function migrateStructure(root, issues) {
138
+ log.warn("This plugin project's structure is outdated:");
139
+ for (const issue of issues) log.info(" • " + issue.desc);
140
+ log.warn("Files will be moved and modified. Back up or commit your work before proceeding.");
141
+
142
+ const answer = await prompt("Migrate project structure? [y/N] ");
143
+ if (answer.toLowerCase() !== "y") {
144
+ log.info("Structure migration skipped.");
145
+ return;
146
+ }
147
+
148
+ for (const issue of issues) {
149
+ if (issue.kind === "src→ui") {
150
+ if (existsSync(resolve(root, "src"))) {
151
+ renameSync(resolve(root, "src"), resolve(root, "ui"));
152
+ log.ok("src/ → ui/");
153
+ }
154
+
155
+ const vitePath = resolve(root, "vite.config.ts");
156
+ if (existsSync(vitePath)) {
157
+ writeFileSync(vitePath, readFileSync(vitePath, "utf8").replace(/["']src\/content\.tsx["']/g, '"ui/content.tsx"'));
158
+ log.ok("vite.config.ts entry updated");
159
+ }
160
+ const tscPath = resolve(root, "tsconfig.json");
161
+ if (existsSync(tscPath)) {
162
+ writeFileSync(tscPath, readFileSync(tscPath, "utf8").replace(/"src"/g, '"ui"'));
163
+ log.ok("tsconfig.json include updated");
164
+ }
165
+ }
166
+
167
+ if (issue.kind === "main→py") {
168
+ mkdirSync(resolve(root, "py"), { recursive: true });
169
+ renameSync(resolve(root, "main.py"), resolve(root, "py", "main.py"));
170
+ log.ok("main.py → py/main.py");
171
+ }
172
+
173
+ if (issue.kind === "backend→py") {
174
+ renameSync(resolve(root, "backend"), resolve(root, "py"));
175
+ log.ok("backend/ → py/");
176
+ const pyReqs = resolve(root, "py", "requirements.txt");
177
+ const rootReqs = resolve(root, "requirements.txt");
178
+ if (existsSync(pyReqs) && !existsSync(rootReqs)) {
179
+ renameSync(pyReqs, rootReqs);
180
+ log.ok("py/requirements.txt → requirements.txt (project root)");
181
+ }
182
+ }
183
+ }
184
+
185
+ log.ok("Migration complete. Run `muscat-pk build` to verify.");
186
+ }
187
+
188
+ // ── plugin project local deps ─────────────────────────────────────────────────
189
+
190
+ function updateLocalDeps(root, tag) {
191
+ const pm = detectPm(root);
192
+ const list = [`${KIT_PUBLISHED_NAME}@${tag}`, `${PLUGIN_UI_PACKAGE}@${tag}`];
38
193
  const cmd =
39
- pm === "npm"
40
- ? `npm install ${list.join(" ")}`
41
- : pm === "yarn"
42
- ? `yarn add ${list.join(" ")}`
43
- : `${pm} add ${list.join(" ")}`;
194
+ pm === "npm" ? `npm install ${list.join(" ")}`
195
+ : pm === "yarn" ? `yarn add ${list.join(" ")}`
196
+ : `${pm} add ${list.join(" ")}`;
44
197
 
45
- log.step(`${pm}: ${list.join(", ")}`);
198
+ log.step(`Updating plugin local deps (${pm}): ${list.join(", ")}`);
46
199
  try {
47
200
  execSync(cmd, { cwd: root, stdio: "inherit" });
48
- log.ok("kit packages updated");
201
+ log.ok("Local deps updated.");
49
202
  } catch {
50
- log.warn("update failed (offline?). Run manually:\n " + cmd);
203
+ log.warn("Local deps update failed (offline?). Run manually:\n " + cmd);
51
204
  }
52
205
  }
206
+
207
+ // ── main ─────────────────────────────────────────────────────────────────────
208
+
209
+ async function resolveTag(explicitTag) {
210
+ if (explicitTag) return explicitTag;
211
+
212
+ const current = kitVersion();
213
+ const isDevVersion = current.includes("-dev.");
214
+
215
+ if (!isDevVersion) return "latest";
216
+
217
+ const answer = await prompt(
218
+ `You are on a dev version (v${current}).\n` +
219
+ ` [1] Stay on dev — update to latest dev build\n` +
220
+ ` [2] Switch to stable — update to latest stable release\n` +
221
+ `Choice [1/2, default: 1]: `
222
+ );
223
+ return answer.trim() === "2" ? "latest" : "dev";
224
+ }
225
+
226
+ export async function runUpdate(args = []) {
227
+ const opts = parseArgs(args);
228
+ const explicitTag = opts._[0] || opts.version || opts.tag || "";
229
+ const tag = await resolveTag(explicitTag);
230
+
231
+ // 1. Update the CLI itself
232
+ if (isBinaryMode) {
233
+ log.step("Updating binary…");
234
+ try {
235
+ await updateBinary(tag);
236
+ } catch (err) {
237
+ log.warn(`Binary update failed: ${err.message}`);
238
+ }
239
+ } else {
240
+ const isGlobal = (() => {
241
+ try {
242
+ const prefix = execSync("npm prefix -g", { encoding: "utf8" }).trim();
243
+ return (
244
+ // Unix: {prefix}/bin/muscat-pk
245
+ existsSync(join(prefix, "bin", "muscat-pk")) ||
246
+ // Windows npm: {prefix}/muscat-pk.cmd (no bin/ subdir)
247
+ existsSync(join(prefix, "muscat-pk.cmd")) ||
248
+ existsSync(join(prefix, "bin", "muscat-pk.cmd"))
249
+ );
250
+ } catch { return false; }
251
+ })();
252
+
253
+ if (isGlobal) {
254
+ try {
255
+ updateNpmGlobal(tag);
256
+ } catch (err) {
257
+ log.warn(`Global CLI update failed: ${err.message}`);
258
+ }
259
+ }
260
+ }
261
+
262
+ // 2. Plugin project: structure migration + local deps
263
+ let root;
264
+ try {
265
+ root = findPluginRoot();
266
+ } catch {
267
+ // Not inside a plugin project — skip
268
+ return;
269
+ }
270
+
271
+ try {
272
+ const structureIssues = detectOldStructure(root);
273
+ if (structureIssues.length > 0) {
274
+ await migrateStructure(root, structureIssues);
275
+ }
276
+ } catch (err) {
277
+ log.warn(`Structure check failed: ${err.message}`);
278
+ }
279
+
280
+ updateLocalDeps(root, tag);
281
+ }
package/cli/src/util.mjs CHANGED
@@ -1,8 +1,10 @@
1
1
  // Shared helpers for muscat-pk.
2
2
  import { spawnSync } from "node:child_process";
3
- import { existsSync, readFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
+ import { tmpdir } from "node:os";
5
6
  import { fileURLToPath } from "node:url";
7
+ import { createInterface } from "node:readline/promises";
6
8
 
7
9
  /** True if an executable is on PATH (cross-platform). */
8
10
  export function hasBin(bin) {
@@ -37,6 +39,32 @@ export function venvPython(venvDir) {
37
39
  : resolve(venvDir, "bin", "python");
38
40
  }
39
41
 
42
+ /**
43
+ * Create/refresh a local `.venv` with requirements.txt installed, for the
44
+ * editor/linter only (the backend actually runs in Docker). Uses `uv` if
45
+ * present, else the system Python. Returns true if the venv is ready.
46
+ */
47
+ export function ensureIdeVenv(root) {
48
+ const venvDir = resolve(root, ".venv");
49
+ const py = venvPython(venvDir);
50
+
51
+ if (!existsSync(py)) {
52
+ let created;
53
+ if (hasBin("uv")) {
54
+ created = spawnSync("uv", ["venv", venvDir, "--python", "3.12", "--seed"], { cwd: root, stdio: "ignore" });
55
+ } else {
56
+ const { cmd } = detectPythonCmd();
57
+ created = spawnSync(cmd[0], [...cmd.slice(1), "-m", "venv", venvDir], { cwd: root, stdio: "ignore" });
58
+ }
59
+ if (created.status !== 0 || !existsSync(py)) return false;
60
+ }
61
+
62
+ const reqs = resolve(root, "requirements.txt");
63
+ const args = ["-m", "pip", "install", "-q"];
64
+ if (existsSync(reqs)) args.push("-r", reqs);
65
+ return spawnSync(py, args, { cwd: root, stdio: "ignore" }).status === 0;
66
+ }
67
+
40
68
  /** Published install/CLI package name. Author imports use @rainbow-robotics/plugin-ui separately. */
41
69
  export const KIT_PUBLISHED_NAME = "@rainbow-robotics/muscat-plugin-kit";
42
70
 
@@ -65,6 +93,40 @@ export function parseArgs(args, { booleans = [] } = {}) {
65
93
  return out;
66
94
  }
67
95
 
96
+ /** Prompt user for input. Returns trimmed answer or fallback (non-TTY or empty). */
97
+ export async function prompt(question, fallback = "") {
98
+ if (!process.stdin.isTTY) return fallback;
99
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
100
+ const answer = (await rl.question(question)).trim();
101
+ rl.close();
102
+ return answer || fallback;
103
+ }
104
+
105
+ const LOCAL_HOSTS = new Set(["", "localhost", "127.0.0.1", "::1"]);
106
+
107
+ /**
108
+ * Ask for robot IP (and token if remote). Returns { robotIp, robotToken }.
109
+ * Skips prompts if already set in process.env (from a parent command like `dev`).
110
+ * Supports --robot-ip / --robot-token flags for non-interactive use.
111
+ */
112
+ export async function promptRobotConnection(args = []) {
113
+ const parsed = parseArgs(args);
114
+ const robotIp =
115
+ parsed["robot-ip"] ??
116
+ process.env.MUSCAT_ROBOT_HOST ??
117
+ (await prompt(" Robot IP (skip if no robot): "));
118
+
119
+ if (LOCAL_HOSTS.has(robotIp)) return { robotIp, robotToken: "" };
120
+
121
+ const robotToken =
122
+ parsed["robot-token"] ??
123
+ process.env.MUSCAT_ROBOT_TOKEN ??
124
+ (await prompt(" SDK Token (required for remote): "));
125
+
126
+ if (!robotToken) throw new Error("SDK token is required for remote robot connection.");
127
+ return { robotIp, robotToken };
128
+ }
129
+
68
130
  /** Walk up from cwd to find the plugin project root (has manifest.json). */
69
131
  export function findPluginRoot(start = process.cwd()) {
70
132
  let dir = resolve(start);
@@ -79,22 +141,74 @@ export function findPluginRoot(start = process.cwd()) {
79
141
  );
80
142
  }
81
143
 
82
- const ORANGE = "\x1b[33m";
83
- const RESET = "\x1b[0m";
144
+ const useColor = process.env.NO_COLOR !== "1" && (process.stdout.isTTY || process.env.FORCE_COLOR);
145
+ const ansi = (code) => (s) => useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s);
146
+
147
+ export const color = {
148
+ bold: ansi("1"),
149
+ dim: ansi("2"),
150
+ red: ansi("31"),
151
+ green: ansi("32"),
152
+ yellow: ansi("33"),
153
+ blue: ansi("34"),
154
+ magenta: ansi("35"),
155
+ cyan: ansi("36"),
156
+ };
84
157
 
85
158
  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}`)),
159
+ info: (...m) => console.log(...m),
160
+ step: (...m) => console.log(color.cyan("›"), ...m.map(s => color.cyan(s))),
161
+ ok: (...m) => console.log(color.green("✓"), ...m),
162
+ warn: (...m) => console.warn(color.yellow("⚠"), ...m.map(s => color.yellow(s))),
163
+ error: (...m) => console.error(color.red("✖"), ...m.map(s => color.red(s))),
90
164
  };
91
165
 
166
+ // dev / dev:back / build run the plugin back end, the dev bridge, and cpp/ros2
167
+ // compilation in Docker. Show a clear install hint when Docker is missing.
168
+ export function ensureDocker() {
169
+ if (hasBin("docker")) return;
170
+ log.warn("Docker not found.");
171
+ log.warn(" muscat-pk runs the plugin back end, the dev bridge, and cpp/ros2 builds in Docker.");
172
+ log.warn(" Install Docker, then retry: https://docs.docker.com/get-docker/");
173
+ throw new Error("Docker is required for dev / dev:back / build.");
174
+ }
175
+
176
+ /**
177
+ * Remove dangling (untagged) muscat-pk images left behind after a rebuild.
178
+ * Uses label filter so only our images are touched — other Docker images are safe.
179
+ */
180
+ export function pruneMuskatImages() {
181
+ spawnSync(
182
+ "docker",
183
+ ["image", "prune", "--force", "--filter", "label=com.rainbow-robotics.muscat-pk=true"],
184
+ { stdio: "ignore" },
185
+ );
186
+ }
187
+
92
188
  export function humanSize(bytes) {
93
189
  if (bytes < 1024) return `${bytes} B`;
94
190
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
95
191
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
96
192
  }
97
193
 
194
+ /**
195
+ * Return the on-disk path of a bundled Dockerfile.
196
+ * In npm-install mode the real file exists under cli/docker/.
197
+ * In compiled-binary mode it doesn't — extract from the embedded snapshot to tmpdir.
198
+ */
199
+ export async function resolveDockerfile(name) {
200
+ const real = resolve(kitRoot, "cli/docker", name);
201
+ if (existsSync(real)) return real;
202
+ const { TEMPLATES } = await import("./templates-embedded.mjs");
203
+ const b64 = TEMPLATES[`docker/${name}`];
204
+ if (!b64) throw new Error(`Embedded Dockerfile missing: docker/${name}. Reinstall muscat-plugin-kit.`);
205
+ const dir = resolve(tmpdir(), "muscat-pk-docker");
206
+ mkdirSync(dir, { recursive: true });
207
+ const tmp = resolve(dir, name);
208
+ writeFileSync(tmp, Buffer.from(b64, "base64"));
209
+ return tmp;
210
+ }
211
+
98
212
  /** This kit's installed version (from its package.json). */
99
213
  export function kitVersion() {
100
214
  try {
@@ -144,8 +258,8 @@ export async function checkKitUpdate({ timeoutMs = 2000 } = {}) {
144
258
  [
145
259
  "",
146
260
  line,
147
- ` muscat-plugin-kit 새 버전이 있습니다: ${current} → ${latest}`,
148
- ` 업데이트: muscat-pk update`,
261
+ ` muscat-plugin-kit update available: ${current} → ${latest}`,
262
+ ` Update: muscat-pk update`,
149
263
  line,
150
264
  "",
151
265
  ].join("\n"),
@@ -0,0 +1,75 @@
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
+
17
+ export const DEFAULT_ZENOH_DEV_PORT = 53917;
18
+ export const LOCAL_HOSTS = new Set(["", "localhost", "127.0.0.1", "::1"]);
19
+
20
+ export const isRemoteRobot = (robotIp) => !!robotIp && !LOCAL_HOSTS.has(robotIp);
21
+
22
+ /** The local interface IP the OS would use to reach `host` (no packet sent). */
23
+ export function detectRouteAddress(host) {
24
+ return new Promise((resolve) => {
25
+ const socket = createSocket("udp4");
26
+ socket.once("error", () => {
27
+ socket.close();
28
+ resolve("");
29
+ });
30
+ socket.connect(80, host, () => {
31
+ const address = socket.address();
32
+ socket.close();
33
+ resolve(typeof address === "object" ? address.address : "");
34
+ });
35
+ });
36
+ }
37
+
38
+ /** `preferred` if free, otherwise an OS-assigned free port. */
39
+ export function getFreePort(preferred) {
40
+ return new Promise((resolve) => {
41
+ const srv = createServer();
42
+ srv.once("error", () => {
43
+ const fallback = createServer();
44
+ fallback.listen(0, "0.0.0.0", () => {
45
+ const address = fallback.address();
46
+ const port = typeof address === "object" && address ? address.port : preferred;
47
+ fallback.close(() => resolve(port));
48
+ });
49
+ });
50
+ srv.listen(preferred, "0.0.0.0", () => srv.close(() => resolve(preferred)));
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Compute and set MUSCAT_ZENOH_ADVERTISE_HOST / MUSCAT_ZENOH_PORT for a remote
56
+ * robot, unless already set by a parent command (idempotent). No-op for a local
57
+ * robot. Returns the resolved { advertiseHost, zenohPort } (or null if local).
58
+ */
59
+ export async function ensureRemoteZenohEnv(robotIp, { log } = {}) {
60
+ if (!isRemoteRobot(robotIp)) return null;
61
+ // Already set (e.g. `muscat-pk dev` computed it before starting the bridge).
62
+ if (process.env.MUSCAT_ZENOH_PORT) {
63
+ return {
64
+ advertiseHost: process.env.MUSCAT_ZENOH_ADVERTISE_HOST || "",
65
+ zenohPort: Number(process.env.MUSCAT_ZENOH_PORT),
66
+ };
67
+ }
68
+ const advertiseHost =
69
+ process.env.MUSCAT_ZENOH_ADVERTISE_HOST || (await detectRouteAddress(robotIp));
70
+ const zenohPort = await getFreePort(DEFAULT_ZENOH_DEV_PORT);
71
+ if (advertiseHost) process.env.MUSCAT_ZENOH_ADVERTISE_HOST = advertiseHost;
72
+ process.env.MUSCAT_ZENOH_PORT = String(zenohPort);
73
+ log?.step?.(`remote Zenoh dev peer → ${advertiseHost || "(auto)"}:${zenohPort}`);
74
+ return { advertiseHost, zenohPort };
75
+ }