@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.9 → 0.10.1-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +1188 -0
- package/README.md +1139 -57
- package/cli/README.md +239 -7
- package/cli/bin/create-muscat-plugin.mjs +1 -1
- package/cli/bin/muscat-pk.mjs +123 -27
- package/cli/bundle-templates.mjs +31 -0
- package/cli/docker/Dockerfile.cpp +28 -0
- package/cli/docker/Dockerfile.dev +17 -0
- package/cli/docker/Dockerfile.python +21 -0
- package/cli/prepack.mjs +12 -0
- package/cli/program-commands.json +2079 -0
- package/cli/src/__pycache__/host_agent.cpython-37.pyc +0 -0
- package/cli/src/ai-rules.mjs +108 -0
- package/cli/src/build.mjs +333 -93
- package/cli/src/check-package.mjs +8 -23
- package/cli/src/completion.mjs +168 -0
- package/cli/src/cpp.mjs +78 -0
- package/cli/src/cppIde.mjs +76 -0
- package/cli/src/dev.mjs +195 -6
- package/cli/src/devBack.mjs +671 -60
- package/cli/src/devId.mjs +54 -0
- package/cli/src/devSession.mjs +67 -0
- package/cli/src/devUi.mjs +218 -25
- package/cli/src/esbuild.mjs +27 -10
- package/cli/src/host_agent.py +223 -0
- package/cli/src/manifest.mjs +288 -0
- package/cli/src/mcp.mjs +1232 -0
- package/cli/src/mcpProxy.mjs +110 -0
- package/cli/src/muscatLocal.mjs +292 -0
- package/cli/src/new.mjs +205 -68
- package/cli/src/pluginDevServer.mjs +111 -0
- package/cli/src/ports.mjs +193 -0
- package/cli/src/programCommands.mjs +327 -0
- package/cli/src/projectCheck.mjs +102 -0
- package/cli/src/python.mjs +112 -0
- package/cli/src/ros2.mjs +62 -18
- package/cli/src/sdk.mjs +74 -24
- package/cli/src/sim.mjs +177 -0
- package/cli/src/storybook.mjs +150 -0
- package/cli/src/templates-embedded.mjs +4 -0
- package/cli/src/type.mjs +93 -34
- package/cli/src/uninstall.mjs +81 -0
- package/cli/src/update.mjs +364 -35
- package/cli/src/util.mjs +341 -11
- package/cli/src/zenohDev.mjs +83 -0
- package/cli/src/zip.mjs +121 -0
- package/cli/storybook-dist/_design-tokens.css +4957 -0
- package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -0
- package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -0
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -0
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -0
- package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -0
- package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -0
- package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -0
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -0
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -0
- package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -0
- package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -0
- package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -0
- package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -0
- package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -0
- package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -0
- package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -0
- package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -0
- package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -0
- package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -0
- package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -0
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -0
- package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -0
- package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -0
- package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -0
- package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -0
- package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -0
- package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -0
- package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -0
- package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -0
- package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -0
- package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -0
- package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -0
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -0
- package/cli/storybook-dist/assets/Color-YHDXOIA2-pCKrjUal.js +1 -0
- package/cli/storybook-dist/assets/Docs-B-Y7I1GA.js +7 -0
- package/cli/storybook-dist/assets/Docs-BFJcE_VS.js +8 -0
- package/cli/storybook-dist/assets/Docs-B_QaTtz8.js +8 -0
- package/cli/storybook-dist/assets/Docs-CryDGmfh.js +20 -0
- package/cli/storybook-dist/assets/Docs-Cw2AVNKx.js +7 -0
- package/cli/storybook-dist/assets/Docs-Q6r1fBeL.js +12 -0
- package/cli/storybook-dist/assets/DocsRenderer-CFRXHY34-_OmZBCn8.js +2 -0
- package/cli/storybook-dist/assets/NxAvatar.stories-Ctf5G2FQ.js +13 -0
- package/cli/storybook-dist/assets/NxButton.stories-DeLObVSj.js +20 -0
- package/cli/storybook-dist/assets/NxIcons.stories-DeqrVzVs.js +3 -0
- package/cli/storybook-dist/assets/NxNumberInput.stories-LqoY0ed1.js +26 -0
- package/cli/storybook-dist/assets/NxPagination.stories-DI6bXuJM.js +11 -0
- package/cli/storybook-dist/assets/NxPointerHoldButton.stories-3HSV1XmN.js +14 -0
- package/cli/storybook-dist/assets/NxRangeInput.stories-1pjgQVaG.js +17 -0
- package/cli/storybook-dist/assets/NxSelectBox.stories-bcQPZEuF.js +17 -0
- package/cli/storybook-dist/assets/NxSlideRange.stories-BosX4axk.js +14 -0
- package/cli/storybook-dist/assets/NxSwitch.stories-DxV6dS33.js +11 -0
- package/cli/storybook-dist/assets/NxTextArea.stories-CO545-LP.js +11 -0
- package/cli/storybook-dist/assets/NxTextInput.stories-BP0npai1.js +11 -0
- package/cli/storybook-dist/assets/PretendardVariable-CJuje-Rk.woff2 +0 -0
- package/cli/storybook-dist/assets/RBAccordion.stories-fVR4Va2a.js +17 -0
- package/cli/storybook-dist/assets/RBAccordionGroup.stories-Du5bsckD.js +33 -0
- package/cli/storybook-dist/assets/RBAlert.stories-DNkMXUu6.js +17 -0
- package/cli/storybook-dist/assets/RBBadge.stories-C44pXt8D.js +1 -0
- package/cli/storybook-dist/assets/RBBattery.stories-DE2JHvfL.js +10 -0
- package/cli/storybook-dist/assets/RBCard.stories-CuIda7NI.js +45 -0
- package/cli/storybook-dist/assets/RBCheckBox.stories-D1qM1ayr.js +1 -0
- package/cli/storybook-dist/assets/RBCheckIconButton.stories-e4gkliBO.js +1 -0
- package/cli/storybook-dist/assets/RBCheckLabelButton.stories-CDzCtsMI.js +24 -0
- package/cli/storybook-dist/assets/RBChipTab.stories-DBrc_3Mp.js +21 -0
- package/cli/storybook-dist/assets/RBChipsBoxTab.stories-hlrsHjKY.js +1 -0
- package/cli/storybook-dist/assets/RBColorIcons.stories-BHUvpkdu.js +3 -0
- package/cli/storybook-dist/assets/RBCommand.stories-azAF41NP.js +73 -0
- package/cli/storybook-dist/assets/RBConfirm.stories-BMNjwbmT.js +20 -0
- package/cli/storybook-dist/assets/RBDateInput.stories-D0VezDzv.js +15 -0
- package/cli/storybook-dist/assets/RBDialog.stories-AOJ975Fy.js +38 -0
- package/cli/storybook-dist/assets/RBDivider.stories-NSvnj43Z.js +1 -0
- package/cli/storybook-dist/assets/RBDrawer.stories-Du0oTvD1.js +14 -0
- package/cli/storybook-dist/assets/RBDropdownMenu.stories-Hsxve4PW.js +86 -0
- package/cli/storybook-dist/assets/RBEmptyView.stories-BlwHx3zp.js +12 -0
- package/cli/storybook-dist/assets/RBIconButton.stories-DAPDY63x.js +1 -0
- package/cli/storybook-dist/assets/RBIcons.stories-W5ZUV2L1.js +3 -0
- package/cli/storybook-dist/assets/RBInlineLabelButton.stories-C99j0Y3g.js +1 -0
- package/cli/storybook-dist/assets/RBJoyStick.stories-Du0H1iAS.js +11 -0
- package/cli/storybook-dist/assets/RBLabelButton.stories-BlvHaU_Z.js +1 -0
- package/cli/storybook-dist/assets/RBLight.stories-BY6DKrre.js +1 -0
- package/cli/storybook-dist/assets/RBMenubar.stories-D2PEGpM9.js +3 -0
- package/cli/storybook-dist/assets/RBMultiRangeInput.stories-Bm2qSmjq.js +14 -0
- package/cli/storybook-dist/assets/RBPagination.stories-DDleJa5N.js +17 -0
- package/cli/storybook-dist/assets/RBPopover.stories-27YxPSFG.js +11 -0
- package/cli/storybook-dist/assets/RBPrompt.stories-BFaIOctr.js +20 -0
- package/cli/storybook-dist/assets/RBRadioBox.stories-BroDar0t.js +1 -0
- package/cli/storybook-dist/assets/RBRadioButton.stories-M3O-wX7V.js +11 -0
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-CWlM_D5X.js +15 -0
- package/cli/storybook-dist/assets/RBResizable.stories-ui8X1qnc.js +20 -0
- package/cli/storybook-dist/assets/RBSelectBox.stories-C8T6cfA0.js +74 -0
- package/cli/storybook-dist/assets/RBSheet.stories-BCOo_Zcc.js +39 -0
- package/cli/storybook-dist/assets/RBSideNumberInput.stories-CU2_MVlJ.js +5 -0
- package/cli/storybook-dist/assets/RBSlider.stories-DgOp1TFc.js +5 -0
- package/cli/storybook-dist/assets/RBSpinner.stories-qLScY3zZ.js +14 -0
- package/cli/storybook-dist/assets/RBSwitch.stories-SXSY1QMb.js +65 -0
- package/cli/storybook-dist/assets/RBTab.stories-BzXXTsXP.js +1 -0
- package/cli/storybook-dist/assets/RBTable.stories-DHTeRNVk.js +62 -0
- package/cli/storybook-dist/assets/RBTableRow.stories-DRNRsd15.js +11 -0
- package/cli/storybook-dist/assets/RBTabs.stories-DP0noBLp.js +21 -0
- package/cli/storybook-dist/assets/RBTextArea.stories-TkaejfxJ.js +5 -0
- package/cli/storybook-dist/assets/RBTextField.stories-B6VpCGyt.js +45 -0
- package/cli/storybook-dist/assets/RBTextInput.stories-D3hZ08e-.js +5 -0
- package/cli/storybook-dist/assets/RBToast.stories-BQ2wS2U0.js +11 -0
- package/cli/storybook-dist/assets/RBToggle.stories-GVOjGqo3.js +12 -0
- package/cli/storybook-dist/assets/RBToggleGroup.stories-pzx0btOD.js +17 -0
- package/cli/storybook-dist/assets/RBTooltip.stories-jufB6fa1.js +9 -0
- package/cli/storybook-dist/assets/SideNumberInput-BYlmNHEw.js +1109 -0
- package/cli/storybook-dist/assets/SideNumberInput-SAkhydsn.css +21 -0
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
- package/cli/storybook-dist/assets/chunk-XP5HYGXS-BHXC9YeQ.js +1 -0
- package/cli/storybook-dist/assets/client-DTf7SId2.js +1 -0
- package/cli/storybook-dist/assets/entry-preview-CAkU-e0D.js +2 -0
- package/cli/storybook-dist/assets/entry-preview-docs-b8bpVBXy.js +46 -0
- package/cli/storybook-dist/assets/iframe-B-BmTsxV.js +211 -0
- package/cli/storybook-dist/assets/index-B4FGfHer.js +1 -0
- package/cli/storybook-dist/assets/index-BRq5TIsn.js +24 -0
- package/cli/storybook-dist/assets/index-CT3U1fyk.js +1 -0
- package/cli/storybook-dist/assets/index-ChsGqxH_.js +9 -0
- package/cli/storybook-dist/assets/index-CkB0GfaI.js +575 -0
- package/cli/storybook-dist/assets/index-ClAc6FBW.js +240 -0
- package/cli/storybook-dist/assets/index-DKFBREX_.js +1 -0
- package/cli/storybook-dist/assets/index-DgH-xKnr.js +11 -0
- package/cli/storybook-dist/assets/index-Dmy2JGbm.js +1 -0
- package/cli/storybook-dist/assets/index-DrFu-skq.js +6 -0
- package/cli/storybook-dist/assets/jsx-runtime-BTJTZTIL.js +9 -0
- package/cli/storybook-dist/assets/preview-B8lJiyuQ.js +34 -0
- package/cli/storybook-dist/assets/preview-BBWR9nbA.js +1 -0
- package/cli/storybook-dist/assets/preview-BVJwlGUF.js +1 -0
- package/cli/storybook-dist/assets/preview-BWzBA1C2.js +396 -0
- package/cli/storybook-dist/assets/preview-CVgpLj3b.js +1 -0
- package/cli/storybook-dist/assets/preview-CvbIS5ZJ.js +1 -0
- package/cli/storybook-dist/assets/preview-CznQwwbo.js +3 -0
- package/cli/storybook-dist/assets/preview-DD_OYowb.js +1 -0
- package/cli/storybook-dist/assets/preview-DGUiP6tS.js +7 -0
- package/cli/storybook-dist/assets/preview-DHQbi4pV.js +1 -0
- package/cli/storybook-dist/assets/preview-M77JRg1A.js +2 -0
- package/cli/storybook-dist/assets/preview-kTZPgP6D.css +1 -0
- package/cli/storybook-dist/assets/react-18-hB3smqWJ.js +1 -0
- package/cli/storybook-dist/assets/react-resizable-panels-HsFbxPbn.css +1 -0
- package/cli/storybook-dist/assets/react-resizable-panels.browser-DuxpZLkF.js +120 -0
- package/cli/storybook-dist/assets/test-utils-BQudk2xt.js +9 -0
- package/cli/storybook-dist/favicon.svg +1 -0
- package/cli/storybook-dist/iframe.html +666 -0
- package/cli/storybook-dist/index.html +190 -0
- package/cli/storybook-dist/index.json +1 -0
- package/cli/storybook-dist/nunito-sans-bold-italic.woff2 +0 -0
- package/cli/storybook-dist/nunito-sans-bold.woff2 +0 -0
- package/cli/storybook-dist/nunito-sans-italic.woff2 +0 -0
- package/cli/storybook-dist/nunito-sans-regular.woff2 +0 -0
- package/cli/storybook-dist/project.json +1 -0
- package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js +331 -0
- package/cli/storybook-dist/sb-addons/chromatic-com-storybook-10/manager-bundle.js.LEGAL.txt +51 -0
- package/cli/storybook-dist/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
- package/cli/storybook-dist/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
- package/cli/storybook-dist/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
- package/cli/storybook-dist/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/links-1/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/storybook-13/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-addons/storybook-experimental-addon-test-12/manager-bundle.js +223 -0
- package/cli/storybook-dist/sb-addons/themes-11/manager-bundle.js +3 -0
- package/cli/storybook-dist/sb-common-assets/favicon.svg +1 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
- package/cli/storybook-dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
- package/cli/storybook-dist/sb-manager/globals-module-info.js +1052 -0
- package/cli/storybook-dist/sb-manager/globals-runtime.js +41775 -0
- package/cli/storybook-dist/sb-manager/globals.js +48 -0
- package/cli/storybook-dist/sb-manager/runtime.js +12048 -0
- package/cli/templates/common/README.md +1070 -83
- package/cli/templates/common/assets/icon.svg +44 -2
- package/cli/templates/common/manifest.json +14 -13
- package/cli/templates/common/package.json +11 -4
- package/cli/templates/common/py/main.py +125 -0
- package/cli/templates/common/requirements.txt +4 -3
- package/cli/templates/common/tsconfig.json +1 -1
- package/cli/templates/common/ui/content.tsx +216 -0
- package/cli/templates/common/ui/style.css +6 -0
- package/cli/templates/common/vite.config.ts +6 -25
- package/cli/templates/cpp/cpp/CMakeLists.txt +14 -0
- package/cli/templates/cpp/cpp/main.cpp +73 -0
- package/cli/templates/readme-cpp/README.md +1012 -0
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +12 -1
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +22 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +41 -0
- package/cli/vite.d.mts +25 -0
- package/cli/vite.mjs +292 -0
- package/package.json +34 -7
- package/cli/src/pack.mjs +0 -61
- package/cli/src/preview.mjs +0 -91
- package/cli/templates/common/main.py +0 -114
- package/cli/templates/common/src/content.tsx +0 -33
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// `muscat-pk mcp` — AI clients keep one stdio MCP server per session, so a `muscat-pk update`
|
|
2
|
+
// never reached a running one. This relays stdio to the real server (`mcp --serve`) and, when the
|
|
3
|
+
// installed kit changes, swaps that child for the new one: it replays the client's initialize and
|
|
4
|
+
// sends tools/list_changed, so the connection stays up and the next calls use the new tools.
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { statSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { createInterface } from "node:readline";
|
|
9
|
+
import { kitRoot, resolveLocalCliBin } from "./util.mjs";
|
|
10
|
+
|
|
11
|
+
const isBinaryMode = typeof Bun !== "undefined";
|
|
12
|
+
const REINIT_ID = "__muscat_pk_reinit__";
|
|
13
|
+
const POLL_MS = 3000;
|
|
14
|
+
|
|
15
|
+
// Re-resolved on every (re)start: an update may install, bump or remove the project's local kit.
|
|
16
|
+
function serverCommand() {
|
|
17
|
+
const local = resolveLocalCliBin(process.cwd());
|
|
18
|
+
if (local) {
|
|
19
|
+
return { cmd: isBinaryMode ? "node" : process.execPath, args: [local, "mcp", "--serve"], watch: resolve(local, "../../../package.json") };
|
|
20
|
+
}
|
|
21
|
+
if (isBinaryMode) return { cmd: process.execPath, args: ["mcp", "--serve"], watch: process.execPath };
|
|
22
|
+
return { cmd: process.execPath, args: [process.argv[1], "mcp", "--serve"], watch: resolve(kitRoot, "package.json") };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const stamp = (file) => {
|
|
26
|
+
try {
|
|
27
|
+
return `${file}:${statSync(file).mtimeMs}`;
|
|
28
|
+
} catch {
|
|
29
|
+
return file;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function runMcpProxy() {
|
|
34
|
+
let child = null;
|
|
35
|
+
let initRequest = null;
|
|
36
|
+
const pending = new Set(); // client request ids the current child hasn't answered
|
|
37
|
+
const toClient = (msg) => process.stdout.write(JSON.stringify(msg) + "\n");
|
|
38
|
+
|
|
39
|
+
const failPending = () => {
|
|
40
|
+
for (const id of pending) {
|
|
41
|
+
toClient({ jsonrpc: "2.0", id, error: { code: -32603, message: "muscat-pk was updated during this call — retry it." } });
|
|
42
|
+
}
|
|
43
|
+
pending.clear();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const start = () => {
|
|
47
|
+
const { cmd, args } = serverCommand();
|
|
48
|
+
const c = spawn(cmd, args, { stdio: ["pipe", "pipe", "inherit"] });
|
|
49
|
+
createInterface({ input: c.stdout }).on("line", (line) => {
|
|
50
|
+
let msg = null;
|
|
51
|
+
try {
|
|
52
|
+
msg = JSON.parse(line);
|
|
53
|
+
} catch {
|
|
54
|
+
/* not JSON — pass through */
|
|
55
|
+
}
|
|
56
|
+
if (msg?.id === REINIT_ID) {
|
|
57
|
+
// Our replayed initialize: the client already has its answer from the first child.
|
|
58
|
+
c.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n");
|
|
59
|
+
toClient({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (msg && msg.id !== undefined && !msg.method) pending.delete(msg.id);
|
|
63
|
+
process.stdout.write(line + "\n");
|
|
64
|
+
});
|
|
65
|
+
c.on("exit", () => {
|
|
66
|
+
if (c !== child) return; // replaced on purpose
|
|
67
|
+
child = null;
|
|
68
|
+
failPending();
|
|
69
|
+
});
|
|
70
|
+
child = c;
|
|
71
|
+
if (initRequest) c.stdin.write(JSON.stringify({ ...initRequest, id: REINIT_ID }) + "\n");
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const restart = () => {
|
|
75
|
+
const old = child;
|
|
76
|
+
child = null;
|
|
77
|
+
old?.kill();
|
|
78
|
+
failPending();
|
|
79
|
+
start();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
start();
|
|
83
|
+
let last = stamp(serverCommand().watch);
|
|
84
|
+
setInterval(() => {
|
|
85
|
+
const now = stamp(serverCommand().watch);
|
|
86
|
+
if (now !== last) {
|
|
87
|
+
last = now;
|
|
88
|
+
process.stderr.write("[muscat-pk] kit updated — restarting the MCP server\n");
|
|
89
|
+
restart();
|
|
90
|
+
}
|
|
91
|
+
}, POLL_MS).unref();
|
|
92
|
+
|
|
93
|
+
createInterface({ input: process.stdin })
|
|
94
|
+
.on("line", (line) => {
|
|
95
|
+
let msg = null;
|
|
96
|
+
try {
|
|
97
|
+
msg = JSON.parse(line);
|
|
98
|
+
} catch {
|
|
99
|
+
/* forward as-is */
|
|
100
|
+
}
|
|
101
|
+
if (msg?.method === "initialize") initRequest = msg;
|
|
102
|
+
if (!child) start(); // the server crashed — bring it back (it replays initialize)
|
|
103
|
+
if (msg && msg.id !== undefined && msg.method) pending.add(msg.id);
|
|
104
|
+
child.stdin.write(line + "\n");
|
|
105
|
+
})
|
|
106
|
+
.on("close", () => {
|
|
107
|
+
child?.kill();
|
|
108
|
+
process.exit(0);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Local Muscat runtime for `dev` / `dev:ui`, with or without a robot. It is the plugin gateway
|
|
2
|
+
// (:10000, plugin-socketio — same Socket.IO protocol as on the robot), MediaMTX, and `common`:
|
|
3
|
+
// preview-host hits http://localhost:3000/common/* (baked into the nexus bundle), so program
|
|
4
|
+
// preview works with no front-end change. The backend container joins its netns. We reuse any
|
|
5
|
+
// Muscat already publishing :10000 (e.g. a `muscat` / `muscat-dev` container) and only otherwise
|
|
6
|
+
// start our own: the S3 runtime image with `common` (plus the services `--sim` needs).
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { createServer } from "node:net";
|
|
9
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { dirname, resolve } from "node:path";
|
|
12
|
+
import { mediaPortFlags, MEDIA_PORTS, publishPortFlags } from "./ports.mjs";
|
|
13
|
+
import { canSkipImageBuild, hasBin, log, buildCacheFlags, pruneMuskatImages } from "./util.mjs";
|
|
14
|
+
import { LOCAL_HOSTS } from "./zenohDev.mjs";
|
|
15
|
+
|
|
16
|
+
export const MUSCAT_CONTAINER = "muscat-pk-muscat";
|
|
17
|
+
// Ports published for the backend (it shares this container's netns); a different set recreates it.
|
|
18
|
+
const PORTS_LABEL = "com.rainbow-robotics.muscat-pk.ports";
|
|
19
|
+
const IMAGE = "muscat-pk-runtime";
|
|
20
|
+
const S3 = process.env.MUSCAT_PK_MUSCAT_S3 ?? "https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat/main";
|
|
21
|
+
const CACHE = resolve(homedir(), ".muscat-pk", "muscat");
|
|
22
|
+
const API_PORT = 3000;
|
|
23
|
+
export const GATEWAY_PORT = 10000;
|
|
24
|
+
// The bin runs inside Docker, so follow the Docker engine's arch, not this Node's
|
|
25
|
+
// (Rosetta/x64 Node on an Apple Silicon host would otherwise pick amd64).
|
|
26
|
+
const dockerArch = () => {
|
|
27
|
+
const r = spawnSync("docker", ["version", "--format", "{{.Server.Arch}}"], { encoding: "utf8" });
|
|
28
|
+
const a = (r.stdout || "").trim();
|
|
29
|
+
return a === "arm64" || a === "amd64" ? a : process.arch === "arm64" ? "arm64" : "amd64";
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Node's fetch reports every network error as "fetch failed"; the real reason (ECONNRESET, ...) is in `cause`.
|
|
33
|
+
const fetchErrorReason = (e) => e.cause?.code ?? e.code ?? e.cause?.message ?? e.message;
|
|
34
|
+
|
|
35
|
+
// The S3 zips are ~200MB, so a dropped connection mid-download is retried. 4xx is not.
|
|
36
|
+
export async function fetchBuffer(url, attempts = 3) {
|
|
37
|
+
for (let i = 1; ; i++) {
|
|
38
|
+
let err;
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(url);
|
|
41
|
+
if (res.ok) return Buffer.from(await res.arrayBuffer());
|
|
42
|
+
err = new Error(`HTTP ${res.status}: ${url}`);
|
|
43
|
+
if (res.status < 500 && res.status !== 429) throw err;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
if (e === err) throw e;
|
|
46
|
+
err = new Error(`fetch failed (${fetchErrorReason(e)}): ${url}`, { cause: e });
|
|
47
|
+
}
|
|
48
|
+
if (i >= attempts) throw err;
|
|
49
|
+
log.warn(`${err.message} — retrying (${i + 1}/${attempts})…`);
|
|
50
|
+
await new Promise((r) => setTimeout(r, 1000 * i));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// macOS/Windows `tar` reads zip; GNU tar does not, Linux has `unzip`.
|
|
55
|
+
function extractZip(zip, dir, members = []) {
|
|
56
|
+
mkdirSync(dir, { recursive: true });
|
|
57
|
+
const tries = [
|
|
58
|
+
["unzip", ["-o", "-q", zip, ...members, "-d", dir]],
|
|
59
|
+
["tar", ["-xf", zip, "-C", dir, ...members]],
|
|
60
|
+
];
|
|
61
|
+
for (const [bin, args] of tries) {
|
|
62
|
+
if (hasBin(bin) && spawnSync(bin, args, { stdio: "ignore" }).status === 0) return;
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`could not extract ${zip} (needs unzip or tar)`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function resolveVersions() {
|
|
68
|
+
const file = resolve(CACHE, "version.json");
|
|
69
|
+
try {
|
|
70
|
+
const data = await fetchBuffer(`${S3}/version.json`);
|
|
71
|
+
mkdirSync(CACHE, { recursive: true });
|
|
72
|
+
writeFileSync(file, data);
|
|
73
|
+
return JSON.parse(data.toString());
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (!existsSync(file)) throw e;
|
|
76
|
+
log.warn(`version.json unreachable (${e.message}) — using cached Muscat version`);
|
|
77
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A new Muscat version replaces the previous runtime image (~2.8GB) and its scripts. One still in
|
|
82
|
+
// use by the running local Muscat stays until a later run (rmi refuses it).
|
|
83
|
+
export function removeOldRuntimes(total) {
|
|
84
|
+
const tags = spawnSync("docker", ["images", IMAGE, "--format", "{{.Tag}}"], { encoding: "utf8" }).stdout ?? "";
|
|
85
|
+
for (const tag of tags.split("\n").map((t) => t.trim()).filter((t) => t && t !== total)) {
|
|
86
|
+
spawnSync("docker", ["rmi", `${IMAGE}:${tag}`], { stdio: "ignore" });
|
|
87
|
+
}
|
|
88
|
+
const dir = resolve(CACHE, "runtime");
|
|
89
|
+
for (const old of existsSync(dir) ? readdirSync(dir) : []) {
|
|
90
|
+
if (old !== total) rmSync(resolve(dir, old), { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function ensureRuntimeImage(total) {
|
|
95
|
+
const image = `${IMAGE}:${total}`;
|
|
96
|
+
removeOldRuntimes(total);
|
|
97
|
+
if (canSkipImageBuild(image)) return image;
|
|
98
|
+
const dir = resolve(CACHE, "runtime", total);
|
|
99
|
+
if (!existsSync(resolve(dir, "Dockerfile.runtime"))) {
|
|
100
|
+
log.step(`downloading Muscat runtime scripts (muscat.zip ${total})…`);
|
|
101
|
+
const zip = resolve(CACHE, "muscat.zip");
|
|
102
|
+
writeFileSync(zip, await fetchBuffer(`${S3}/muscat.zip`));
|
|
103
|
+
extractZip(zip, dir);
|
|
104
|
+
rmSync(zip, { force: true });
|
|
105
|
+
}
|
|
106
|
+
log.step(`docker build -t ${image} (first run downloads MongoDB/InfluxDB/ROS base — slow, then cached)…`);
|
|
107
|
+
const r = spawnSync(
|
|
108
|
+
"docker",
|
|
109
|
+
["build", ...buildCacheFlags(), "-t", image, "-f", resolve(dir, "Dockerfile.runtime"), "--label", "com.rainbow-robotics.muscat-pk=true", dir],
|
|
110
|
+
{ stdio: "inherit" },
|
|
111
|
+
);
|
|
112
|
+
if (r.status !== 0) throw new Error("docker build failed (Muscat runtime image).");
|
|
113
|
+
pruneMuskatImages();
|
|
114
|
+
return image;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function ensureServiceBinary(svc, version) {
|
|
118
|
+
const ARCH = dockerArch();
|
|
119
|
+
const dir = resolve(CACHE, "services", svc);
|
|
120
|
+
const stamp = resolve(dir, ".version");
|
|
121
|
+
if (existsSync(stamp) && readFileSync(stamp, "utf8").trim() === version && existsSync(resolve(dir, `${svc}.${ARCH}.bin`))) return dir;
|
|
122
|
+
log.step(`downloading Muscat ${svc} service ${version} (${ARCH})…`);
|
|
123
|
+
const zip = resolve(CACHE, `${svc}-${version}.zip`);
|
|
124
|
+
writeFileSync(zip, await fetchBuffer(`${S3}/${svc}/${version}.zip`));
|
|
125
|
+
extractZip(zip, dir, ["config.env", `${svc}.${ARCH}.bin`]);
|
|
126
|
+
rmSync(zip, { force: true });
|
|
127
|
+
chmodSync(resolve(dir, `${svc}.${ARCH}.bin`), 0o755);
|
|
128
|
+
writeFileSync(stamp, `${version}\n`);
|
|
129
|
+
return dir;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Sim containers live on this network and connect to common's Zenoh by container name.
|
|
133
|
+
export const MUSCAT_NETWORK = "muscat-pk";
|
|
134
|
+
|
|
135
|
+
function ensureNetwork() {
|
|
136
|
+
if (spawnSync("docker", ["network", "inspect", MUSCAT_NETWORK], { stdio: "ignore" }).status === 0) return;
|
|
137
|
+
spawnSync("docker", ["network", "create", "--label", "com.rainbow-robotics.muscat-pk=true", MUSCAT_NETWORK], { stdio: "ignore" });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// common (127.0.0.1:7447) and the services (127.0.0.1:0) listen on loopback by default; rb-ctl's
|
|
141
|
+
// listen-endpoints file opens both to the network. Sims reach common as muscat-pk-muscat:7447, and
|
|
142
|
+
// gossip has them dial each service back — on loopback only the sim that happened to connect first
|
|
143
|
+
// answers manipulate's receive_one.
|
|
144
|
+
// Mounted as the only file of /app/data; the rest of the runtime's data stays in the container.
|
|
145
|
+
function zenohListenMount(open) {
|
|
146
|
+
if (!open) return [];
|
|
147
|
+
const file = resolve(CACHE, "zenoh_listen_endpoints.json5");
|
|
148
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
149
|
+
writeFileSync(file, `{\n common: ["tcp/0.0.0.0:7447"],\n service: ["tcp/0.0.0.0:0"],\n}\n`);
|
|
150
|
+
return ["-v", `${file}:/app/data/zenoh_listen_endpoints.json5:ro`];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function waitForApi(timeoutMs = 180_000) {
|
|
154
|
+
const start = Date.now();
|
|
155
|
+
for (;;) {
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetch(`http://127.0.0.1:${API_PORT}/common/docs`);
|
|
158
|
+
if (res.ok) return;
|
|
159
|
+
} catch { /* not listening yet */ }
|
|
160
|
+
if (Date.now() - start > timeoutMs) throw new Error(`Muscat did not answer on :${API_PORT} in time — see \`docker logs ${MUSCAT_CONTAINER}\``);
|
|
161
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Does the robot at `ip` run Muscat (common answers /is-muscat)? Then the dev UI and backend use its
|
|
166
|
+
// gateway and no local Muscat is started. Local addresses and unreachable hosts → false.
|
|
167
|
+
async function isMuscat(ip) {
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch(`http://${ip}:${API_PORT}/common/is-muscat`, { signal: AbortSignal.timeout(3000) });
|
|
170
|
+
return res.ok && (await res.json())?.result === true;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function robotHasMuscat(ip) {
|
|
177
|
+
if (!ip || LOCAL_HOSTS.has(ip)) return false;
|
|
178
|
+
return isMuscat(ip);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// rby2's full Muscat dev stack on this machine — unlike muscat-pk's own minimal runtime it has the
|
|
182
|
+
// whole UI backend, so `dev` without a robot IP shows its real Muscat UI, like a robot's.
|
|
183
|
+
const LOCAL_FULL_MUSCAT = "muscat-dev";
|
|
184
|
+
export async function localFullMuscat(ip) {
|
|
185
|
+
if (!LOCAL_HOSTS.has(ip ?? "") || !hasBin("docker")) return false;
|
|
186
|
+
const r = spawnSync("docker", ["ps", "--filter", `name=^${LOCAL_FULL_MUSCAT}$`, "--format", "{{.Names}}"], { encoding: "utf8" });
|
|
187
|
+
return !!(r.stdout || "").trim() && isMuscat("127.0.0.1");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Is a host TCP port already bound?
|
|
191
|
+
export function isPortInUse(port) {
|
|
192
|
+
return new Promise((res) => {
|
|
193
|
+
const srv = createServer();
|
|
194
|
+
srv.once("error", (err) => res(err.code === "EADDRINUSE"));
|
|
195
|
+
srv.once("listening", () => srv.close(() => res(false)));
|
|
196
|
+
srv.listen(port, "0.0.0.0");
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A running Muscat container (ours or e.g. muscat-dev) that owns host port 10000, or null. */
|
|
201
|
+
export function localMuscatContainer() {
|
|
202
|
+
const r = spawnSync("docker", ["ps", "--filter", `publish=${GATEWAY_PORT}`, "--format", "{{.Names}}"], { encoding: "utf8" });
|
|
203
|
+
return (r.stdout || "").split("\n").map((n) => n.trim()).find(Boolean) ?? null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const containerLabel = (name, label) =>
|
|
207
|
+
spawnSync("docker", ["inspect", "-f", `{{index .Config.Labels "${label}"}}`, name], { encoding: "utf8" }).stdout?.trim() ?? "";
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Make sure a Muscat answers on localhost:3000 / :10000. Returns the container name,
|
|
211
|
+
* or null when one can't be started (no Docker, foreign process on the ports).
|
|
212
|
+
* `dev` removes it on exit; `dev:ui` alone leaves it running (`docker rm -f muscat-pk-muscat`).
|
|
213
|
+
*/
|
|
214
|
+
export async function ensureLocalMuscat({ pluginPorts = [], zenohPort = null, services = [], env = {} } = {}) {
|
|
215
|
+
if (process.env.MUSCAT_PK_NO_LOCAL_MUSCAT === "1") return null;
|
|
216
|
+
// Extra services (log, manipulate, rby1, ...) and env come with --sim; a different set recreates it too.
|
|
217
|
+
const svcs = ["common", ...services.filter((s) => s !== "common")];
|
|
218
|
+
const envFlags = Object.entries(env).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
|
|
219
|
+
const portsKey = [
|
|
220
|
+
...pluginPorts,
|
|
221
|
+
...(zenohPort ? [`zenoh:${zenohPort}`] : []),
|
|
222
|
+
"ipc:host",
|
|
223
|
+
...(services.length ? [`svc:${svcs.join("+")}`] : []),
|
|
224
|
+
...Object.entries(env).map(([k, v]) => `${k}=${v}`),
|
|
225
|
+
].join(",");
|
|
226
|
+
const existing = hasBin("docker") ? localMuscatContainer() : null;
|
|
227
|
+
if (existing === MUSCAT_CONTAINER && containerLabel(existing, PORTS_LABEL) !== portsKey) {
|
|
228
|
+
log.step(`recreating local Muscat for ${portsKey || "no extra ports / services"}`);
|
|
229
|
+
spawnSync("docker", ["rm", "-f", MUSCAT_CONTAINER], { stdio: "ignore" });
|
|
230
|
+
} else if (existing) {
|
|
231
|
+
if (existing !== MUSCAT_CONTAINER && portsKey)
|
|
232
|
+
log.warn(`'${existing}' is not managed by muscat-pk — port(s) ${portsKey} are not published from it.`);
|
|
233
|
+
return existing;
|
|
234
|
+
}
|
|
235
|
+
if (!hasBin("docker")) {
|
|
236
|
+
log.warn("Docker not found — no local Muscat; program preview will have no back end.");
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
// Ports taken but no container publishing them: usually a Muscat on host networking (robot PC,
|
|
240
|
+
// CI runner) — the caller uses it as is.
|
|
241
|
+
for (const port of [API_PORT, GATEWAY_PORT]) {
|
|
242
|
+
if (await isPortInUse(port)) return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const versions = await resolveVersions();
|
|
246
|
+
const total = versions.total;
|
|
247
|
+
if (!total) throw new Error("version.json has no total");
|
|
248
|
+
const missing = svcs.filter((s) => !versions.services?.[s]?.version);
|
|
249
|
+
if (missing.length) throw new Error(`version.json has no services.${missing.join(" / ")}.version`);
|
|
250
|
+
|
|
251
|
+
const image = await ensureRuntimeImage(total);
|
|
252
|
+
const svcMounts = [];
|
|
253
|
+
for (const s of svcs) svcMounts.push("-v", `${await ensureServiceBinary(s, versions.services[s].version)}:/app/services/${s}`);
|
|
254
|
+
ensureNetwork();
|
|
255
|
+
// Earlier kits kept DB, logs and programs here across runs.
|
|
256
|
+
rmSync(resolve(CACHE, "data"), { recursive: true, force: true });
|
|
257
|
+
|
|
258
|
+
let media = true;
|
|
259
|
+
for (const port of MEDIA_PORTS) if (await isPortInUse(port)) media = false;
|
|
260
|
+
// A busy plugin port would make `docker run` fail with a raw daemon error — skip it and say so.
|
|
261
|
+
const busy = [];
|
|
262
|
+
for (const port of pluginPorts) if (await isPortInUse(port)) busy.push(port);
|
|
263
|
+
if (busy.length) log.warn(`plugin port(s) already in use on the host: ${busy.join(", ")} — not published (the backend still listens inside).`);
|
|
264
|
+
const published = pluginPorts.filter((port) => !busy.includes(port));
|
|
265
|
+
|
|
266
|
+
spawnSync("docker", ["rm", "-f", MUSCAT_CONTAINER], { stdio: "ignore" });
|
|
267
|
+
log.step(`starting local Muscat (${svcs.map((s) => `${s} ${versions.services[s].version}`).join(", ")}) → http://localhost:${API_PORT}/common/docs`);
|
|
268
|
+
const r = spawnSync("docker", [
|
|
269
|
+
"run", "-d", "--name", MUSCAT_CONTAINER, "--label", "com.rainbow-robotics.muscat-pk=true", "--label", `${PORTS_LABEL}=${portsKey}`,
|
|
270
|
+
"--network", MUSCAT_NETWORK,
|
|
271
|
+
// Like the robot, where plugins run with --ipc host: zenoh SHM and plugin shm_open reach the services.
|
|
272
|
+
"--ipc", "host",
|
|
273
|
+
"-e", `RB_ENABLED_SERVICES=${svcs.join(",")}`,
|
|
274
|
+
"-e", "RB_RUNTIME_ENABLE_ZENOH_BRIDGE=0",
|
|
275
|
+
...envFlags,
|
|
276
|
+
...svcMounts,
|
|
277
|
+
...zenohListenMount(services.length > 0),
|
|
278
|
+
"-p", `127.0.0.1:${API_PORT}:${API_PORT}`,
|
|
279
|
+
"-p", `127.0.0.1:${GATEWAY_PORT}:${GATEWAY_PORT}`,
|
|
280
|
+
...(media ? mediaPortFlags() : []),
|
|
281
|
+
...publishPortFlags(published),
|
|
282
|
+
// The robot reverse-dials the backend's Zenoh session from the LAN, so not loopback-only.
|
|
283
|
+
...(zenohPort ? ["-p", `${zenohPort}:${zenohPort}`] : []),
|
|
284
|
+
image,
|
|
285
|
+
], { stdio: ["ignore", "ignore", "inherit"] });
|
|
286
|
+
if (r.status !== 0) throw new Error("failed to start the local Muscat container.");
|
|
287
|
+
await waitForApi();
|
|
288
|
+
log.step("local Muscat ready");
|
|
289
|
+
if (published.length) log.step(`plugin backend ports published (TCP/UDP): ${published.join(", ")}`);
|
|
290
|
+
if (zenohPort) log.step(`Zenoh dev peer port published on :${zenohPort}`);
|
|
291
|
+
return MUSCAT_CONTAINER;
|
|
292
|
+
}
|