@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,193 @@
|
|
|
1
|
+
// Port contract helpers for plugin back ends.
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { usesHostNetwork } from "./zenohDev.mjs";
|
|
4
|
+
|
|
5
|
+
export const PLUGIN_PORT_MIN = 28600;
|
|
6
|
+
export const PLUGIN_PORT_MAX = 28999;
|
|
7
|
+
export const MEDIA_PORTS = [8554, 8889, 8189]; // MediaMTX: RTSP publish, WebRTC, WebRTC media
|
|
8
|
+
export const INTERNAL_DEV_PORTS = new Set([10000, ...MEDIA_PORTS]);
|
|
9
|
+
|
|
10
|
+
export function mediaPortFlags() {
|
|
11
|
+
return [
|
|
12
|
+
"-p", "127.0.0.1:8554:8554", "-p", "127.0.0.1:8889:8889",
|
|
13
|
+
"-p", "127.0.0.1:8189:8189/udp", "-p", "127.0.0.1:8189:8189/tcp",
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
export const RESERVED_BACKEND_PORTS = new Map([
|
|
17
|
+
[502, "Modbus TCP"],
|
|
18
|
+
[4840, "OPC-UA"],
|
|
19
|
+
[10000, "Muscat plugin gateway"],
|
|
20
|
+
[2222, "EtherNet/IP Implicit"],
|
|
21
|
+
[30004, "RTDE"],
|
|
22
|
+
[34964, "ProfiNET"],
|
|
23
|
+
[44818, "EtherNet/IP Explicit"],
|
|
24
|
+
[50051, "gRPC"],
|
|
25
|
+
]);
|
|
26
|
+
export const RESERVED_BACKEND_PORT_RANGES = [
|
|
27
|
+
{ from: 5170, to: 5179, name: "UI 517x servers" },
|
|
28
|
+
{ from: 8000, to: 8999, name: "Muscat/internal 8000 range" },
|
|
29
|
+
{ from: 50000, to: 50005, name: "robot TCP/UDP ASCII and REST interfaces" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function reservedPortReason(port) {
|
|
33
|
+
if (RESERVED_BACKEND_PORTS.has(port)) return RESERVED_BACKEND_PORTS.get(port);
|
|
34
|
+
const range = RESERVED_BACKEND_PORT_RANGES.find((item) => port >= item.from && port <= item.to);
|
|
35
|
+
if (!range) return null;
|
|
36
|
+
return `${range.name} (${range.from}-${range.to})`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// function isPluginManagedPort(port) {
|
|
40
|
+
// return reservedPortReason(port) || (port >= PLUGIN_PORT_MIN && port <= PLUGIN_PORT_MAX);
|
|
41
|
+
// }
|
|
42
|
+
|
|
43
|
+
function isInt(value) {
|
|
44
|
+
return Number.isInteger(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validatePort(port, field, errors) {
|
|
48
|
+
if (!isInt(port)) {
|
|
49
|
+
errors.push(`${field}: must be a port number`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (port < 1 || port > 65535) {
|
|
53
|
+
errors.push(`${field}: port must be between 1 and 65535`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const reserved = reservedPortReason(port);
|
|
57
|
+
if (reserved) {
|
|
58
|
+
errors.push(`${field}: ${port} is reserved for ${reserved}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (port < PLUGIN_PORT_MIN || port > PLUGIN_PORT_MAX) {
|
|
62
|
+
errors.push(
|
|
63
|
+
`${field}: external plugin backend ports must be within ${PLUGIN_PORT_MIN}-${PLUGIN_PORT_MAX}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function normalizePorts(ports, errors = []) {
|
|
69
|
+
if (ports === undefined || ports === null) return [];
|
|
70
|
+
if (!Array.isArray(ports)) {
|
|
71
|
+
errors.push(`"ports" must be an array`);
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
if (ports.length === 0) {
|
|
75
|
+
errors.push(`"ports" must not be empty when provided`);
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const normalized = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
ports.forEach((port, i) => {
|
|
82
|
+
validatePort(port, `ports[${i}]`, errors);
|
|
83
|
+
if (!isInt(port)) return;
|
|
84
|
+
if (seen.has(port)) {
|
|
85
|
+
errors.push(`"ports" entries must be unique (duplicate: ${port})`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
seen.add(port);
|
|
89
|
+
normalized.push(port);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function validatePorts(ports, errors) {
|
|
96
|
+
normalizePorts(ports, errors);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function manifestPortRanges(manifest) {
|
|
100
|
+
return normalizePorts(manifest?.ports, []);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function formatPortRange(range) {
|
|
104
|
+
return String(range);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function formatPortRanges(ranges) {
|
|
108
|
+
return ranges.map(formatPortRange).join(", ");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function portInRanges(port, ranges) {
|
|
112
|
+
return ranges.includes(port);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function portEnvFlags(ranges) {
|
|
116
|
+
if (ranges.length === 0) return [];
|
|
117
|
+
return ["-e", `RB_PLUGIN_PORTS=${ranges.map(formatPortRange).join(",")}`];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* `docker run -p` flags for the plugin's declared ports, TCP and UDP on every host interface like
|
|
122
|
+
* on the robot, so LAN devices (e.g. a Meta Quest streaming UDP) reach the backend. Native Linux
|
|
123
|
+
* without a local Muscat uses --network host instead. Only valid on the container that owns the netns.
|
|
124
|
+
*/
|
|
125
|
+
export function publishPortFlags(ranges = []) {
|
|
126
|
+
return ranges.flatMap((port) => ["-p", `${port}:${port}`, "-p", `${port}:${port}/udp`]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Skips OS-assigned loopback listeners (127.0.0.1:0, e.g. the SDK's Zenoh session).
|
|
130
|
+
function listeningTcpPorts(container) {
|
|
131
|
+
const script = `
|
|
132
|
+
read -r eph_lo eph_hi < /proc/sys/net/ipv4/ip_local_port_range 2>/dev/null || { eph_lo=32768; eph_hi=60999; }
|
|
133
|
+
for f in /proc/net/tcp /proc/net/tcp6; do
|
|
134
|
+
[ -r "$f" ] || continue
|
|
135
|
+
tail -n +2 "$f" | while read -r sl local_address rem_address st rest; do
|
|
136
|
+
[ "$st" = "0A" ] || continue
|
|
137
|
+
hex_ip="\${local_address%%:*}"
|
|
138
|
+
port=$((0x\${local_address##*:}))
|
|
139
|
+
case "$hex_ip" in
|
|
140
|
+
0100007F|00000000000000000000000001000000)
|
|
141
|
+
[ "$port" -ge "$eph_lo" ] && [ "$port" -le "$eph_hi" ] && continue ;;
|
|
142
|
+
esac
|
|
143
|
+
echo "$port"
|
|
144
|
+
done
|
|
145
|
+
done | sort -n -u
|
|
146
|
+
`;
|
|
147
|
+
const r = spawnSync("docker", ["exec", container, "bash", "-lc", script], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
});
|
|
150
|
+
if (r.status !== 0) return [];
|
|
151
|
+
return String(r.stdout || "")
|
|
152
|
+
.split(/\s+/)
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.map((v) => Number(v))
|
|
155
|
+
.filter((v) => Number.isInteger(v));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function startPortContractMonitor({ container, ranges, log, intervalMs = 1000, allow = [] }) {
|
|
159
|
+
// Under --network host (or a local Muscat container's netns) /proc/net/tcp shows every
|
|
160
|
+
// host/robot service port, not just the plugin's — the contract can't be enforced.
|
|
161
|
+
if (usesHostNetwork() || process.env.MUSCAT_PK_MUSCAT_CONTAINER) return () => false;
|
|
162
|
+
const declared = ranges ?? [];
|
|
163
|
+
// muscat-pk's own dev plumbing shares this netns — not the plugin's doing.
|
|
164
|
+
const ignored = new Set([...INTERNAL_DEV_PORTS, ...allow.map(Number).filter(Number.isInteger)]);
|
|
165
|
+
let reported = false;
|
|
166
|
+
const timer = setInterval(() => {
|
|
167
|
+
// Every listening port that isn't declared is a violation — a port outside
|
|
168
|
+
// 28600-28999 can't be declared at all, so it must not be opened either.
|
|
169
|
+
// Otherwise it silently escapes both this check and rby2's install-time
|
|
170
|
+
// conflict check, and only collides once it's on a robot.
|
|
171
|
+
const unexpected = listeningTcpPorts(container)
|
|
172
|
+
.filter((port) => !ignored.has(port))
|
|
173
|
+
.filter((port) => !portInRanges(port, declared));
|
|
174
|
+
if (unexpected.length === 0) return;
|
|
175
|
+
|
|
176
|
+
clearInterval(timer);
|
|
177
|
+
reported = true;
|
|
178
|
+
const declaredText = declared.length > 0 ? formatPortRanges(declared) : "(none)";
|
|
179
|
+
log.error(
|
|
180
|
+
`backend opened undeclared TCP port(s): ${unexpected.join(", ")}. ` +
|
|
181
|
+
`Every listening port must be in manifest.json "ports" within ${PLUGIN_PORT_MIN}-${PLUGIN_PORT_MAX}. ` +
|
|
182
|
+
`Declared: ${declaredText}`,
|
|
183
|
+
);
|
|
184
|
+
spawnSync("docker", ["rm", "-f", container], { stdio: "ignore" });
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}, intervalMs);
|
|
187
|
+
timer.unref?.();
|
|
188
|
+
|
|
189
|
+
return () => {
|
|
190
|
+
clearInterval(timer);
|
|
191
|
+
return reported;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// Built-in Program page commands for the `mcp` command's list_program_commands
|
|
2
|
+
// tool. The source of truth is nexus's program.c.tsx; it imports React/host
|
|
3
|
+
// code, so instead of running it we read its object literals with the
|
|
4
|
+
// TypeScript AST. prepack stages the result as JSON so an external install
|
|
5
|
+
// (no monorepo) gets the same list.
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { kitRoot } from "./util.mjs";
|
|
10
|
+
|
|
11
|
+
const REPO_PROGRAM_SRC = resolve(kitRoot, "../..", "apps/web/nexus/src/constants/program.c.tsx");
|
|
12
|
+
export const SHIPPED_PROGRAM_COMMANDS = resolve(kitRoot, "cli/program-commands.json");
|
|
13
|
+
|
|
14
|
+
// Exported list name → robot category the Program page shows it for.
|
|
15
|
+
const LISTS = {
|
|
16
|
+
COMMON_PROGRAM_COMMAND_LIST: "common",
|
|
17
|
+
MANIPULATE_PROGRAM_COMMAND_LIST: "manipulate",
|
|
18
|
+
AMR_PROGRAM_COMMAND_LIST: "amr",
|
|
19
|
+
RBY1_PROGRAM_COMMAND_LIST: "rby1",
|
|
20
|
+
};
|
|
21
|
+
// Reached through the Thread modal, not the palette list (nexus CALL_EVENT_PLUGIN_COMMAND).
|
|
22
|
+
const EXTRA = { CALL_EVENT_THREAD_PROGRAM_COMMAND: "common" };
|
|
23
|
+
const RULE_KEYS = [
|
|
24
|
+
"allowChildren", "allowParents", "notAllowChildren", "childrenCommand", "requireChildren",
|
|
25
|
+
"requireParent", "fixChildren", "syncGroupId", "groupFirstItem", "groupLastItem", "isStrict",
|
|
26
|
+
"disableCopy", "disableDelete", "disabled",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const UNRESOLVED = Symbol("unresolved");
|
|
30
|
+
|
|
31
|
+
function propName(name, sf) {
|
|
32
|
+
return ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : name.getText(sf);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Best-effort literal evaluation. Anything that needs the live robot or host
|
|
36
|
+
// (fetch*, uniqueId(), imported option tables) comes back as its source text
|
|
37
|
+
// in angle brackets, so the agent sees what the value depends on.
|
|
38
|
+
function makeEvaluator(sf, fileScope) {
|
|
39
|
+
const raw = (node) => `<${node.getText(sf).replace(/\s+/g, " ")}>`;
|
|
40
|
+
|
|
41
|
+
function evalFn(fn, scope) {
|
|
42
|
+
if (!ts.isBlock(fn.body)) return evalNode(fn.body, scope);
|
|
43
|
+
const local = new Map(scope);
|
|
44
|
+
let result = UNRESOLVED;
|
|
45
|
+
for (const st of fn.body.statements) {
|
|
46
|
+
if (ts.isVariableStatement(st)) {
|
|
47
|
+
for (const d of st.declarationList.declarations) {
|
|
48
|
+
if (ts.isIdentifier(d.name) && d.initializer) local.set(d.name.text, evalNode(d.initializer, local));
|
|
49
|
+
}
|
|
50
|
+
} else if (ts.isReturnStatement(st) && st.expression) {
|
|
51
|
+
result = evalNode(st.expression, local);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result === UNRESOLVED ? raw(fn) : result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function evalNode(node, scope) {
|
|
58
|
+
if (!node) return null;
|
|
59
|
+
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isAwaitExpression(node)) {
|
|
60
|
+
return evalNode(node.expression, scope);
|
|
61
|
+
}
|
|
62
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
63
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
64
|
+
if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
|
|
65
|
+
return -Number(node.operand.text);
|
|
66
|
+
}
|
|
67
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
68
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
69
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
70
|
+
if (ts.isIdentifier(node)) {
|
|
71
|
+
if (node.text === "undefined") return null;
|
|
72
|
+
if (scope.has(node.text)) return scope.get(node.text);
|
|
73
|
+
const decl = fileScope.get(node.text);
|
|
74
|
+
return decl ? evalNode(decl, scope) : raw(node);
|
|
75
|
+
}
|
|
76
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
77
|
+
return node.elements.flatMap((el) => {
|
|
78
|
+
if (!ts.isSpreadElement(el)) return [evalNode(el, scope)];
|
|
79
|
+
const v = evalNode(el.expression, scope);
|
|
80
|
+
return Array.isArray(v) ? v : [raw(el)];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const p of node.properties) {
|
|
86
|
+
if (ts.isSpreadAssignment(p)) {
|
|
87
|
+
const v = evalNode(p.expression, scope);
|
|
88
|
+
if (v && typeof v === "object" && !Array.isArray(v)) Object.assign(out, v);
|
|
89
|
+
} else if (ts.isPropertyAssignment(p)) {
|
|
90
|
+
out[propName(p.name, sf)] = evalNode(p.initializer, scope);
|
|
91
|
+
} else if (ts.isShorthandPropertyAssignment(p)) {
|
|
92
|
+
out[p.name.text] = evalNode(p.name, scope);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
98
|
+
const obj = evalNode(node.expression, scope);
|
|
99
|
+
return obj && typeof obj === "object" && Object.hasOwn(obj, node.name.text) ? obj[node.name.text] : raw(node);
|
|
100
|
+
}
|
|
101
|
+
if (ts.isTemplateExpression(node)) {
|
|
102
|
+
let s = node.head.text;
|
|
103
|
+
for (const span of node.templateSpans) {
|
|
104
|
+
const v = evalNode(span.expression, scope);
|
|
105
|
+
if (typeof v === "string" && v.startsWith("<")) return raw(node);
|
|
106
|
+
s += String(v) + span.literal.text;
|
|
107
|
+
}
|
|
108
|
+
return s;
|
|
109
|
+
}
|
|
110
|
+
// (() => ({...}))() — an IIFE building the default data.
|
|
111
|
+
if (ts.isCallExpression(node) && node.arguments.length === 0) {
|
|
112
|
+
const callee = ts.isParenthesizedExpression(node.expression) ? node.expression.expression : node.expression;
|
|
113
|
+
if (ts.isArrowFunction(callee) || ts.isFunctionExpression(callee)) return evalFn(callee, scope);
|
|
114
|
+
}
|
|
115
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) return evalFn(node, scope);
|
|
116
|
+
return raw(node);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return evalNode;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function extractProgramCommands(srcPath = REPO_PROGRAM_SRC) {
|
|
123
|
+
const sf = ts.createSourceFile(srcPath, readFileSync(srcPath, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
124
|
+
const fileScope = new Map();
|
|
125
|
+
for (const st of sf.statements) {
|
|
126
|
+
if (!ts.isVariableStatement(st)) continue;
|
|
127
|
+
for (const d of st.declarationList.declarations) {
|
|
128
|
+
if (ts.isIdentifier(d.name) && d.initializer) fileScope.set(d.name.text, d.initializer);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const evalNode = makeEvaluator(sf, fileScope);
|
|
132
|
+
|
|
133
|
+
const toCommand = (node, category) => {
|
|
134
|
+
// A list element is an object literal, a const name, or { ...CONST, data: {...} }.
|
|
135
|
+
const props = new Map();
|
|
136
|
+
const collect = (n) => {
|
|
137
|
+
if (ts.isIdentifier(n) && fileScope.has(n.text)) return collect(fileScope.get(n.text));
|
|
138
|
+
if (!ts.isObjectLiteralExpression(n)) return;
|
|
139
|
+
for (const p of n.properties) {
|
|
140
|
+
if (ts.isSpreadAssignment(p)) collect(p.expression);
|
|
141
|
+
else if (ts.isPropertyAssignment(p)) props.set(propName(p.name, sf), p.initializer);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
collect(node);
|
|
145
|
+
if (!props.has("command")) return null;
|
|
146
|
+
|
|
147
|
+
const value = (key) => (props.has(key) ? evalNode(props.get(key), new Map()) : undefined);
|
|
148
|
+
const factory = props.get("factoryDataFn");
|
|
149
|
+
const data = value("data") ?? (factory ? evalNode(factory, new Map()) : undefined);
|
|
150
|
+
const rules = Object.fromEntries(RULE_KEYS.map((k) => [k, value(k)]).filter(([, v]) => v !== undefined));
|
|
151
|
+
return {
|
|
152
|
+
command: value("command"),
|
|
153
|
+
category,
|
|
154
|
+
label: value("label"),
|
|
155
|
+
method: data?.method ?? value("command"),
|
|
156
|
+
funcName: data?.funcName ?? null,
|
|
157
|
+
postFuncName: data?.postFuncName ?? null,
|
|
158
|
+
defaultArgs: data?.args ?? null,
|
|
159
|
+
defaultData: data ?? null,
|
|
160
|
+
rules,
|
|
161
|
+
// factoryDataFn: defaults are computed when the step is added (group/task ids, live robot pose, tool list, ...).
|
|
162
|
+
computedDefaults: !!factory,
|
|
163
|
+
// confirmModal: the Program page asks the user something (type, count, program) before adding it.
|
|
164
|
+
asksOnInsert: props.has("confirmModal"),
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const out = [];
|
|
169
|
+
const seen = new Set();
|
|
170
|
+
for (const [name, category] of Object.entries({ ...LISTS, ...EXTRA })) {
|
|
171
|
+
const init = fileScope.get(name);
|
|
172
|
+
if (!init) continue;
|
|
173
|
+
const nodes = ts.isArrayLiteralExpression(init) ? init.elements : [init];
|
|
174
|
+
for (const n of nodes) {
|
|
175
|
+
const cmd = toCommand(n, category);
|
|
176
|
+
if (cmd && !seen.has(cmd.command)) {
|
|
177
|
+
seen.add(cmd.command);
|
|
178
|
+
out.push(cmd);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function loadProgramCommands() {
|
|
186
|
+
if (existsSync(REPO_PROGRAM_SRC)) return extractProgramCommands();
|
|
187
|
+
if (existsSync(SHIPPED_PROGRAM_COMMANDS)) return JSON.parse(readFileSync(SHIPPED_PROGRAM_COMMANDS, "utf8"));
|
|
188
|
+
throw new Error(`Program command list not found (${SHIPPED_PROGRAM_COMMANDS}) — reinstall muscat-plugin-kit.`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// prepack: stage the list so it ships with the kit. Outside the monorepo the
|
|
192
|
+
// already-staged file is kept.
|
|
193
|
+
export function stageProgramCommands() {
|
|
194
|
+
if (!existsSync(REPO_PROGRAM_SRC)) return;
|
|
195
|
+
writeFileSync(SHIPPED_PROGRAM_COMMANDS, JSON.stringify(extractProgramCommands(), null, 2) + "\n");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const DEFAULT_COMMON_URL = "http://localhost:3000";
|
|
199
|
+
|
|
200
|
+
export const PROGRAM_API_GUIDE = `# Building a Muscat program from a plugin
|
|
201
|
+
|
|
202
|
+
Commands: list_program_commands gives every built-in command (name, default args, funcName,
|
|
203
|
+
nesting rules). A step is one command instance; nesting goes through children / parentStepId.
|
|
204
|
+
|
|
205
|
+
## A. From plugin UI: rb.program (edits the program open on the Program page)
|
|
206
|
+
|
|
207
|
+
\`\`\`ts
|
|
208
|
+
const rb = useRb();
|
|
209
|
+
const catalog = await rb.program.getCatalog(scope); // commands of the loaded robot, incl. other plugins' commands
|
|
210
|
+
const steps = await rb.program.getSteps(scope); // current main step tree
|
|
211
|
+
await rb.program.setSteps([ // replaces the main tree (unsaved — the user presses Save)
|
|
212
|
+
{ command: "MoveJ", children: [{ command: "Point", args: { tar_values: [0, 0, 90, 0, 90, 0, 0] } }] },
|
|
213
|
+
{ command: "Wait", args: { second: 1 } },
|
|
214
|
+
{ command: "Repeat", args: { count: 3 }, children: [{ command: "Wait", args: { second: 0.5 } }] },
|
|
215
|
+
{ command: "SubTask", fields: { subTaskType: "INSERT", subTaskScriptPath: "..." } },
|
|
216
|
+
], scope);
|
|
217
|
+
const pointDefaults = await rb.program.getCommandDefaults?.("Point", scope); // computedDefaults commands (ids, live robot state)
|
|
218
|
+
\`\`\`
|
|
219
|
+
|
|
220
|
+
- Step shape: { command, args?, summary?, fields?, children? }. The host fills the rest from the command
|
|
221
|
+
defaults: args = default args merged with yours, robot_model filled with the loaded robot, name = the
|
|
222
|
+
command label, funcName/postFuncName, nesting rules, fresh stepIds.
|
|
223
|
+
- fields only accepts subTaskType, subTaskScriptPath, name, memo, funcName, postFuncName.
|
|
224
|
+
- scope = { taskId, robotModel }: a program.monitor plugin gets it as rb.data; without it the first main task is used.
|
|
225
|
+
- Unknown command names throw ("unknown command: X").
|
|
226
|
+
|
|
227
|
+
## B. From a backend or external tool: common REST API
|
|
228
|
+
|
|
229
|
+
Base: http://<robot>:3000/common (robot backend: http://127.0.0.1:3000, muscat-pk dev backend container on
|
|
230
|
+
macOS/Windows: http://host.docker.internal:3000). The same API is reachable over TCP/MQTT/Socket.IO/WebRTC
|
|
231
|
+
with topic common/<path> (e.g. common/program/steps/upsert) and the request body as data.
|
|
232
|
+
|
|
233
|
+
1. Create: POST /program/create { name, repeatCnt } → { program, mainTasks[] } — take program.programId and the
|
|
234
|
+
main task's taskId (one main task per robot component, see mainTasks[].robotModel).
|
|
235
|
+
2. Steps: POST /program/steps/upsert { steps: [...] } with a FLAT list. Each step needs
|
|
236
|
+
stepId (unique string), taskId, programId, name, method, order. Also send what the command defines:
|
|
237
|
+
funcName / postFuncName / args, and its rules (allowChildren, requireChildren, groupId, ...).
|
|
238
|
+
- name is required (422 without it). The Program page stores the command label, e.g. "program.button.wait"
|
|
239
|
+
(label in list_program_commands), not the display text.
|
|
240
|
+
- method = the command name ("MoveJ", "Point", "Wait", ...).
|
|
241
|
+
- Nesting: a child's parentStepId = parent's stepId. order = pre-order (DFS) index over the whole tree,
|
|
242
|
+
starting at 0 — the parent before its children.
|
|
243
|
+
- Args like "$parent.move_type" are resolved from the parent step at run time; keep them as given.
|
|
244
|
+
- If/ElseIf/Else: sibling Condition steps sharing one groupId, args.condition_type "If" | "ElseIf" | "Else",
|
|
245
|
+
names "program.button.condition" | "program.button.elif" | "program.button.else". Switch and its Case
|
|
246
|
+
children share one groupId too.
|
|
247
|
+
- robot_model "" in default args → set it to the task's robotModel.
|
|
248
|
+
3. Read back: GET /program/main-tasks/{programId}, GET /program/steps/{taskId}, GET /program/step/{stepId}.
|
|
249
|
+
4. Delete steps: DELETE /program/steps/delete. Edit / clone / delete program: PUT /program/edit,
|
|
250
|
+
POST /program/clone, DELETE /program/delete/{programId}. List: GET /programs.
|
|
251
|
+
5. Run (moves the robot): POST /program/load { programId } then POST /program/start { programId };
|
|
252
|
+
/program/pause | resume | stop with the same body. State: GET /program/current/state,
|
|
253
|
+
GET /program/task/{taskId}/step-state, GET /program/executor/state, variables: GET /program/{robot_model}/variables.
|
|
254
|
+
6. Export / import as a script: GET /program/export/{programId or name} (.zip, or .py with taskId),
|
|
255
|
+
POST /program/import/preview, POST /program/import.
|
|
256
|
+
|
|
257
|
+
Exact request/response fields come from the live OpenAPI: call get_program_api with path, e.g. "/program/steps/upsert".
|
|
258
|
+
|
|
259
|
+
## Manipulator Point notes
|
|
260
|
+
|
|
261
|
+
- tar_values always has 7 values. input_method 0 = absolute target. Joint targets (tar_frame -1) go under MoveJ/MoveJB,
|
|
262
|
+
Cartesian (tar_frame 0) under MoveL/MoveLB. PinPoint (Cartesian pin) under MoveL, PinJoint under MoveJ.
|
|
263
|
+
- Relative move: input_method 1 + a reference point (pin name or RB_POINT_REF) + 7 offsets.
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
function compactSchema(schema, spec, depth = 0) {
|
|
267
|
+
if (!schema) return "any";
|
|
268
|
+
if (schema.$ref) {
|
|
269
|
+
const name = schema.$ref.split("/").pop();
|
|
270
|
+
if (depth > 3) return name;
|
|
271
|
+
return compactSchema(spec.components?.schemas?.[name], spec, depth + 1);
|
|
272
|
+
}
|
|
273
|
+
const alt = schema.anyOf ?? schema.oneOf;
|
|
274
|
+
if (alt) return alt.map((s) => compactSchema(s, spec, depth)).map((s) => (typeof s === "string" ? s : JSON.stringify(s))).join(" | ");
|
|
275
|
+
if (schema.enum) return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
276
|
+
if (schema.type === "array") return [compactSchema(schema.items, spec, depth)];
|
|
277
|
+
if (schema.type === "object" || schema.properties) {
|
|
278
|
+
if (!schema.properties) return "object";
|
|
279
|
+
const req = new Set(schema.required ?? []);
|
|
280
|
+
return Object.fromEntries(
|
|
281
|
+
Object.entries(schema.properties).map(([k, v]) => [req.has(k) ? k : `${k}?`, compactSchema(v, spec, depth)]),
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return schema.type ?? "any";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Live endpoint list / schema from the common service's OpenAPI.
|
|
288
|
+
export async function describeProgramApi(path, baseUrl = DEFAULT_COMMON_URL) {
|
|
289
|
+
const url = `${baseUrl.replace(/\/$/, "")}/common/openapi.json`;
|
|
290
|
+
let spec;
|
|
291
|
+
try {
|
|
292
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
|
293
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
294
|
+
spec = await res.json();
|
|
295
|
+
} catch (e) {
|
|
296
|
+
return `Could not read ${url} (${e.message}). Start Muscat (muscat-pk dev) or pass url with the robot's address, e.g. "http://192.168.0.10:3000".`;
|
|
297
|
+
}
|
|
298
|
+
const paths = spec.paths ?? {};
|
|
299
|
+
if (!path) {
|
|
300
|
+
return Object.entries(paths)
|
|
301
|
+
.filter(([p]) => p.startsWith("/program"))
|
|
302
|
+
.flatMap(([p, ops]) => Object.entries(ops).map(([m, op]) => `${m.toUpperCase()} /common${p} — ${(op.summary ?? "").trim()}`))
|
|
303
|
+
.join("\n");
|
|
304
|
+
}
|
|
305
|
+
const key = path.replace(/^\/common/, "");
|
|
306
|
+
const ops = paths[key];
|
|
307
|
+
if (!ops) {
|
|
308
|
+
const near = Object.keys(paths).filter((p) => p.includes(key.split("/").filter(Boolean).pop() ?? ""));
|
|
309
|
+
return `No endpoint "${path}".${near.length ? ` Similar: ${near.slice(0, 10).join(", ")}` : ""}`;
|
|
310
|
+
}
|
|
311
|
+
return Object.entries(ops)
|
|
312
|
+
.map(([m, op]) => {
|
|
313
|
+
const body = op.requestBody?.content?.["application/json"]?.schema;
|
|
314
|
+
const params = (op.parameters ?? []).map((p) => `${p.name} (${p.in}${p.required ? ", required" : ""}): ${JSON.stringify(compactSchema(p.schema, spec))}`);
|
|
315
|
+
const ok = op.responses?.["200"]?.content?.["application/json"]?.schema;
|
|
316
|
+
return [
|
|
317
|
+
`${m.toUpperCase()} /common${key} — ${(op.summary ?? "").trim()}`,
|
|
318
|
+
op.description ? op.description.trim() : null,
|
|
319
|
+
params.length ? `Parameters:\n ${params.join("\n ")}` : null,
|
|
320
|
+
body ? `Request body (? = optional):\n${JSON.stringify(compactSchema(body, spec), null, 2)}` : null,
|
|
321
|
+
ok ? `Response 200:\n${JSON.stringify(compactSchema(ok, spec), null, 2)}` : null,
|
|
322
|
+
]
|
|
323
|
+
.filter(Boolean)
|
|
324
|
+
.join("\n\n");
|
|
325
|
+
})
|
|
326
|
+
.join("\n\n---\n\n");
|
|
327
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// What a project must change for the plugin-ui it has installed: known old APIs (ai-rules.mjs) plus
|
|
2
|
+
// `tsc --noEmit`. Shared by `muscat-pk update` and the MCP tool check_project, so a user and an
|
|
3
|
+
// agent see the same list.
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join, relative, resolve } from "node:path";
|
|
7
|
+
import { LEGACY_COMPONENTS, LEGACY_SUBPATHS } from "./ai-rules.mjs";
|
|
8
|
+
|
|
9
|
+
const isBinaryMode = typeof Bun !== "undefined";
|
|
10
|
+
|
|
11
|
+
// The clause can't hold quotes or `;`, so a match can't start at an earlier import statement.
|
|
12
|
+
const PLUGIN_UI_IMPORT = /import\s+([^;'"]*?)\s+from\s+["']@rainbow-robotics\/plugin-ui\/([\w-]+)["']/g;
|
|
13
|
+
|
|
14
|
+
function listUiFiles(dir) {
|
|
15
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
16
|
+
e.isDirectory() ? listUiFiles(join(dir, e.name)) : /\.[jt]sx?$/.test(e.name) ? [join(dir, e.name)] : [],
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// rb.toast takes one { message, type } object now; flag a string or multi-argument call.
|
|
21
|
+
const TOAST_CALL = /\.toast\(([^()]*)\)/g;
|
|
22
|
+
|
|
23
|
+
/** Uses of the pre-1.x plugin-ui API in the project (ui/ and vite.config), as { file, line, text }. */
|
|
24
|
+
function findLegacyUiUsage(root) {
|
|
25
|
+
const uiDir = resolve(root, "ui");
|
|
26
|
+
if (!existsSync(uiDir)) return [];
|
|
27
|
+
const found = [];
|
|
28
|
+
// The old hand-written config builds entries without their exports (connect() rendered as a side
|
|
29
|
+
// effect), so `export default App` is dropped from dist. The muscat() preset keeps it.
|
|
30
|
+
const viteConfig = ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"].find((f) => existsSync(resolve(root, f)));
|
|
31
|
+
if (viteConfig && !readFileSync(resolve(root, viteConfig), "utf8").includes("@rainbow-robotics/muscat-plugin-kit/vite")) {
|
|
32
|
+
found.push({
|
|
33
|
+
file: viteConfig,
|
|
34
|
+
line: 1,
|
|
35
|
+
text: 'not using the muscat() preset — the build drops the entry\'s default export. Use: import { muscat } from "@rainbow-robotics/muscat-plugin-kit/vite"; defineConfig({ plugins: [muscat()] })',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
for (const file of listUiFiles(uiDir)) {
|
|
39
|
+
const src = readFileSync(file, "utf8");
|
|
40
|
+
const rel = relative(root, file);
|
|
41
|
+
const lineOf = (index) => src.slice(0, index).split("\n").length;
|
|
42
|
+
for (const m of src.matchAll(PLUGIN_UI_IMPORT)) {
|
|
43
|
+
const [, clause, subpath] = m;
|
|
44
|
+
if (LEGACY_SUBPATHS[subpath]) {
|
|
45
|
+
found.push({ file: rel, line: lineOf(m.index), text: `@rainbow-robotics/plugin-ui/${subpath} ${LEGACY_SUBPATHS[subpath]}` });
|
|
46
|
+
} else if (subpath === "react") {
|
|
47
|
+
const named = clause.match(/\{([\s\S]*)\}/)?.[1] ?? "";
|
|
48
|
+
for (const spec of named.split(",")) {
|
|
49
|
+
const name = spec.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0];
|
|
50
|
+
if (LEGACY_COMPONENTS[name]) {
|
|
51
|
+
found.push({ file: rel, line: lineOf(m.index), text: `${name} → ${LEGACY_COMPONENTS[name]}` });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const m of src.matchAll(TOAST_CALL)) {
|
|
57
|
+
const arg = m[1].trim();
|
|
58
|
+
if (!arg.startsWith("{") && (/^["'`]/.test(arg) || arg.includes(","))) {
|
|
59
|
+
found.push({ file: rel, line: lineOf(m.index), text: "rb.toast(message, type) → rb.toast({ message, type })" });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Top-level ui/ files are the entries; the host mounts their default export.
|
|
63
|
+
if (relative(uiDir, file).split(/[\\/]/).length === 1 && !/export\s+default\b/.test(src)) {
|
|
64
|
+
found.push({ file: rel, line: 1, text: "no default export — the host renders the entry's default export" });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The known-API list above only knows past changes. Type-checking against the plugin-ui that was
|
|
71
|
+
// just installed catches whatever a later release removes or reshapes, with no list to keep up.
|
|
72
|
+
function typeScriptErrors(root) {
|
|
73
|
+
const tsc = resolve(root, "node_modules/typescript/bin/tsc");
|
|
74
|
+
if (!existsSync(tsc) || !existsSync(resolve(root, "tsconfig.json"))) return [];
|
|
75
|
+
const runtime = isBinaryMode ? "node" : process.execPath;
|
|
76
|
+
const r = spawnSync(runtime, [tsc, "--noEmit", "-p", root, "--pretty", "false"], { cwd: root, encoding: "utf8" });
|
|
77
|
+
return `${r.stdout ?? ""}`
|
|
78
|
+
.split("\n")
|
|
79
|
+
.map((l) => /^(.+?)\((\d+),\d+\): error (TS\d+): (.*)$/.exec(l))
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.map(([, file, line, code, text]) => ({ file: relative(root, resolve(root, file)), line: Number(line), text: `${code} ${text}` }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Everything the project must change, as { file, line, text, source: "known" | "tsc" }. */
|
|
85
|
+
export function checkProject(root) {
|
|
86
|
+
return [
|
|
87
|
+
...findLegacyUiUsage(root).map((f) => ({ ...f, source: "known" })),
|
|
88
|
+
...typeScriptErrors(root).map((e) => ({ ...e, source: "tsc" })),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Plain-text lines for a checkProject() result (empty when there is nothing to fix). */
|
|
93
|
+
export function formatProjectCheck(items) {
|
|
94
|
+
if (!items.length) return [];
|
|
95
|
+
const line = (i) => ` ${i.file}:${i.line} ${i.text}`;
|
|
96
|
+
const tsc = items.filter((i) => i.source === "tsc");
|
|
97
|
+
return [
|
|
98
|
+
...items.filter((i) => i.source !== "tsc").map(line),
|
|
99
|
+
...(tsc.length ? [" TypeScript (tsc --noEmit):", ...tsc.map(line)] : []),
|
|
100
|
+
" For working examples, generate a fresh project and read its ui/content.tsx: muscat-pk new <dir>",
|
|
101
|
+
];
|
|
102
|
+
}
|