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

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 +25 -14
  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
package/cli/src/devUi.mjs CHANGED
@@ -1,38 +1,56 @@
1
- // muscat-pk dev:ui — preview the plugin inside a mini-Muscat (preview-host).
1
+
2
2
  import { spawn, spawnSync } from "node:child_process";
3
+ import { createServer as createNetServer } from "node:net";
3
4
  import { createRequire } from "node:module";
4
- import { existsSync, mkdirSync } from "node:fs";
5
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, watch } from "node:fs";
5
6
  import { homedir } from "node:os";
6
7
  import { dirname, resolve } from "node:path";
7
- import { buildPlugin } from "./build.mjs";
8
- import { checkKitUpdate, findPluginRoot, hasBin, kitRoot, log } from "./util.mjs";
8
+ import { fileURLToPath } from "node:url";
9
+ import { checkKitUpdate, findPluginRoot, hasBin, kitRoot, log, parseArgs, prompt } from "./util.mjs";
10
+
11
+
12
+ const DEV_PLUGIN_PORT = 4181;
13
+
14
+ function getFreePort(preferred) {
15
+ return new Promise((res) => {
16
+ const srv = createNetServer();
17
+ srv.once("error", () => {
18
+ const s2 = createNetServer();
19
+ s2.listen(0, () => {
20
+ const { port } = s2.address();
21
+ s2.close(() => res(port));
22
+ });
23
+ });
24
+ srv.listen(preferred, () => srv.close(() => res(preferred)));
25
+ });
26
+ }
9
27
 
10
28
  const PREVIEW_PKG = "@rainbow-robotics/preview-host";
11
29
  const CACHE_DIR = resolve(homedir(), ".muscat-pk");
12
30
  const CACHE_ROOT = resolve(CACHE_DIR, "node_modules", "@rainbow-robotics", "preview-host");
13
31
 
14
- /**
15
- * Resolve preview-host root in order:
16
- * 1. Standard require (npm/yarn global install)
17
- * 2. Monorepo sibling (dev workspace)
18
- * 3. ~/.muscat-pk cache (previous auto-install)
19
- * 4. Auto-install to ~/.muscat-pk via npm (binary users who have Node.js)
20
- */
32
+
33
+ const DEV_OUT = ".muscat-dev";
34
+
35
+ function findNodeBin() {
36
+ if (typeof Bun === "undefined") return process.execPath;
37
+ for (const c of ["node", "node20", "node18"]) {
38
+ if (hasBin(c)) return c;
39
+ }
40
+ return null;
41
+ }
42
+
21
43
  function findPreviewHostRoot() {
22
- // 1. npm/yarn 설치 경로
23
44
  try {
24
45
  const req = createRequire(import.meta.url);
25
46
  return dirname(req.resolve(`${PREVIEW_PKG}/package.json`));
26
47
  } catch {}
27
48
 
28
- // 2. 모노레포 sibling
29
49
  const sibling = resolve(kitRoot, "../preview-host");
30
50
  if (existsSync(resolve(sibling, "package.json"))) return sibling;
31
51
 
32
- // 3. 캐시
33
52
  if (existsSync(resolve(CACHE_ROOT, "package.json"))) return CACHE_ROOT;
34
53
 
35
- // 4. npm 자동 설치 → 캐시
36
54
  if (!hasBin("npm")) {
37
55
  throw new Error(
38
56
  `preview-host를 찾을 수 없습니다.\n` +
@@ -53,20 +71,128 @@ function findPreviewHostRoot() {
53
71
  return CACHE_ROOT;
54
72
  }
55
73
 
74
+ function syncAssets(pluginRoot, outDir) {
75
+ const assetsSrc = resolve(pluginRoot, "assets");
76
+ if (existsSync(assetsSrc)) cpSync(assetsSrc, resolve(outDir, "assets"), { recursive: true });
77
+ }
78
+
79
+
80
+ function stageDevAssets(pluginRoot) {
81
+ const outDir = resolve(pluginRoot, DEV_OUT);
82
+ mkdirSync(outDir, { recursive: true });
83
+ const manifestSrc = resolve(pluginRoot, "manifest.json");
84
+ if (existsSync(manifestSrc)) copyFileSync(manifestSrc, resolve(outDir, "manifest.json"));
85
+ syncAssets(pluginRoot, outDir);
86
+ return outDir;
87
+ }
88
+
89
+ function watchAssets(pluginRoot, outDir) {
90
+ const assetsSrc = resolve(pluginRoot, "assets");
91
+ if (!existsSync(assetsSrc)) return;
92
+ watch(assetsSrc, { recursive: true }, () => syncAssets(pluginRoot, outDir));
93
+ }
94
+
95
+
96
+ function findViteBin(pluginRoot) {
97
+ try {
98
+ const req = createRequire(resolve(pluginRoot, "package.json"));
99
+ const vitePkg = req.resolve("vite/package.json");
100
+ return resolve(dirname(vitePkg), "bin", "vite.js");
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+
107
+ function spawnPluginDevServer(pluginRoot, node, port, readyFile, sharedRuntimeBase) {
108
+ if (!node || !findViteBin(pluginRoot)) return null;
109
+ const runner = fileURLToPath(new URL("./pluginDevServer.mjs", import.meta.url));
110
+
111
+ rmSync(resolve(pluginRoot, "node_modules", ".vite"), { recursive: true, force: true });
112
+ log.step(`vite dev server (HMR) starting at :${port} — ui/*.tsx`);
113
+
114
+ return spawn(node, [runner, pluginRoot, String(port), readyFile], {
115
+ cwd: pluginRoot,
116
+ stdio: "inherit",
117
+ env: {
118
+ ...process.env,
119
+ VITE_PLUGIN_PREVIEW: "true",
120
+ MUSCAT_PLUGIN_SHARED_RUNTIME_BASE: sharedRuntimeBase,
121
+ },
122
+ });
123
+ }
124
+
125
+ function waitForPluginDevPort(readyFile, proc, timeoutMs = 15_000) {
126
+ const start = Date.now();
127
+ return new Promise((res, rej) => {
128
+ let exited = false;
129
+ proc.once("exit", (code) => {
130
+ exited = true;
131
+ rej(new Error(`plugin dev server exited before ready (code ${code ?? 0})`));
132
+ });
133
+
134
+ const tick = () => {
135
+ if (existsSync(readyFile)) {
136
+ try {
137
+ const { port } = JSON.parse(readFileSync(readyFile, "utf8"));
138
+ if (Number.isInteger(port)) return res(port);
139
+ } catch {
140
+ }
141
+ }
142
+ if (Date.now() - start > timeoutMs)
143
+ return rej(new Error("plugin dev server did not become ready in time"));
144
+ if (!exited) setTimeout(tick, 100);
145
+ };
146
+ tick();
147
+ });
148
+ }
149
+
56
150
  export async function runDevUi(args = []) {
57
- const portIdx = args.indexOf("--port");
58
- const port = portIdx !== -1 ? args[portIdx + 1] : "4180";
151
+ const parsed = parseArgs(args);
152
+ const requestedPort = Number(parsed.port ?? "4180");
153
+ const robotIp = parsed["robot-ip"] ?? process.env.MUSCAT_ROBOT_HOST ?? await prompt(" Robot IP (skip if no robot): ");
59
154
 
60
155
  await checkKitUpdate();
61
- const { distDir } = await buildPlugin();
156
+ const pluginRoot = findPluginRoot();
157
+ const outDir = stageDevAssets(pluginRoot);
158
+ const port = await getFreePort(requestedPort);
159
+ if (port !== requestedPort)
160
+ log.warn(`preview-host port ${requestedPort} is in use — using ${port} instead.`);
161
+
162
+ if (robotIp) log.info(`MUSCAT_API_ORIGIN → http://${robotIp}:*`);
163
+
164
+ watchAssets(pluginRoot, outDir);
165
+
166
+ const node = findNodeBin();
167
+ const readyFile = resolve(outDir, "plugin-dev-port.json");
168
+ rmSync(readyFile, { force: true });
169
+ const sharedRuntimeBase = `http://localhost:${port}/plugin-shared`;
170
+ const viteProc = spawnPluginDevServer(
171
+ pluginRoot,
172
+ node,
173
+ DEV_PLUGIN_PORT,
174
+ readyFile,
175
+ sharedRuntimeBase,
176
+ );
177
+ let devPort = DEV_PLUGIN_PORT;
178
+
179
+ if (!viteProc) {
180
+ log.warn("vite not found — UI will not load. Run `npm install` in the plugin project.");
181
+ } else {
182
+ devPort = await waitForPluginDevPort(readyFile, viteProc);
183
+ viteProc.on("exit", (code) => {
184
+ if (code) log.warn(`plugin dev server exited (code ${code}) — UI won't load (is :${devPort} free?).`);
185
+ });
186
+ }
62
187
 
63
188
  const previewRoot = findPreviewHostRoot();
64
189
  const serveEntry = resolve(previewRoot, "serve.mjs");
65
190
  if (!existsSync(resolve(previewRoot, "dist", "index.html"))) {
66
191
  log.step("building preview-host bundle (first run)…");
67
192
  await new Promise((res, rej) => {
68
- const p = spawn(process.execPath, [resolve(previewRoot, "build.mjs")], {
193
+ const p = spawn(node ?? process.execPath, [resolve(previewRoot, "build.mjs")], {
69
194
  stdio: "inherit",
195
+ env: { ...process.env, VITE_PLUGIN_PREVIEW: "true" },
70
196
  });
71
197
  p.on("exit", (c) => (c === 0 ? res() : rej(new Error("preview-host build failed"))));
72
198
  });
@@ -74,11 +200,30 @@ export async function runDevUi(args = []) {
74
200
 
75
201
  log.step(`starting preview-host on :${port}`);
76
202
  const child = spawn(
77
- process.execPath,
78
- [serveEntry, "--plugin", distDir, "--port", String(port)],
203
+ node ?? process.execPath,
204
+ [
205
+ serveEntry,
206
+ "--plugin", outDir,
207
+ "--port", String(port),
208
+ "--dev-server-url", `http://localhost:${devPort}`,
209
+ ...(robotIp ? ["--robot-host", robotIp] : []),
210
+ ],
79
211
  { stdio: "inherit" },
80
212
  );
81
- child.on("exit", (code) => process.exit(code ?? 0));
213
+
214
+ let cleaned = false;
215
+ const cleanup = () => {
216
+ if (cleaned) return;
217
+ cleaned = true;
218
+ if (viteProc && !viteProc.killed && viteProc.exitCode === null) viteProc.kill();
219
+ if (!child.killed && child.exitCode === null) child.kill();
220
+ };
221
+ process.once("SIGINT", cleanup);
222
+ process.once("SIGTERM", cleanup);
223
+ child.on("exit", (code) => {
224
+ cleanup();
225
+ process.exit(code ?? 0);
226
+ });
82
227
  }
83
228
 
84
229
  export { findPluginRoot, findPreviewHostRoot };
@@ -20,20 +20,37 @@ async function buildWithBun(options) {
20
20
  }
21
21
  }
22
22
 
23
+ async function buildWithWasm(options) {
24
+ const wasm = await import("esbuild-wasm");
25
+ return wasm.build(options);
26
+ }
27
+
28
+ function isNativeMissing(err) {
29
+ const msg = String(err?.message || err?.code || err || "");
30
+ return (
31
+ msg.includes("another platform") ||
32
+ msg.includes("Cannot find module") ||
33
+ msg.includes("Cannot find package") ||
34
+ msg.includes("not found") ||
35
+ msg.includes("No such file") ||
36
+ msg.includes("ENOENT") ||
37
+ msg.includes("MODULE_NOT_FOUND")
38
+ );
39
+ }
40
+
23
41
  export async function buildWithFallback(options) {
24
42
  if (typeof Bun !== "undefined") return buildWithBun(options);
25
43
 
26
- const native = await import("esbuild");
44
+ // Try native esbuild (faster, no extra setup). Falls back to wasm when:
45
+ // - esbuild was not installed (removed from hard dependencies to avoid
46
+ // postinstall script blocking npm i -g on Windows)
47
+ // - binary was built for a different platform
27
48
  try {
49
+ const native = await import("esbuild");
28
50
  return await native.build(options);
29
- } catch (error) {
30
- const message = String(error?.message || error);
31
- if (!message.includes("another platform")) {
32
- throw error;
33
- }
34
-
35
- const wasm = await import("esbuild-wasm");
36
- console.warn("[muscat-pk] native esbuild binary mismatch; falling back to esbuild-wasm");
37
- return wasm.build(options);
51
+ } catch (err) {
52
+ if (!isNativeMissing(err)) throw err;
38
53
  }
54
+
55
+ return buildWithWasm(options);
39
56
  }
@@ -0,0 +1,162 @@
1
+ """Host-side raw Zenoh byte-proxy.
2
+
3
+ Runs on the host as a normal Zenoh peer (reverse-dials the robot, same as the
4
+ SDK's own connect()) and serves a tiny domain-agnostic byte protocol over TCP
5
+ — {query, put, subscribe} by keyexpr + bytes only — so a Docker Desktop
6
+ container, which cannot join the robot's Zenoh mesh itself, can talk to a
7
+ process that can.
8
+
9
+ Env: MUSCAT_ROBOT_HOST, MUSCAT_ROBOT_TOKEN, MUSCAT_ZENOH_ADVERTISE_HOST,
10
+ MUSCAT_ZENOH_PORT (robot-facing), MUSCAT_PROXY_PORT (container-facing).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import contextlib
17
+ import json
18
+ import os
19
+ import socket
20
+ import struct
21
+ import threading
22
+
23
+
24
+ def _send_frame(conn: socket.socket, lock: threading.Lock, obj: dict) -> None:
25
+ body = json.dumps(obj).encode()
26
+ with lock:
27
+ conn.sendall(struct.pack(">I", len(body)) + body)
28
+
29
+
30
+ def _recv_frame(conn: socket.socket) -> dict | None:
31
+ hdr = b""
32
+ while len(hdr) < 4:
33
+ chunk = conn.recv(4 - len(hdr))
34
+ if not chunk:
35
+ return None
36
+ hdr += chunk
37
+ n = struct.unpack(">I", hdr)[0]
38
+ buf = b""
39
+ while len(buf) < n:
40
+ chunk = conn.recv(n - len(buf))
41
+ if not chunk:
42
+ return None
43
+ buf += chunk
44
+ return json.loads(buf.decode())
45
+
46
+
47
+ def _handle_conn(conn: socket.socket, zenoh_client) -> None:
48
+ send_lock = threading.Lock()
49
+ sub_ids: dict[str, int] = {}
50
+ try:
51
+ while True:
52
+ req = _recv_frame(conn)
53
+ if req is None:
54
+ return
55
+ op = req.get("type")
56
+
57
+ if op == "query":
58
+ payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else None
59
+ try:
60
+ replies = zenoh_client._client.query(
61
+ req["keyexpr"], payload, req.get("timeout_ms", 2000), req.get("target", 0)
62
+ )
63
+ out = {
64
+ "type": "query_resp", "id": req["id"], "ok": True,
65
+ "replies": [
66
+ {"key": k, "payload_hex": bytes(p).hex(), "att": a, "encoding": e, "is_error": ie}
67
+ for (k, p, a, e, ie) in replies
68
+ ],
69
+ }
70
+ except Exception as exc:
71
+ out = {"type": "query_resp", "id": req["id"], "ok": False, "error": f"{type(exc).__name__}: {exc}"}
72
+ _send_frame(conn, send_lock, out)
73
+
74
+ elif op == "put":
75
+ payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else b""
76
+ try:
77
+ zenoh_client._client.publish(req["keyexpr"], payload, "", 0, 5, False)
78
+ out = {"type": "put_resp", "id": req["id"], "ok": True}
79
+ except Exception as exc:
80
+ out = {"type": "put_resp", "id": req["id"], "ok": False, "error": f"{type(exc).__name__}: {exc}"}
81
+ _send_frame(conn, send_lock, out)
82
+
83
+ elif op == "subscribe":
84
+ keyexpr = req["keyexpr"]
85
+
86
+ def on_sample(topic: str, payload_bytes: bytes, att: str, _ke: str = keyexpr) -> None:
87
+ _send_frame(conn, send_lock, {
88
+ "type": "sample", "keyexpr": _ke, "key": topic,
89
+ "payload_hex": bytes(payload_bytes).hex(), "att": att,
90
+ })
91
+
92
+ try:
93
+ sub_id = zenoh_client._client.declare_subscriber(keyexpr, on_sample)
94
+ sub_ids[keyexpr] = sub_id
95
+ out = {"type": "subscribe_resp", "id": req["id"], "ok": True}
96
+ except Exception as exc:
97
+ out = {"type": "subscribe_resp", "id": req["id"], "ok": False, "error": f"{type(exc).__name__}: {exc}"}
98
+ _send_frame(conn, send_lock, out)
99
+
100
+ elif op == "unsubscribe":
101
+ keyexpr = req["keyexpr"]
102
+ sub_id = sub_ids.pop(keyexpr, None)
103
+ if sub_id is not None:
104
+ try:
105
+ zenoh_client._client.undeclare_subscriber(sub_id)
106
+ except Exception:
107
+ pass
108
+ _send_frame(conn, send_lock, {"type": "unsubscribe_resp", "id": req["id"], "ok": True})
109
+
110
+ else:
111
+ _send_frame(conn, send_lock, {"type": "error", "error": f"unknown op {op!r}"})
112
+ except (ConnectionResetError, BrokenPipeError, OSError):
113
+ pass
114
+ finally:
115
+ for sub_id in sub_ids.values():
116
+ try:
117
+ zenoh_client._client.undeclare_subscriber(sub_id)
118
+ except Exception:
119
+ pass
120
+ conn.close()
121
+
122
+
123
+ def main() -> None:
124
+ from rainbow.rb_sdk.base import RBBaseSDK
125
+
126
+ host = os.environ["MUSCAT_ROBOT_HOST"]
127
+ token = os.environ.get("MUSCAT_ROBOT_TOKEN", "")
128
+ advertise = os.environ["MUSCAT_ZENOH_ADVERTISE_HOST"]
129
+ zenoh_port = int(os.environ["MUSCAT_ZENOH_PORT"])
130
+ proxy_port = int(os.environ["MUSCAT_PROXY_PORT"])
131
+
132
+ # The robot reverse-dials the advertised host:port; force it to the host's
133
+ # real LAN IP (matches what dev.mjs computed via ensureRemoteZenohEnv).
134
+ RBBaseSDK._detect_local_ip = staticmethod(lambda _host: advertise)
135
+
136
+ peer = RBBaseSDK()
137
+ connect_kwargs = {"host": host, "zenoh_port": zenoh_port, "mesh_timeout": 15.0}
138
+ if token:
139
+ connect_kwargs["token"] = token
140
+
141
+ asyncio.run(peer.connect(**connect_kwargs))
142
+ print(f"[host-agent] zenoh peer joined robot mesh (listening :{zenoh_port})", flush=True)
143
+
144
+ srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
145
+ srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
146
+ srv.bind(("0.0.0.0", proxy_port))
147
+ srv.listen(32)
148
+ print(f"[host-agent] byte-proxy listening :{proxy_port} (container connects via host.docker.internal)", flush=True)
149
+
150
+ try:
151
+ while True:
152
+ conn, _addr = srv.accept()
153
+ threading.Thread(target=_handle_conn, args=(conn, peer.zenoh_client), daemon=True).start()
154
+ except KeyboardInterrupt:
155
+ pass
156
+ finally:
157
+ with contextlib.suppress(Exception):
158
+ asyncio.run(peer.disconnect())
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()
@@ -0,0 +1,208 @@
1
+ // manifest.mjs — manifest.json schema definition and validation
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { manifestPortRanges, validatePorts } from "./ports.mjs";
5
+
6
+ export const CONTRIBUTION_POINTS = ["program.command", "toolbar", "move.rnb"];
7
+ const ICON_SIZES = ["16", "48", "128"];
8
+ const RUNTIME_TYPES = ["none", "python", "cpp", "ros2"];
9
+ const ROS_CLIENTS = ["rclpy", "rclcpp"];
10
+ const I18N_LANGS = ["ko", "en", "ja", "zh", "vi", "th", "es"];
11
+ const TARGET_CATEGORIES = ["manipulate", "amr", "rby1", "vision"];
12
+
13
+ function validateI18nLabel(value, field, errors) {
14
+ if (value === undefined || value === null) {
15
+ errors.push(`${field} is required`);
16
+ return;
17
+ }
18
+ if (typeof value === "string") return;
19
+ if (typeof value === "object") {
20
+ if (typeof value.en !== "string")
21
+ errors.push(`${field}: "en" is required when using object form`);
22
+ const unknown = Object.keys(value).filter((k) => !I18N_LANGS.includes(k));
23
+ if (unknown.length > 0)
24
+ errors.push(`${field}: unsupported language key(s) — ${unknown.map((k) => `"${k}"`).join(", ")} (allowed: ${I18N_LANGS.join(", ")})`);
25
+ return;
26
+ }
27
+ errors.push(`${field}: must be a string or { en (required), ko, ja, zh, vi, th, es }`);
28
+ }
29
+
30
+ export function readManifest(root) {
31
+ const path = resolve(root, "manifest.json");
32
+ if (!existsSync(path)) return {};
33
+ try {
34
+ return JSON.parse(readFileSync(path, "utf8"));
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ export function manifestRuntime(root) {
41
+ return readManifest(root)?.runtime?.type ?? "python";
42
+ }
43
+
44
+ function validateIcons(icons, errors) {
45
+ if (icons === undefined || icons === null) {
46
+ errors.push("icons is required");
47
+ return;
48
+ }
49
+ if (typeof icons === "string") {
50
+ if (!icons.toLowerCase().endsWith(".svg"))
51
+ errors.push(`icons: non-SVG images (PNG, JPG, etc.) must use the { "16": "...", "48": "...", "128": "..." } object form`);
52
+ } else if (typeof icons === "object") {
53
+ const missing = ICON_SIZES.filter((s) => typeof icons[s] !== "string" || icons[s].length === 0);
54
+ if (missing.length > 0)
55
+ errors.push(`icons: missing size(s) ${missing.map((s) => `"${s}"`).join(", ")} — all three (16, 48, 128) are required for image files`);
56
+ } else {
57
+ errors.push(`icons: must be a string (SVG path) or { "16", "48", "128" } object (PNG/JPG)`);
58
+ }
59
+ }
60
+
61
+ function validateRuntime(runtime, errors) {
62
+ if (runtime === undefined || runtime === null) {
63
+ errors.push("runtime is required");
64
+ return;
65
+ }
66
+ if (typeof runtime !== "object") {
67
+ errors.push("runtime must be an object");
68
+ return;
69
+ }
70
+ if (!RUNTIME_TYPES.includes(runtime.type))
71
+ errors.push(`runtime.type must be one of ${RUNTIME_TYPES.map((t) => `"${t}"`).join(" | ")}`);
72
+ if (runtime.type === "python" && !runtime.entry)
73
+ errors.push("runtime.entry is required for python runtime");
74
+ if (runtime.type === "cpp" && !runtime.entry)
75
+ errors.push("runtime.entry is required for cpp runtime (the built executable name)");
76
+ if (runtime.type === "ros2") {
77
+ if (!runtime.package) errors.push("runtime.package is required for ros2 runtime");
78
+ if (!runtime.launch) errors.push("runtime.launch is required for ros2 runtime");
79
+ if (!ROS_CLIENTS.includes(runtime.rosClient))
80
+ errors.push(`runtime.rosClient must be one of ${ROS_CLIENTS.map((c) => `"${c}"`).join(" | ")}`);
81
+ }
82
+ }
83
+
84
+ function validateCommandInfo(info, prefix, errors) {
85
+ if (typeof info.name !== "string" || info.name.length === 0)
86
+ errors.push(`${prefix}.commandInfo.name is required`);
87
+ if (typeof info.icon !== "string" || info.icon.length === 0)
88
+ errors.push(`${prefix}.commandInfo.icon is required`);
89
+ if (info.iconClassName !== undefined && typeof info.iconClassName !== "string")
90
+ errors.push(`${prefix}.commandInfo.iconClassName must be a string`);
91
+ if (info.funcName !== undefined && typeof info.funcName !== "string")
92
+ errors.push(`${prefix}.commandInfo.funcName must be a string`);
93
+ if (info.args !== undefined && (typeof info.args !== "object" || Array.isArray(info.args)))
94
+ errors.push(`${prefix}.commandInfo.args must be an object`);
95
+ for (const key of ["disabled", "syncGroupId", "disableCopy", "disableDelete", "requireChildren", "requireParent"]) {
96
+ if (info[key] !== undefined && typeof info[key] !== "boolean")
97
+ errors.push(`${prefix}.commandInfo.${key} must be a boolean`);
98
+ }
99
+ for (const key of ["allowChildren", "allowParents", "notAllowChildren"]) {
100
+ if (info[key] !== undefined && (!Array.isArray(info[key]) || info[key].some((v) => typeof v !== "string")))
101
+ errors.push(`${prefix}.commandInfo.${key} must be a string[]`);
102
+ }
103
+ if (info.childrenCommand !== undefined && typeof info.childrenCommand !== "string")
104
+ errors.push(`${prefix}.commandInfo.childrenCommand must be a string`);
105
+ }
106
+
107
+ function validateContributes(contributes, errors) {
108
+ if (!Array.isArray(contributes) || contributes.length === 0) {
109
+ errors.push("contributes is required and must be a non-empty array");
110
+ return;
111
+ }
112
+ if (!contributes.some((c) => c.point === "toolbar"))
113
+ errors.push(`contributes: at least one "toolbar" entry is required`);
114
+
115
+ const uiValues = contributes.map((c) => c.ui).filter(Boolean);
116
+ const duplicateUi = uiValues.filter((v, i) => uiValues.indexOf(v) !== i);
117
+ if (duplicateUi.length > 0)
118
+ errors.push(`contributes: "ui" values must be unique across all entries (duplicates: ${[...new Set(duplicateUi)].map((v) => `"${v}"`).join(", ")})`);
119
+
120
+ contributes.forEach((c, i) => {
121
+ validateI18nLabel(c.label, `contributes[${i}].label`, errors);
122
+ if (c.point === "program.command") {
123
+ if (!c.commandInfo)
124
+ errors.push(`contributes[${i}]: "commandInfo" is required when point is "program.command"`);
125
+ else
126
+ validateCommandInfo(c.commandInfo, `contributes[${i}]`, errors);
127
+ }
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Validate manifest.json. Returns an array of error messages (empty = valid).
133
+ */
134
+ export function validateManifest(manifest) {
135
+ const errors = [];
136
+
137
+ if (typeof manifest !== "object" || manifest === null)
138
+ return ["manifest.json is not a valid JSON object"];
139
+
140
+ for (const key of ["id", "name", "version", "minMuscatVersion"]) {
141
+ if (typeof manifest[key] !== "string" || manifest[key].length === 0)
142
+ errors.push(`"${key}" is required`);
143
+ }
144
+ if (typeof manifest.author !== "string" || manifest.author.length === 0)
145
+ errors.push(`"author" is required`);
146
+ if (typeof manifest.email !== "string" || manifest.email.length === 0)
147
+ errors.push(`"email" is required`);
148
+ if (manifest.company !== undefined && manifest.company !== null && typeof manifest.company !== "string")
149
+ errors.push(`"company" must be a string`);
150
+
151
+ const hasModels = manifest.targetModels !== undefined && manifest.targetModels !== null;
152
+ const hasCategorys = manifest.targetCategorys !== undefined && manifest.targetCategorys !== null;
153
+ if (!hasModels && !hasCategorys) {
154
+ errors.push(`"targetModels" or "targetCategorys" is required`);
155
+ } else {
156
+ if (hasModels) {
157
+ if (manifest.targetModels !== "all" && (!Array.isArray(manifest.targetModels) || manifest.targetModels.length === 0))
158
+ errors.push(`"targetModels" must be "all" or a non-empty array of model strings`);
159
+ else if (Array.isArray(manifest.targetModels) && manifest.targetModels.some((m) => typeof m !== "string"))
160
+ errors.push(`"targetModels": all entries must be strings`);
161
+ }
162
+ if (hasCategorys) {
163
+ if (manifest.targetCategorys !== "all" && (!Array.isArray(manifest.targetCategorys) || manifest.targetCategorys.length === 0))
164
+ errors.push(`"targetCategorys" must be "all" or a non-empty array`);
165
+ else if (Array.isArray(manifest.targetCategorys)) {
166
+ const invalid = manifest.targetCategorys.filter((c) => !TARGET_CATEGORIES.includes(c));
167
+ if (invalid.length > 0)
168
+ errors.push(`"targetCategorys": invalid value(s) ${invalid.map((c) => `"${c}"`).join(", ")} — allowed: ${TARGET_CATEGORIES.join(", ")}`);
169
+ }
170
+ }
171
+ }
172
+ validateI18nLabel(manifest.description, "description", errors);
173
+
174
+ validateIcons(manifest.icons, errors);
175
+ validatePorts(manifest.ports, errors);
176
+ validateRuntime(manifest.runtime, errors);
177
+ validateContributes(manifest.contributes, errors);
178
+
179
+ return errors;
180
+ }
181
+
182
+ /**
183
+ * Read and validate manifest.json. Throws if any errors are found.
184
+ */
185
+ export function assertManifest(root) {
186
+ const manifest = readManifest(root);
187
+ if (manifest === null)
188
+ throw new Error("manifest.json parse failed — check JSON syntax");
189
+ if (Object.keys(manifest).length === 0)
190
+ return manifest;
191
+ const errors = validateManifest(manifest);
192
+ if (errors.length > 0)
193
+ throw new Error(`manifest.json errors:\n${errors.map((e) => ` • ${e}`).join("\n")}`);
194
+ return manifest;
195
+ }
196
+
197
+ export function assertManifestPorts(root) {
198
+ const manifest = readManifest(root);
199
+ if (manifest === null)
200
+ throw new Error("manifest.json parse failed — check JSON syntax");
201
+ if (Object.keys(manifest).length === 0)
202
+ return [];
203
+ const errors = [];
204
+ validatePorts(manifest.ports, errors);
205
+ if (errors.length > 0)
206
+ throw new Error(`manifest.json port errors:\n${errors.map((e) => ` • ${e}`).join("\n")}`);
207
+ return manifestPortRanges(manifest);
208
+ }