@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
package/README.md
CHANGED
|
@@ -1,106 +1,1188 @@
|
|
|
1
1
|
# @rainbow-robotics/muscat-plugin-kit
|
|
2
2
|
|
|
3
|
-
Muscat
|
|
3
|
+
CLI for developing Muscat plugins (`muscat-pk`): scaffolding, dev servers (UI + backend), build/pack, and the SDK references a plugin author needs.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[한국어 문서 (README.ko.md)](./README.ko.md)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Contents
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
1. [Install](#1-install)
|
|
10
|
+
2. [Quick start](#2-quick-start)
|
|
11
|
+
3. [Commands](#3-commands)
|
|
12
|
+
4. [Project layout](#4-project-layout)
|
|
13
|
+
5. [manifest.json](#5-manifestjson)
|
|
14
|
+
6. [Plugin UI SDK (`useRb`)](#6-plugin-ui-sdk-userb)
|
|
15
|
+
7. [Python plugin SDK](#7-python-plugin-sdk)
|
|
16
|
+
8. [Other backend runtimes (C++ / ROS 2)](#8-other-backend-runtimes-c--ros-2)
|
|
17
|
+
9. [Development environment](#9-development-environment)
|
|
18
|
+
10. [Styles](#10-styles)
|
|
19
|
+
11. [AI coding agent setup (`muscat-pk mcp`)](#11-ai-coding-agent-setup-muscat-pk-mcp)
|
|
20
|
+
12. [Environment variables](#12-environment-variables)
|
|
21
|
+
13. [Troubleshooting](#13-troubleshooting)
|
|
22
|
+
|
|
23
|
+
---
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
npm install -g @rainbow-robotics/muscat-plugin-kit@dev
|
|
25
|
+
## 1. Install
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
### npm (Node.js 20+)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit # latest stable
|
|
31
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit@dev # dev channel
|
|
32
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit@1.2.3 # specific version
|
|
18
33
|
```
|
|
19
34
|
|
|
20
|
-
###
|
|
35
|
+
### Install script (no Node.js required)
|
|
21
36
|
|
|
22
37
|
macOS / Linux / WSL:
|
|
23
38
|
|
|
24
39
|
```bash
|
|
25
|
-
|
|
26
|
-
curl -fsSL https://
|
|
27
|
-
|
|
28
|
-
# dev 채널
|
|
29
|
-
MUSCAT_PK_TAG=dev curl -fsSL https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.sh | sh
|
|
30
|
-
|
|
31
|
-
# 특정 버전
|
|
32
|
-
MUSCAT_PK_TAG=1.2.3 curl -fsSL https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.sh | sh
|
|
40
|
+
curl -fsSL https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.sh | sh
|
|
41
|
+
curl -fsSL https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.sh | MUSCAT_PK_TAG=dev sh
|
|
42
|
+
curl -fsSL https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.sh | MUSCAT_PK_TAG=1.2.3 sh
|
|
33
43
|
```
|
|
34
44
|
|
|
35
45
|
Windows PowerShell:
|
|
36
46
|
|
|
37
47
|
```powershell
|
|
38
|
-
|
|
39
|
-
irm https://
|
|
40
|
-
|
|
41
|
-
# dev 채널
|
|
42
|
-
$env:MUSCAT_PK_TAG="dev"; irm https://raw.githubusercontent.com/rainbow-robotics/web-rainbow-ui/main/scripts/install-muscat-pk.ps1 | iex
|
|
48
|
+
irm https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.ps1 | iex
|
|
49
|
+
$env:MUSCAT_PK_TAG="dev"; irm https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/install.ps1 | iex
|
|
43
50
|
```
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
The script uses npm when Node.js 20+ is present, otherwise it downloads a self-contained binary.
|
|
46
53
|
|
|
47
|
-
###
|
|
54
|
+
### Direct binary download
|
|
48
55
|
|
|
49
56
|
```
|
|
50
|
-
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/v{
|
|
57
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/v{version}/{binary}
|
|
58
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/stable/{binary}
|
|
59
|
+
https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat-plugin-kit/dev/{binary}
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
| OS |
|
|
62
|
+
| OS | File |
|
|
54
63
|
|---|---|
|
|
55
64
|
| Linux x64 | `muscat-pk-linux-x64` |
|
|
56
65
|
| macOS ARM64 | `muscat-pk-darwin-arm64` |
|
|
66
|
+
| macOS x64 | `muscat-pk-darwin-x64` |
|
|
57
67
|
| Windows x64 | `muscat-pk-win-x64.exe` |
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
Channel pointers: `.../muscat-plugin-kit/latest-version.txt` (stable), `.../muscat-plugin-kit/dev-version.txt` (dev).
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
### Requirements
|
|
72
|
+
|
|
73
|
+
| Tool | Needed for |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Node.js 20+ (or the binary) | the CLI, UI build |
|
|
76
|
+
| Docker | every backend runtime except `none` (`dev:back`, `dev`, `build`) |
|
|
77
|
+
| `uv` or Python 3.12 (optional) | IDE-only `.venv` for Python linting |
|
|
78
|
+
|
|
79
|
+
### Uninstall
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
```bash
|
|
82
|
+
muscat-pk uninstall # works for npm and binary installs
|
|
83
|
+
npm uninstall -g @rainbow-robotics/muscat-plugin-kit # manual, npm
|
|
84
|
+
sudo rm -f /usr/local/bin/muscat-pk # manual, binary
|
|
67
85
|
```
|
|
68
86
|
|
|
69
87
|
---
|
|
70
88
|
|
|
71
|
-
##
|
|
89
|
+
## 2. Quick start
|
|
72
90
|
|
|
73
91
|
```bash
|
|
74
|
-
muscat-pk new my-plugin
|
|
92
|
+
muscat-pk new my-plugin # scaffold (asks author, email, description, type, package manager)
|
|
75
93
|
cd my-plugin
|
|
94
|
+
muscat-pk dev # UI preview + backend together
|
|
95
|
+
muscat-pk build # dist/ + distributable ZIP
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Install the ZIP from the robot's Muscat plugin page.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 3. Commands
|
|
103
|
+
|
|
104
|
+
| Command | Description |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `muscat-pk new [dir]` | Scaffold a plugin project |
|
|
107
|
+
| `muscat-pk type change` | Switch the backend type |
|
|
108
|
+
| `muscat-pk dev` | Local Muscat runtime + `dev:ui` + `dev:back` |
|
|
109
|
+
| `muscat-pk dev:ui` | UI preview in a mini-Muscat (preview-host) |
|
|
110
|
+
| `muscat-pk dev:back` | Backend only, in Docker, with hot reload |
|
|
111
|
+
| `muscat-pk build` | Build, lock Python deps, vendor wheels, ZIP |
|
|
112
|
+
| `muscat-pk update` | Update the kit, `plugin-ui` and `preview-host` |
|
|
113
|
+
| `muscat-pk sdk <list\|update [version]>` | Manage the `rainbow-rb-sdk` version |
|
|
114
|
+
| `muscat-pk sim list` | List the robot simulators for `dev --sim` |
|
|
115
|
+
| `muscat-pk storybook` | Browse the bundled rb-components Storybook |
|
|
116
|
+
| `muscat-pk mcp` | MCP server for AI coding agents |
|
|
117
|
+
| `muscat-pk uninstall` | Remove the CLI |
|
|
118
|
+
| `muscat-pk version` | Print the running version (`-v`, `--version`) |
|
|
119
|
+
| `muscat-pk completion [shell]` | Print a shell completion script |
|
|
120
|
+
|
|
121
|
+
`muscat-pk <command> --help` prints the options of a command.
|
|
122
|
+
|
|
123
|
+
### 3.1 `new`
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
muscat-pk new [dir] [--type python|cpp|ros2|ros2-cpp] [--pm npm|yarn|pnpm|bun]
|
|
127
|
+
[--author <name>] [--email <email>] [--description <text>]
|
|
128
|
+
[--yes] [--no-install]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Option | Meaning |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `dir` | target directory (must be empty); asked when omitted |
|
|
134
|
+
| `--type` | backend type: `python` (default), `cpp`, `ros2` (rclpy), `ros2-cpp` (rclcpp) |
|
|
135
|
+
| `--pm` | front-end package manager; defaults to `yarn` inside a Yarn workspace, else `npm` |
|
|
136
|
+
| `--author` / `--email` / `--description` | manifest fields (email is required by the manifest) |
|
|
137
|
+
| `--yes` | no prompts; unspecified values use defaults |
|
|
138
|
+
| `--no-install` | skip `<pm> install`, the IDE `.venv` and the C++ dev image |
|
|
139
|
+
| `--help` | print the usage |
|
|
140
|
+
|
|
141
|
+
The plugin id is `com.example.<dir>` (lowercased; edit it in `manifest.json`). `new` also writes `.gitignore`, `AGENTS.md`, `CLAUDE.md` and `.mcp.json` (see [11](#11-ai-coding-agent-setup-muscat-pk-mcp)), and runs `git init` with an initial commit (delete `.git` if you don't want it).
|
|
142
|
+
|
|
143
|
+
Unless `--no-install`:
|
|
144
|
+
|
|
145
|
+
- python / ros2 (rclpy): an IDE-only `.venv` (see [9.6](#96-ide-support-python)).
|
|
146
|
+
- cpp / ros2-cpp: builds the C++ dev image so the editor finds the SDK headers (skipped with a warning when Docker is missing).
|
|
147
|
+
|
|
148
|
+
### 3.2 `type change`
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
muscat-pk type change [--to python|cpp|ros2|ros2-cpp] [--readme]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Option | Meaning |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `--to` | new type; a numbered prompt when omitted |
|
|
157
|
+
| `--readme` | replace `README.md` with the new type's without asking (old file → `README.md.bak`) |
|
|
158
|
+
|
|
159
|
+
Rewrites `manifest.json` → `runtime` and adds the new backend's files (`cpp/` or `ros2_ws/`) unless that directory already exists. Files of the old type (`cpp/`, `ros2_ws/`, …) are left in place. When switching between a Python and a C++ backend it offers to replace the README (`--readme` answers yes). Switching to a C++ type prepares the C++ dev image like `new`.
|
|
160
|
+
|
|
161
|
+
### 3.3 `dev`, `dev:ui`, `dev:back`
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
muscat-pk dev [--robot-ip <ip>] [--robot-token <token>] [--keep-id] [--sim <model>]
|
|
165
|
+
[--port <n>] [--no-open]
|
|
166
|
+
muscat-pk dev:ui [--robot-ip <ip>] [--port <n>] [--keep-id] [--no-open]
|
|
167
|
+
muscat-pk dev:back [--robot-ip <ip>] [--robot-token <token>] [--keep-id]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| Command | What runs |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `dev` | builds the dev image and backend first (a failed build only warns), then a local Muscat runtime when no robot is given (or the robot runs no Muscat), then `dev:ui` and `dev:back` |
|
|
173
|
+
| `dev:ui` | the plugin's Vite dev server + preview-host at `http://localhost:4180` (next free port), opened in the browser |
|
|
174
|
+
| `dev:back` | the backend in a Docker container matching the robot. Python restarts on `py/**/*.py` changes (watchfiles) |
|
|
175
|
+
|
|
176
|
+
| Option | Meaning |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `--robot-ip` | robot to develop against; else `MUSCAT_ROBOT_HOST`, else a prompt (blank = no robot) |
|
|
179
|
+
| `--robot-token` | service token for a robot with the login policy on; else `MUSCAT_ROBOT_TOKEN`, else a prompt (blank = none) |
|
|
180
|
+
| `--keep-id` | run under the manifest id instead of a dev id (see [9.3](#93-several-developers-on-one-robot)) |
|
|
181
|
+
| `--sim` | `dev` only, no robot: start a robot simulator (below). Skips the robot IP prompt |
|
|
182
|
+
| `--port` | preview-host port (default `4180`; the next free one when taken) |
|
|
183
|
+
| `--no-open` | don't open the browser (it is not opened in CI or without a TTY either) |
|
|
184
|
+
|
|
185
|
+
- One dev environment per PC: they share the backend container, the local Muscat and its ports. A second `dev` / `dev:ui` / `dev:back` asks to stop the running one (`Stop it and start this one? [y/N]`).
|
|
186
|
+
- Against a remote robot the plugin runs under a dev id (see [9.3](#93-several-developers-on-one-robot)); `--keep-id` uses the manifest id.
|
|
187
|
+
- Before start, `dev` and `dev:back` check `manifest.json` → `variables` and `ports` (errors stop it); `dev:back` also prints other manifest errors as warnings. The full check runs on `build`. Undeclared listening ports stop the backend (see [5.6](#56-ports)).
|
|
188
|
+
- Against a robot running Muscat, the UI is that robot's Muscat UI and the backend uses its gateway. With a local `muscat-dev` container (or `--sim`) it is the local real Muscat UI; otherwise the mock preview.
|
|
189
|
+
- `dev` / `dev:ui` print a hint when a newer kit is published (`MUSCAT_PK_NO_UPDATE_CHECK=1` skips it).
|
|
190
|
+
- Stopping `dev` (Ctrl+C) removes the backend container, and its own local Muscat and simulators.
|
|
191
|
+
|
|
192
|
+
#### Developing without a robot (`--sim`)
|
|
193
|
+
|
|
194
|
+
With no robot IP, `dev --sim <model>` also starts a robot simulator next to the local Muscat. The plugin SDK's robot calls then reach a real coreSW.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
muscat-pk sim list # "<name>\t<what runs>" per line
|
|
198
|
+
muscat-pk dev --sim 500920
|
|
199
|
+
muscat-pk dev --sim rby1-m_v1.2
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
| `--sim` | Muscat services | Simulator |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `500920` | common, log, manipulate | one `rainbowroboticsofficial/cobot-sim` |
|
|
205
|
+
| `RBY2` | common, log, manipulate | three cobot-sim (C500880, C501880, C502880) |
|
|
206
|
+
| `rby1-a`, `rby1-m`, `rby1-ub` (or a version, e.g. `rby1-m_v1.2`) | common, log, rby1 | `rainbowroboticsofficial/rby1-sim` + Xvfb, built locally |
|
|
207
|
+
|
|
208
|
+
- The simulated model is registered as the local Muscat's robot model (`500920` → C500920, `rby1-m` → m). The UI is then the real Muscat UI (dashboard, components, Move, Log, Settings) instead of the mock preview.
|
|
209
|
+
- A simulator runs only with `--sim`. Any other value fails with `--sim takes one of: 500920, RBY2, rby1-a, rby1-m, rby1-ub (rby1 also takes rby1-m_v1.2)`. A model missing from the manifest's `targetModels` only gets a warning.
|
|
210
|
+
- Simulator images use the latest Docker Hub tag (an rby1 version picks the matching tag). Offline, an already pulled image is used.
|
|
211
|
+
- rby1-sim keeps rendering, so it uses about one CPU core. The cap is `MUSCAT_PK_RBY1_CPUS` (default 1.5).
|
|
212
|
+
- No simulator is attached while another Muscat such as `muscat-dev` is running.
|
|
213
|
+
- There is no amr simulator yet.
|
|
214
|
+
- The local Muscat, the simulators and the backend all run with `--ipc host` like on the robot, so zenoh SHM and `shm_open` reach each other.
|
|
215
|
+
|
|
216
|
+
### 3.4 `build`
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
muscat-pk build [--arch amd64|arm64|both]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Steps:
|
|
223
|
+
|
|
224
|
+
1. Asks for a new `manifest.json` version (blank keeps the current one; TTY only).
|
|
225
|
+
2. Asks for the target arch (unless `--arch`) and deletes an existing ZIP of that version, so a failed build leaves no stale ZIP.
|
|
226
|
+
3. Validates the manifest (all of section 5) and checks that each `contributes[].ui` has `ui/<entry>.tsx` (or `.ts` / `.jsx` / `.js`).
|
|
227
|
+
4. `vite build` of every `contributes[].ui` entry → `dist/<entry>.js`. A plugin without `contributes` builds no UI. Without Node.js 20+ (binary CLI) it asks `Build the plugin without its UI? [y/N]`; yes drops `contributes` from `dist/manifest.json`.
|
|
228
|
+
5. Copies `manifest.json`, `assets/`, `README.md`, `README.<lang>.md`, `CHANGELOG.md`, `py/` and `requirements.txt` into `dist/`.
|
|
229
|
+
6. Backend:
|
|
230
|
+
- python / ros2 (rclpy): resolves `requirements.txt` into `dist/requirements.lock` (`uv pip compile`, Python 3.12; pre-releases allowed when a pinned line is one) and downloads binary wheels (cp312, `--only-binary=:all:`) into `dist/wheels/` per arch. A package with no matching wheel stops the build.
|
|
231
|
+
- cpp: CMake Release build in Docker → `dist/<arch>/<entry>` plus `dist/<arch>/lib/` (`librb_sdk.so`). The CMake target name must equal `runtime.entry`.
|
|
232
|
+
- ros2: colcon build in Docker → `dist/ros2_ws/install/` (rclpy, built once) or `dist/ros2_ws/install-<arch>/` (rclcpp, per arch).
|
|
233
|
+
7. Writes `installedSize` (bytes of `dist/` without `wheels/`) into `manifest.json` and `dist/manifest.json`.
|
|
234
|
+
8. Packs `dist/` into `<project dir>-<version>.zip` in the project root.
|
|
235
|
+
|
|
236
|
+
| Option | Meaning |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `--arch` | `amd64` (`x86_64`), `arm64` (`aarch64`) or `both`; asked in a TTY (default both), `both` otherwise |
|
|
239
|
+
|
|
240
|
+
Every runtime except `none` needs Docker. Building the other arch needs Docker emulation; on native Linux enable it once with `docker run --privileged --rm tonistiigi/binfmt --install <arch>`.
|
|
241
|
+
|
|
242
|
+
### 3.5 `update`
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
muscat-pk update # this project only (when it has a local install)
|
|
246
|
+
muscat-pk update --global # this project + the global CLI
|
|
247
|
+
muscat-pk update dev # follow the dev channel
|
|
248
|
+
muscat-pk update 1.2.3 # a specific version (also --tag <tag> / --version <v>)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| Option | Meaning |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `[tag]` | npm dist-tag or version (`latest`, `dev`, `1.2.3`). Omitted: `latest`; on a dev build it asks to stay on dev (default) or switch to stable |
|
|
254
|
+
| `--global` | also update the global CLI when run from a project's local install |
|
|
255
|
+
|
|
256
|
+
| Where you run it | What it updates |
|
|
257
|
+
|---|---|
|
|
258
|
+
| Inside a project with a local install | that project's `muscat-plugin-kit` + `plugin-ui` (+ `preview-host` when listed in `package.json`) |
|
|
259
|
+
| Same, with `--global` | the above + the global CLI |
|
|
260
|
+
| Anywhere else | the global CLI (npm `-g` or the S3 binary) and local deps if inside a project |
|
|
261
|
+
|
|
262
|
+
- When the project's local install is older than the CLI you run, that CLI updates the project instead (project only).
|
|
263
|
+
- Packages stay in their current `dependencies` / `devDependencies` section; the package manager is taken from `packageManager` or the lock file.
|
|
264
|
+
- An old project layout is migrated after a `[y/N]` prompt: `src/` → `ui/` (+ `vite.config.ts`, `tsconfig.json`), `main.py` → `py/main.py`, `backend/` → `py/`.
|
|
265
|
+
- Missing `.mcp.json`, `AGENTS.md`, `CLAUDE.md` are added; existing ones are kept.
|
|
266
|
+
- On Windows the new binary replaces the old one on the next run.
|
|
267
|
+
|
|
268
|
+
After updating it lists code that needs changes for the new `plugin-ui` (``This project needs changes for the updated plugin-ui. Fix these before `muscat-pk dev` / `build`:``). Fix them (or run `check_project` through `muscat-pk mcp`).
|
|
269
|
+
|
|
270
|
+
### 3.6 `sdk`
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
muscat-pk sdk list # 12 newest published versions
|
|
274
|
+
muscat-pk sdk update [version] # latest stable when omitted
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
| Backend | `list` source | `update` rewrites |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| python, ros2 (rclpy) | PyPI `rainbow-rb-sdk` | every `rainbow-rb-*==` pin in `requirements.txt` together (they pin each other exactly); adds `rainbow-rb-sdk==<ver>` when missing. An existing `.venv` is refreshed; the dev container installs it on the next `dev` / `dev:back` |
|
|
280
|
+
| C++ (`cpp`, `ros2-cpp`) | apt `rainbow-rb-sdk-dev` (stable, numeric versions only) | `find_package(rb_sdk <ver> EXACT REQUIRED)` in `cpp/CMakeLists.txt` / `ros2_ws/src/*/CMakeLists.txt`; the dev image is rebuilt with it on the next `build` / `dev:back` |
|
|
281
|
+
|
|
282
|
+
A C++ version must be digits and dots (`"<v>" is not a CMake version (digits and dots only)`).
|
|
283
|
+
|
|
284
|
+
### 3.7 `storybook`, `mcp`, `completion`, `version`
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
muscat-pk storybook [--port 6007] [--rebuild]
|
|
288
|
+
muscat-pk mcp [--install codex]
|
|
289
|
+
muscat-pk completion [bash|zsh|fish|powershell]
|
|
290
|
+
muscat-pk version
|
|
291
|
+
muscat-pk uninstall
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
| Command / option | Meaning |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `storybook --port` | port to serve on (default `6007`); a taken port fails with `port <n> is in use — pass a free one with --port <n>.` |
|
|
297
|
+
| `storybook --rebuild` | rebuild the Storybook first (only from a repo checkout; the published kit ships it prebuilt) |
|
|
298
|
+
| `mcp` | stdio MCP server (section 11). It relays to the real server (`mcp --serve`) and restarts it when the kit is updated, so a running agent session picks up the new tools |
|
|
299
|
+
| `mcp --install codex` | register the server in `~/.codex/config.toml` (no-op when already there) |
|
|
300
|
+
| `version` | the version of the copy that actually ran (also `-v`, `--version`) |
|
|
301
|
+
| `uninstall` | npm global: `npm uninstall -g`; binary: deletes the executable. A project-local install is removed with the project's package manager instead |
|
|
302
|
+
|
|
303
|
+
`completion` prints a Tab-completion script for your shell — once registered, `muscat-pk d<Tab>` offers `dev`, `dev:ui`, `dev:back`. It only prints the script; append it to your shell's rc file as below. `[shell]` is `bash`, `zsh`, `fish` or `powershell`, defaulting to `$SHELL` (else `powershell` on Windows, `bash` elsewhere). Windows `cmd.exe` has no completion hook — use PowerShell, Git Bash or WSL. Optional convenience; nothing depends on it.
|
|
304
|
+
|
|
305
|
+
| OS / shell | Completion setup |
|
|
306
|
+
|---|---|
|
|
307
|
+
| macOS (zsh) | `muscat-pk completion zsh >> ~/.zshrc && source ~/.zshrc` |
|
|
308
|
+
| Linux (bash) | `muscat-pk completion bash >> ~/.bashrc && source ~/.bashrc` |
|
|
309
|
+
| fish | `muscat-pk completion fish > ~/.config/fish/completions/muscat-pk.fish` |
|
|
310
|
+
| PowerShell | `muscat-pk completion powershell >> $PROFILE; . $PROFILE` |
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 4. Project layout
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
my-plugin/
|
|
318
|
+
├─ ui/
|
|
319
|
+
│ ├─ content.tsx # UI entry (one file per contributes[].ui), `export default App`
|
|
320
|
+
│ └─ style.css # auto-loaded for every entry
|
|
321
|
+
├─ py/main.py # python backend entry (runtime.entry)
|
|
322
|
+
├─ cpp/ # cpp backend (CMake) — cpp type
|
|
323
|
+
├─ ros2_ws/ # colcon workspace — ros2 / ros2-cpp type
|
|
324
|
+
├─ assets/icon.svg
|
|
325
|
+
├─ manifest.json
|
|
326
|
+
├─ requirements.txt # python deps (locked into dist/requirements.lock on build)
|
|
327
|
+
├─ package.json # UI deps (not shipped)
|
|
328
|
+
├─ vite.config.ts # must use the muscat() preset
|
|
329
|
+
├─ tsconfig.json
|
|
330
|
+
├─ AGENTS.md, CLAUDE.md # rules for AI coding agents (CLAUDE.md → @AGENTS.md)
|
|
331
|
+
├─ .mcp.json # registers `muscat-pk mcp` for Claude Code
|
|
332
|
+
├─ .gitignore
|
|
333
|
+
└─ README.md # copied into dist/ (with README.<lang>.md, CHANGELOG.md)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The scaffolded `requirements.txt` pins `rainbow-rb-sdk`, `rainbow-rb-flat-buffers` and `rainbow-rb-zenoh` to the same version (change it with `muscat-pk sdk update`). Generated, not committed: `node_modules/`, `dist/`, `.muscat-dev/`, `.venv/`, `*.zip`, `cpp/build*`, `ros2_ws/{install,build*,log}`.
|
|
337
|
+
|
|
338
|
+
`vite.config.ts`:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { defineConfig } from "vite";
|
|
342
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
343
|
+
import { muscat } from "@rainbow-robotics/muscat-plugin-kit/vite";
|
|
344
|
+
|
|
345
|
+
export default defineConfig({ plugins: [tailwindcss(), muscat()] });
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The ZIP contains only the plugin's own code and assets. `@rainbow-robotics/plugin-ui`, `react` and `react-dom` come from the host's import map.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 5. manifest.json
|
|
353
|
+
|
|
354
|
+
Fully validated by `muscat-pk build` (errors: `manifest.json errors: …`). `dev` / `dev:back` check `variables` and `ports` before start, and `dev:back` prints the rest as warnings.
|
|
355
|
+
|
|
356
|
+
### 5.1 Full example
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"id": "com.example.my-plugin",
|
|
361
|
+
"name": "My Plugin",
|
|
362
|
+
"version": "1.0.0",
|
|
363
|
+
"minMuscatVersion": "1.0.0",
|
|
364
|
+
"author": "Kim",
|
|
365
|
+
"email": "kim@example.com",
|
|
366
|
+
"company": "Example Inc.",
|
|
367
|
+
"description": { "en": "Gripper helper", "ko": "그리퍼 도우미" },
|
|
368
|
+
"icons": "assets/icon.svg",
|
|
369
|
+
"targetModels": "all",
|
|
370
|
+
"targetCategorys": ["manipulate"],
|
|
371
|
+
"contributes": [
|
|
372
|
+
{ "point": "toolbar", "icon": "assets/icon.svg", "label": { "en": "My Plugin" }, "ui": "content", "opens": "popover" }
|
|
373
|
+
],
|
|
374
|
+
"variables": [
|
|
375
|
+
{ "name": "RBP_grip_force", "type": "number", "init": 0.5, "description": { "en": "Gripper force (0–1)" } }
|
|
376
|
+
],
|
|
377
|
+
"ports": [28600],
|
|
378
|
+
"runtime": { "type": "python", "entry": "main.py" }
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 5.2 Top-level fields
|
|
383
|
+
|
|
384
|
+
| Field | Required | Rule |
|
|
385
|
+
|---|---|---|
|
|
386
|
+
| `id` | yes | reverse-domain: `.`-separated parts of lowercase letters/digits joined by single `-`, each starting with a letter (`com.example.my-plugin`). No `_` |
|
|
387
|
+
| `name`, `version`, `minMuscatVersion` | yes | non-empty strings |
|
|
388
|
+
| `author`, `email` | yes | non-empty strings |
|
|
389
|
+
| `company` | no | string |
|
|
390
|
+
| `description` | yes | string or i18n object |
|
|
391
|
+
| `icons` | yes | SVG path string, or `{ "16", "48", "128" }` for PNG/JPG (all three) |
|
|
392
|
+
| `targetModels` / `targetCategorys` | at least one | `"all"` or a non-empty array of strings. Categories: `manipulate`, `amr`, `rby1`, `vision` |
|
|
393
|
+
| `contributes` | no | see 5.3. Missing or `[]` = a backend-only plugin (no screens, no UI build) |
|
|
394
|
+
| `variables` | no | see 5.5 |
|
|
395
|
+
| `ports` | no | see 5.6 |
|
|
396
|
+
| `runtime` | yes | see 5.7 |
|
|
397
|
+
| `installedSize` | — | written by `build` (bytes) |
|
|
398
|
+
|
|
399
|
+
i18n object: `en` is required; allowed keys `ko`, `en`, `ja`, `zh`, `vi`, `th`, `es`.
|
|
400
|
+
|
|
401
|
+
### 5.3 `contributes`
|
|
402
|
+
|
|
403
|
+
| Field | Rule |
|
|
404
|
+
|---|---|
|
|
405
|
+
| `point` | `toolbar`, `lnb`, `move.panel`, `program.panel`, `program.monitor` |
|
|
406
|
+
| `ui` | UI entry name under `ui/` without extension (default `content`); unique across entries |
|
|
407
|
+
| `label` | required, string or i18n object |
|
|
408
|
+
| `icon` | icon path |
|
|
409
|
+
| `opens` | toolbar only: `popover`, `panel`, `page` |
|
|
410
|
+
| `commandInfo` | required for `program.panel` (5.4) |
|
|
411
|
+
|
|
412
|
+
A non-empty `contributes` needs at least one `toolbar` entry (that is where the host lists the plugin). Each `ui` must have a matching `ui/<entry>.tsx` (or `.ts` / `.jsx` / `.js`), checked on `build`.
|
|
413
|
+
|
|
414
|
+
| Point | Where it shows |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `toolbar` | header toolbar button; opens as popover / panel / page |
|
|
417
|
+
| `lnb` | left navigation |
|
|
418
|
+
| `move.panel` | Move page panel |
|
|
419
|
+
| `program.panel` | a custom command in the Program page's step tree; its `ui` is the step's edit form |
|
|
420
|
+
| `program.monitor` | a tab in the Program page's monitor panel (next to General / Digital I/O / Monitor / Log), rendered per task |
|
|
421
|
+
|
|
422
|
+
### 5.4 `commandInfo` (`program.panel`)
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"point": "program.panel",
|
|
427
|
+
"ui": "program",
|
|
428
|
+
"label": { "en": "My Step" },
|
|
429
|
+
"commandInfo": {
|
|
430
|
+
"name": "My Step",
|
|
431
|
+
"icon": "assets/icon.svg",
|
|
432
|
+
"funcName": "rb_program_sdk.plugin_execute",
|
|
433
|
+
"args": { "message": "hello" }
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
| Field | Type | Meaning |
|
|
439
|
+
|---|---|---|
|
|
440
|
+
| `name` | string, required | command name in the tree |
|
|
441
|
+
| `icon` | string, required | icon path |
|
|
442
|
+
| `iconClassName` | string | extra icon class |
|
|
443
|
+
| `funcName` | string | function the executor calls (`rb_program_sdk.plugin_execute` for Python backends) |
|
|
444
|
+
| `args` | object | default step args, passed with `plugin_id` to the backend |
|
|
445
|
+
| `disabled`, `syncGroupId`, `disableCopy`, `disableDelete`, `requireChildren`, `requireParent` | boolean | tree editing rules |
|
|
446
|
+
| `allowChildren`, `allowParents`, `notAllowChildren` | string[] | allowed / forbidden neighbour commands |
|
|
447
|
+
| `childrenCommand` | string | command created as a child |
|
|
448
|
+
|
|
449
|
+
### 5.5 `variables` (PyFM)
|
|
450
|
+
|
|
451
|
+
Variables the plugin exposes to PyFM programs. The Program page's variable picker lists only declared ones.
|
|
452
|
+
|
|
453
|
+
```json
|
|
454
|
+
{ "name": "RBP_target_pose", "type": "array", "init": [0, 0, 0], "description": { "en": "Target pose" } }
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
| Field | Required | Rule |
|
|
458
|
+
|---|---|---|
|
|
459
|
+
| `name` | yes | `RBP_` + letters/digits/underscore; unique per plugin |
|
|
460
|
+
| `type` | yes | `number`, `string`, `boolean`, `array` |
|
|
461
|
+
| `init` | yes | initial value matching `type` |
|
|
462
|
+
| `description` | no | string or i18n object — write one, the UI shows nothing otherwise |
|
|
463
|
+
|
|
464
|
+
### 5.6 `ports`
|
|
465
|
+
|
|
466
|
+
A backend that opens a TCP listener must declare every port:
|
|
467
|
+
|
|
468
|
+
```json
|
|
469
|
+
{ "ports": [28600, 28601] }
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
| Rule | Enforced by |
|
|
473
|
+
|---|---|
|
|
474
|
+
| Only `28600-28999`, `number[]`, non-empty when present, no duplicates | manifest validation |
|
|
475
|
+
| Reserved ports rejected: `502`, `2222`, `4840`, `5170-5179`, `8000-8999`, `10000`, `30004`, `34964`, `44818`, `50000-50005`, `50051` | manifest validation |
|
|
476
|
+
| Every listening port must be declared | `dev` / `dev:back` runtime monitor stops the backend on the first undeclared port (`backend opened undeclared TCP port(s): …`) |
|
|
477
|
+
| No two enabled plugins share a port | the robot, at install (HTTP 409) |
|
|
478
|
+
|
|
479
|
+
- The kit's own dev ports (gateway `10000`, MediaMTX `8554` / `8889` / `8189`, the Zenoh dev peer port) and OS-assigned loopback ports are excluded.
|
|
480
|
+
- The monitor can't tell plugin ports apart when the backend shares a network namespace with other services, so it is off on native Linux (`--network host`) and when the backend joins a local Muscat container (the usual `dev` without a robot). Declare them anyway — the robot checks them at install.
|
|
481
|
+
- Declared ports are published on every host interface, TCP and UDP, so devices on the LAN reach them as on the robot (from the local Muscat under `dev`); a busy host port is skipped with a warning. The backend gets them as `RB_PLUGIN_PORTS`.
|
|
482
|
+
|
|
483
|
+
### 5.7 `runtime`
|
|
484
|
+
|
|
485
|
+
| runtime | Source | Required fields |
|
|
486
|
+
|---|---|---|
|
|
487
|
+
| `{ "type": "none" }` | — | UI-only plugin |
|
|
488
|
+
| `{ "type": "python", "entry": "main.py" }` | `py/` | `entry` |
|
|
489
|
+
| `{ "type": "cpp", "entry": "<executable>" }` | `cpp/` | `entry` = built executable name |
|
|
490
|
+
| `{ "type": "ros2", "rosClient": "rclpy", "package": "<pkg>", "launch": "<pkg>_node" }` | `ros2_ws/` | `rosClient`, `package`, `launch` |
|
|
491
|
+
| `{ "type": "ros2", "rosClient": "rclcpp", "package": "<pkg>", "launch": "<pkg>_node" }` | `ros2_ws/` | same |
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 6. Plugin UI SDK (`useRb`)
|
|
496
|
+
|
|
497
|
+
Package `@rainbow-robotics/plugin-ui`:
|
|
498
|
+
|
|
499
|
+
| Import | Contents |
|
|
500
|
+
|---|---|
|
|
501
|
+
| `@rainbow-robotics/plugin-ui/react` | Muscat components (`RB*`), `useRb`, `useRbFormData`, `useRbSave`, `useRbStorage`, `createRbStore`, `RbSdk` / `RbStore` / `RbSetState` types |
|
|
502
|
+
| `@rainbow-robotics/plugin-ui/sdk` | the same hooks without the components, plus `RbContext` and the `RbNotifyType` type |
|
|
503
|
+
| `@rainbow-robotics/plugin-ui/manifest` | manifest types (`PluginManifest`, `PluginContribution`, `PluginVariable`, `PluginRuntime`), `CONTRIBUTION_POINTS`, `VARIABLE_TYPES`, `validateManifest` |
|
|
504
|
+
|
|
505
|
+
Outside a Muscat host (a unit test, Storybook) `useRb()` returns a stub: calls log `[plugin-ui] rb.<name> called outside of a host context` and resolves to an empty value.
|
|
506
|
+
|
|
507
|
+
### 6.1 Entry component
|
|
508
|
+
|
|
509
|
+
Each top-level `ui/*.tsx` default-exports a React component. The host mounts it and provides `rb` through context.
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
import { useRb } from "@rainbow-robotics/plugin-ui/sdk";
|
|
513
|
+
import { RBLabelButton } from "@rainbow-robotics/plugin-ui/react";
|
|
514
|
+
|
|
515
|
+
export default function App() {
|
|
516
|
+
const rb = useRb();
|
|
517
|
+
return <RBLabelButton onClick={() => rb.action("grip", { width: 20 })}>Grip</RBLabelButton>;
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Use plugin-ui components instead of native `<button>`, `<input>`, `<select>`, `<textarea>`, `<table>`, `<dialog>`. Common mappings: button → `RBLabelButton` / `RBIconButton`, text input → `RBTextInput` / `RBTextField`, number → `RBSideNumberInput`, select → `RBSelectBox` + `Option`, checkbox → `RBCheckBox`, switch → `RBSwitch`, slider → `RBSlider`, table → `RBTable`, modal → `RBModal`, tabs → `RBTabs`. `muscat-pk storybook` and `muscat-pk mcp` show props and examples.
|
|
522
|
+
|
|
523
|
+
### 6.2 Messaging with the backend
|
|
524
|
+
|
|
525
|
+
| UI | Backend (Python) | Pattern |
|
|
526
|
+
|---|---|---|
|
|
527
|
+
| `rb.pub(topic, payload)` | `@plugin.subscribe(topic)` | fire-and-forget UI → backend |
|
|
528
|
+
| `rb.sub(topic, handler)` | `plugin.publish(topic, payload)` | backend → UI stream |
|
|
529
|
+
| `rb.action(topic, payload?)` | `@plugin.serve(topic)` | UI request, backend reply |
|
|
530
|
+
| `rb.serve(topic, handler)` | `plugin.call(topic, payload=...)` | backend request, UI reply |
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
const rb = useRb();
|
|
534
|
+
|
|
535
|
+
await rb.pub("event", { clicked: true });
|
|
536
|
+
|
|
537
|
+
const sub = await rb.sub<{ ready: boolean }>("state", (s) => setReady(s.ready));
|
|
538
|
+
// cleanup: sub.undeclare()
|
|
539
|
+
|
|
540
|
+
const res = await rb.action<{ ok: boolean }, { width: number }>("grip", { width: 20 });
|
|
541
|
+
|
|
542
|
+
await rb.serve("tool/ping", (payload) => ({ pong: payload }));
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
- Topics are scoped to the plugin id: `"state"` → `{pluginId}/state`. A leading `/` makes it absolute (`"/broadcast/x"`).
|
|
546
|
+
- All four take generics for payload / result types.
|
|
547
|
+
- Unsubscribe in a `useEffect` cleanup:
|
|
548
|
+
|
|
549
|
+
```tsx
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
let sub: { undeclare(): Promise<void> } | undefined;
|
|
552
|
+
rb.sub("state", setState).then((s) => (sub = s));
|
|
553
|
+
return () => void sub?.undeclare();
|
|
554
|
+
}, [rb]);
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### 6.3 Host dialogs and toast
|
|
558
|
+
|
|
559
|
+
| API | Resolves |
|
|
560
|
+
|---|---|
|
|
561
|
+
| `rb.toast({ title?, message, type?, actionLabel?, handleActionClick?, closeOnActionClick?, duration?, onClose? })` | — (`message`: string or ReactElement; `type`: `info`, `danger`, `warning`, `caution`, `success`) |
|
|
562
|
+
| `rb.alert({ title?, message, buttonText? })` | when closed |
|
|
563
|
+
| `rb.confirm({ title?, message, buttonText?: { yes, no } })` | `true` / `false` |
|
|
564
|
+
| `rb.prompt({ title?, message, value?, placeholder?, buttonText?: { yes, no } })` | entered string, or `undefined` when cancelled |
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
567
|
+
if (await rb.confirm({ title: "Run", message: "Start the backend action?" })) {
|
|
568
|
+
await rb.action("run");
|
|
569
|
+
rb.toast({ message: "Started", type: "success" });
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### 6.4 Program page (`rb.program`)
|
|
574
|
+
|
|
575
|
+
| API | Meaning |
|
|
576
|
+
|---|---|
|
|
577
|
+
| `rb.program.getSteps(scope?)` | main step tree of the program open on the Program page |
|
|
578
|
+
| `rb.program.setSteps(steps, scope?)` | replace it — an unsaved edit the user then saves |
|
|
579
|
+
| `rb.program.getCatalog(scope?)` | the loaded robot's command catalog (names, default args, nesting rules) |
|
|
580
|
+
| `rb.program.getCommandDefaults?(command, scope?)` | default step of a command whose defaults read the live robot (catalog `rules.hasFactoryDataFn`); optional, host-dependent |
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
type PluginProgramStep = {
|
|
584
|
+
command: string; // "MoveJ", "Point", "Wait", "Repeat", "Condition", ...
|
|
585
|
+
args?: Record<string, unknown>;
|
|
586
|
+
summary?: string;
|
|
587
|
+
fields?: Record<string, unknown>; // step-level fields, e.g. SubTask's subTaskType
|
|
588
|
+
children?: PluginProgramStep[];
|
|
589
|
+
};
|
|
590
|
+
type PluginProgramScope = { taskId?: string; robotModel?: string };
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Without `scope` the host uses the first main task. In a `program.monitor` tab `rb.data` is `{ point: "program.monitor", taskId, robotModel }` — pass it as `scope` so a multi-component robot hits the right task.
|
|
594
|
+
|
|
595
|
+
### 6.5 Step form save/restore (`program.panel`)
|
|
596
|
+
|
|
597
|
+
| Hook | Meaning |
|
|
598
|
+
|---|---|
|
|
599
|
+
| `useRbFormData<T>()` | values saved in this step, `undefined` for a new step |
|
|
600
|
+
| `useRbSave(fn)` | registers the getter the host calls on Save; always sees current state. Return value must be JSON |
|
|
601
|
+
|
|
602
|
+
```tsx
|
|
603
|
+
import { useState } from "react";
|
|
604
|
+
import { RBSlider, useRbFormData, useRbSave } from "@rainbow-robotics/plugin-ui/react";
|
|
605
|
+
|
|
606
|
+
type Args = { width: number };
|
|
607
|
+
|
|
608
|
+
export default function App() {
|
|
609
|
+
const saved = useRbFormData<Args>();
|
|
610
|
+
const [args, setArgs] = useState<Args>({ width: 20, ...saved });
|
|
611
|
+
useRbSave(() => ({ args, summary: `${args.width}mm` }));
|
|
612
|
+
return (
|
|
613
|
+
<RBSlider
|
|
614
|
+
value={[args.width]}
|
|
615
|
+
min={0}
|
|
616
|
+
max={100}
|
|
617
|
+
onValueChange={(values: number[]) => setArgs({ width: values[0] ?? 0 })}
|
|
618
|
+
/>
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
The saved `args` reach the backend's `on_execute(args)` (see [7.3](#73-pluginprogramcontext--program-command)).
|
|
624
|
+
|
|
625
|
+
### 6.6 Browser storage (`rb.storage`, `useRbStorage`, `createRbStore`)
|
|
626
|
+
|
|
627
|
+
Keep browser-side plugin data only here. Everything is namespaced `muscat-plugin:<pluginId>`, and the host deletes it when the plugin is uninstalled — data stored any other way (raw `localStorage`, your own IndexedDB) is never cleaned up.
|
|
628
|
+
|
|
629
|
+
| Backend | Use |
|
|
630
|
+
|---|---|
|
|
631
|
+
| `rb.storage` (= `rb.storage.indexedDB`) | default; structured values (objects, arrays, `Date`, `Blob`, …) |
|
|
632
|
+
| `rb.storage.local` | `localStorage`, JSON values, survives reloads |
|
|
633
|
+
| `rb.storage.session` | `sessionStorage`, JSON values, gone when the tab closes |
|
|
634
|
+
| `rb.storage.cookie` | ~4 KB per value and sent with every request to the host — tiny values only |
|
|
635
|
+
|
|
636
|
+
Every backend has the same API:
|
|
637
|
+
|
|
638
|
+
| Method | Meaning |
|
|
639
|
+
|---|---|
|
|
640
|
+
| `get<T>(key)` | value, or `undefined` |
|
|
641
|
+
| `set(key, value)` | save |
|
|
642
|
+
| `delete(key)` | remove |
|
|
643
|
+
| `keys()` | this plugin's keys |
|
|
644
|
+
| `subscribe(listener, key?)` | `({ kind, key, value }) => void` on every change made through this API — this screen, other screens of the plugin (toolbar panel, Program tab, …) and other browser tabs. `value` is `undefined` after a delete. Returns the unsubscribe function |
|
|
645
|
+
| `getItem` / `setItem` / `removeItem` | zustand `StateStorage` (raw strings) |
|
|
646
|
+
|
|
647
|
+
```tsx
|
|
648
|
+
const rb = useRb();
|
|
649
|
+
await rb.storage.set("lastRun", { at: new Date(), ok: true });
|
|
650
|
+
const last = await rb.storage.get<{ at: Date; ok: boolean }>("lastRun");
|
|
651
|
+
await rb.storage.local.set("theme", "dark");
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
#### `useRbStorage` — one stored value as React state
|
|
655
|
+
|
|
656
|
+
```tsx
|
|
657
|
+
import { RBTextInput, useRbStorage } from "@rainbow-robotics/plugin-ui/react";
|
|
658
|
+
|
|
659
|
+
type Draft = { name: string };
|
|
660
|
+
|
|
661
|
+
function Editor() {
|
|
662
|
+
const [draft, setDraft] = useRbStorage<Draft>("draft", { kind: "local", initial: { name: "" } });
|
|
663
|
+
// setDraft(next) saves; setDraft(undefined) deletes
|
|
664
|
+
return <RBTextInput value={draft?.name ?? ""} handleChange={(name: string) => void setDraft({ name })} />;
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
- `useRbStorage<T>(key, { kind?, initial? })` → `[value, setValue]`. `kind` defaults to `"indexeddb"`.
|
|
669
|
+
- Re-renders when the value changes anywhere (other screens, other tabs).
|
|
670
|
+
- `value` is `initial` until the stored value has loaded.
|
|
671
|
+
|
|
672
|
+
#### `createRbStore` — persisted zustand-style store
|
|
673
|
+
|
|
674
|
+
Define once at module level, use from any screen of the plugin:
|
|
675
|
+
|
|
676
|
+
```tsx
|
|
677
|
+
import { createRbStore, RBSlider } from "@rainbow-robotics/plugin-ui/react";
|
|
678
|
+
|
|
679
|
+
type Settings = {
|
|
680
|
+
speed: number;
|
|
681
|
+
points: string[];
|
|
682
|
+
setSpeed: (v: number) => void;
|
|
683
|
+
addPoint: (p: string) => void;
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
export const useSettings = createRbStore<Settings>(
|
|
687
|
+
(set, get) => ({
|
|
688
|
+
speed: 50,
|
|
689
|
+
points: [],
|
|
690
|
+
setSpeed: (speed) => set({ speed }),
|
|
691
|
+
addPoint: (p) => set((s) => ({ points: [...s.points, p] })),
|
|
692
|
+
}),
|
|
693
|
+
{ name: "settings", storage: "local" },
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
function SpeedPanel() {
|
|
697
|
+
const speed = useSettings((s) => s.speed); // re-renders only when speed changes
|
|
698
|
+
const setSpeed = useSettings((s) => s.setSpeed);
|
|
699
|
+
return <RBSlider value={[speed]} min={0} max={100} onValueChange={(v: number[]) => setSpeed(v[0] ?? 0)} />;
|
|
700
|
+
}
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
| Member | Meaning |
|
|
704
|
+
|---|---|
|
|
705
|
+
| `createRbStore<T>(init, { name, storage? })` | `init(set, get)` returns initial state + actions. `name` is the storage key; `storage` is a backend kind (default `"indexeddb"`) |
|
|
706
|
+
| `useStore()` / `useStore(selector)` | whole state / a slice (re-renders only when the slice changes) |
|
|
707
|
+
| `useStore.getState()` | current state, outside React |
|
|
708
|
+
| `useStore.setState(partial \| (s) => partial)` | merge a change, outside React |
|
|
709
|
+
| `useStore.subscribe((next, prev) => …)` | listen outside React; returns unsubscribe |
|
|
710
|
+
|
|
711
|
+
- State changes only through `set` / `setState`. Every change is saved and re-renders every screen and tab using the store.
|
|
712
|
+
- Functions (actions) are not saved; only data fields are.
|
|
713
|
+
- The saved data loads when the first component using the store mounts (the plugin id is known only then). Before that `getState()` returns the initial values.
|
|
714
|
+
- Changes are merged shallowly: set a new array/object (`[...s.points, p]`) instead of mutating it.
|
|
715
|
+
|
|
716
|
+
#### Using zustand `persist` directly
|
|
717
|
+
|
|
718
|
+
Every backend is a zustand `StateStorage`:
|
|
719
|
+
|
|
720
|
+
```ts
|
|
721
|
+
import { persist, createJSONStorage } from "zustand/middleware";
|
|
722
|
+
persist(fn, { name: "settings", storage: createJSONStorage(() => rb.storage.local) });
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
`zustand` is then a dependency of your plugin; `createRbStore` needs nothing extra.
|
|
726
|
+
|
|
727
|
+
### 6.7 Other
|
|
728
|
+
|
|
729
|
+
| API | Meaning |
|
|
730
|
+
|---|---|
|
|
731
|
+
| `rb.data` | host-provided mount data (`program.monitor` scope, saved form values); `undefined` for a fresh mount |
|
|
732
|
+
| `rb.onSave(getPayload)` | low-level form of `useRbSave`; returns the unregister function |
|
|
733
|
+
| `rb.rosPeers.get()` | PCs allowed to reach this plugin's ROS 2 nodes |
|
|
734
|
+
| `rb.rosPeers.set(ips)` | save the list and restart the backend; rejects an invalid IP |
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
## 7. Python plugin SDK
|
|
739
|
+
|
|
740
|
+
Python backends (`runtime.type: "python"`, entry `py/main.py`) use `rainbow-rb-sdk`, pinned in `requirements.txt` and changed with `muscat-pk sdk update`. The robot's plugin container provides the SDK at runtime.
|
|
741
|
+
|
|
742
|
+
```python
|
|
743
|
+
from rainbow.rb_sdk import RBPluginCall, PluginProgramContext, RBManipulateSDK, RBAmrSDK, RBRby1SDK
|
|
744
|
+
from rainbow.rb_sdk.rb_sdk_cpp import ConnectOptions, PluginRouteError, RBPluginGatewayError
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
`rainbow.rb_sdk` exports `RBPluginCall`, `PluginProgramContext`, `RBManipulateSDK`, `RBAmrSDK`, `RBRby1SDK`, `RBProgramSDK`, `RBBaseSDK` and `rby1` (the vendored rby1-sdk types). Everything else (`ConnectOptions`, `PluginRouter`, `PluginRouteError`, `RBPluginGatewayError`, …) is in `rainbow.rb_sdk.rb_sdk_cpp`.
|
|
748
|
+
|
|
749
|
+
Which API depends on the contribution point:
|
|
750
|
+
|
|
751
|
+
| Contribution point | Backend |
|
|
752
|
+
|---|---|
|
|
753
|
+
| `toolbar` / `lnb` / `move.panel` / `program.monitor` | `RBPluginCall` + robot SDKs (`RBManipulateSDK`, `RBAmrSDK`) called directly. No stop/pause/variable context |
|
|
754
|
+
| `program.panel` | `PluginProgramContext` — runs as a program step, tied to stop/pause and program variables |
|
|
755
|
+
|
|
756
|
+
### 7.1 `RBPluginCall` — UI ↔ backend
|
|
757
|
+
|
|
758
|
+
Internal only (UI ↔ backend, plugin ↔ plugin); not reachable from outside Muscat. `RBPluginCall(plugin_id=None)`: the id is the argument, else env `RB_PLUGIN_ID`, else `PLUGIN_ID` (slashes trimmed). There is one instance per process and plugin id — calling `RBPluginCall()` again returns the same object.
|
|
759
|
+
|
|
760
|
+
```python
|
|
761
|
+
plugin = RBPluginCall()
|
|
762
|
+
|
|
763
|
+
@plugin.serve("grip") # UI: await rb.action("grip", { width })
|
|
764
|
+
def grip(payload=None):
|
|
765
|
+
width = (payload or {}).get("width", 20)
|
|
766
|
+
plugin.publish("state", {"last_grip_width": width})
|
|
767
|
+
return {"ok": True, "width": width}
|
|
768
|
+
|
|
769
|
+
@plugin.subscribe("event") # UI: rb.pub("event", ...)
|
|
770
|
+
def on_event(payload=None):
|
|
771
|
+
print(payload)
|
|
76
772
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
773
|
+
plugin.publish("state", {"ready": True}) # UI: rb.sub("state", ...)
|
|
774
|
+
res = plugin.call("tool/ping", payload={"hi": 1}, timeout=3.0) # UI: rb.serve("tool/ping", ...)
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
| Member | Meaning |
|
|
778
|
+
|---|---|
|
|
779
|
+
| `serve(keyexpr, handler=None)` | request/response handler; decorator or direct call. Return value = reply. One handler per key — serving it again replaces the previous one |
|
|
780
|
+
| `subscribe(topic, callback=None)` | receive publishes |
|
|
781
|
+
| `publish(topic, payload=None)` | push to subscribers; dropped silently while disconnected |
|
|
782
|
+
| `call(keyexpr, *, payload, timeout=3.0)` | request the other side and wait; raises `RBPluginGatewayError` when disconnected or on timeout |
|
|
783
|
+
| `unserve(keyexpr=None)` / `undeclare_queryable(keyexpr=None)` | remove one / all serve handlers |
|
|
784
|
+
| `unsubscribe(topic=None, callback=None)` / `unsubscribe_topic(topic=None, callback=None)` | remove one callback, a topic's subscriptions, or all |
|
|
785
|
+
| `plugin_id` | the plugin id in use (dev id under `muscat-pk dev`) |
|
|
786
|
+
| `connected` | transport connected |
|
|
787
|
+
| `data_dir` | persistent data directory, created on access: `RB_PLUGIN_DATA_DIR` (set by Muscat), else `/app/data/runtime/plugins/data/{plugin_id}`. Survives updates and restarts; removed with the plugin. The install directory (cwd) is replaced on every update — keep state, caches and settings here |
|
|
788
|
+
| `router` | external endpoints (7.2) |
|
|
789
|
+
|
|
790
|
+
Payloads must be JSON-serialisable. Handlers may be sync or async. Topics are scoped like the UI's: `"state"` → `{plugin_id}/state`, a leading `/` is absolute.
|
|
791
|
+
|
|
792
|
+
### 7.2 `plugin.router` — external endpoints
|
|
793
|
+
|
|
794
|
+
Exposes `common/plugin/{plugin_id}/{path}` over REST, Socket.IO, TCP, MQTT and WebRTC. The running Muscat (`common`) picks it up without a restart; it disappears when the plugin stops. Requires `rainbow-rb-sdk` ≥ 0.0.9.dev5 on the robot (older: `AttributeError`).
|
|
80
795
|
|
|
81
|
-
|
|
82
|
-
|
|
796
|
+
```python
|
|
797
|
+
@plugin.router.on("/hello") # every transport
|
|
798
|
+
def hello(payload, meta):
|
|
799
|
+
return {"msg": f"hi {payload.get('name')}", "via": meta["transport"]}
|
|
800
|
+
|
|
801
|
+
@plugin.router.on("/admin", explicit_routes=["api"]) # REST only
|
|
802
|
+
def admin(payload, meta):
|
|
803
|
+
if not (meta.get("principal") or {}).get("is_admin"):
|
|
804
|
+
raise PluginRouteError(403, "admin only")
|
|
805
|
+
return {"ok": True}
|
|
806
|
+
|
|
807
|
+
@plugin.router.subscribe("/cmd", explicit_events=["tcp"])
|
|
808
|
+
def on_cmd(payload):
|
|
809
|
+
...
|
|
810
|
+
|
|
811
|
+
plugin.router.emit("/state", {"busy": True})
|
|
83
812
|
```
|
|
84
813
|
|
|
85
|
-
|
|
814
|
+
| API | Meaning | External side |
|
|
815
|
+
|---|---|---|
|
|
816
|
+
| `router.on(path, explicit_routes=None)` | request/response | calls `common/plugin/{id}/{path}` (REST: `POST`) |
|
|
817
|
+
| `router.subscribe(topic, explicit_events=None)` | receive external publishes | publishes to `common/plugin/{id}/{topic}` |
|
|
818
|
+
| `router.emit(topic, payload=None)` | push to external subscribers | subscribes to `common/plugin/{id}/{topic}` |
|
|
819
|
+
|
|
820
|
+
Handler contract:
|
|
821
|
+
|
|
822
|
+
- Signature `(payload, meta)`, `(payload)` or `()` — only declared arguments are injected. Sync or async.
|
|
823
|
+
- `payload`: what the caller sent (REST: JSON body merged with query params).
|
|
824
|
+
- `meta`: `{"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": dict | None}`. With the login policy on, `principal` is the token subject. Role checks are the plugin's job.
|
|
825
|
+
- Return value = response body (JSON). `None` → empty.
|
|
826
|
+
- `raise PluginRouteError(code, message)` sets the status; any other exception → 500 with `str(exc)`.
|
|
827
|
+
- `subscribe` handlers take `(payload)` and return nothing.
|
|
828
|
+
|
|
829
|
+
Rules:
|
|
86
830
|
|
|
87
|
-
|
|
831
|
+
- Write only the path; the SDK adds `plugin_id`. Multi-segment paths work (`/tool/echo`). Declaring a path twice → `ValueError`.
|
|
832
|
+
- `explicit_routes` / `explicit_events`: `None` = every transport. Names `api | socket | tcp | mqtt | webrtc`; `subscribe` accepts only `tcp | mqtt | webrtc`. Unknown → `ValueError`. A disallowed transport gets 404.
|
|
833
|
+
- `on` and `subscribe` may share a name.
|
|
834
|
+
|
|
835
|
+
Calling from outside:
|
|
836
|
+
|
|
837
|
+
| Transport | How |
|
|
88
838
|
|---|---|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
| `muscat-pk dev:ui` | 미니 Muscat 미리보기 서버 |
|
|
95
|
-
| `muscat-pk dev:back` | 백엔드 개발 서버 (hot-reload) |
|
|
96
|
-
| `muscat-pk update` | kit + plugin-ui 최신 버전으로 업데이트 |
|
|
97
|
-
| `muscat-pk sdk <list\|update>` | Python rainbow-rb-sdk 버전 관리 |
|
|
839
|
+
| REST | `POST /common/plugin/{id}/{path}`, JSON body |
|
|
840
|
+
| Socket.IO | emit `common/plugin/{id}/{path}`; reply in the ack |
|
|
841
|
+
| TCP | topic `common/plugin/{id}/{path}` request / publish |
|
|
842
|
+
| MQTT | request `common/plugin/{id}/{path}/__req__`; publish `common/plugin/{id}/{topic}` |
|
|
843
|
+
| WebRTC | data channel topic `common/plugin/{id}/{path}` |
|
|
98
844
|
|
|
99
|
-
|
|
845
|
+
```bash
|
|
846
|
+
curl -s -X POST http://<robot>/common/plugin/com.example.my-plugin/hello \
|
|
847
|
+
-H 'Content-Type: application/json' -d '{"name":"kim"}'
|
|
848
|
+
# under muscat-pk dev (the dev id is printed at start)
|
|
849
|
+
curl -s -X POST http://localhost:3000/common/plugin/<dev-id>/hello -H 'Content-Type: application/json' -d '{}'
|
|
850
|
+
```
|
|
100
851
|
|
|
101
|
-
|
|
|
852
|
+
| Situation | Status |
|
|
102
853
|
|---|---|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
854
|
+
| No such path / plugin down / transport not allowed | 404 |
|
|
855
|
+
| No answer within 10 s | 504 |
|
|
856
|
+
| `common` ↔ gateway not connected | 503 |
|
|
857
|
+
| Handler raised | 500 (`PluginRouteError` → its code) |
|
|
858
|
+
|
|
859
|
+
Auth: with the login policy on a token is required (any role); REST uses the same Bearer token as other Muscat APIs. Off: no token.
|
|
860
|
+
|
|
861
|
+
Known limits:
|
|
862
|
+
|
|
863
|
+
- A path named `install` can't be called over REST (taken by the registry install route).
|
|
864
|
+
- MQTT echoes to the sender: `emit` + `subscribe` on the same topic receives its own push over `mqtt`. Drop `mqtt` from `explicit_events` or use separate topics.
|
|
865
|
+
- After restarting the plugin, external calls may return 504 for up to a minute, then 404, until the gateway drops the old socket.
|
|
866
|
+
|
|
867
|
+
### 7.3 `PluginProgramContext` — program command
|
|
868
|
+
|
|
869
|
+
Used only for `program.panel`. When the program reaches the step, the executor calls `commandInfo.funcName` with `plugin_id` + the step args, and the SDK calls your `on_execute`.
|
|
870
|
+
|
|
871
|
+
```python
|
|
872
|
+
import asyncio
|
|
873
|
+
from rainbow.rb_sdk import PluginProgramContext
|
|
874
|
+
|
|
875
|
+
ctx = PluginProgramContext(plugin_id="com.example.my-plugin")
|
|
876
|
+
|
|
877
|
+
@ctx.on_execute
|
|
878
|
+
async def handle(args: dict) -> None:
|
|
879
|
+
count = await ctx.get_variable("counter")
|
|
880
|
+
await ctx.set_variable("counter", (count or 0) + 1)
|
|
881
|
+
await ctx.set_variable("RBP_my_plugin_result", 42) # new variable → RBP_ required
|
|
882
|
+
|
|
883
|
+
for _ in range(100):
|
|
884
|
+
await ctx.check_stop() # stop → CancelledError, pause → waits until resumed
|
|
885
|
+
await asyncio.sleep(0.5)
|
|
886
|
+
|
|
887
|
+
await ctx.done() # always; the step never finishes otherwise
|
|
888
|
+
|
|
889
|
+
@ctx.on_executor_state_change
|
|
890
|
+
async def on_change(robot_model: str, state: str) -> None:
|
|
891
|
+
if state == "paused":
|
|
892
|
+
print(f"{robot_model} paused")
|
|
893
|
+
|
|
894
|
+
async def background() -> None:
|
|
895
|
+
while True:
|
|
896
|
+
await asyncio.sleep(1.0)
|
|
897
|
+
await ctx.set_variable("RBP_my_plugin_heartbeat", 1) # ignored when no program runs
|
|
898
|
+
|
|
899
|
+
async def main() -> None:
|
|
900
|
+
asyncio.create_task(background())
|
|
901
|
+
await ctx.start() # stays resident
|
|
902
|
+
|
|
903
|
+
asyncio.run(main())
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
| Member | Meaning |
|
|
907
|
+
|---|---|
|
|
908
|
+
| `on_execute(fn)` | decorator; called once per execution of this step with the step args |
|
|
909
|
+
| `done()` | step finished — always call |
|
|
910
|
+
| `check_stop(robot_model=None)` | stop → `CancelledError`, pause → waits. Call every loop iteration |
|
|
911
|
+
| `get_variable(name, robot_model=None, plugin_id=None)` | read; `None` when no program runs |
|
|
912
|
+
| `set_variable(name, value)` | write to every running main task; ignored when no program runs |
|
|
913
|
+
| `executor_state(robot_model=None)` | state string for a model, or `{model: state}` for all |
|
|
914
|
+
| `is_paused(robot_model=None)` / `is_stopped(robot_model=None)` | state checks |
|
|
915
|
+
| `on_executor_state_change(fn)` | decorator; `(robot_model, state)` once per change |
|
|
916
|
+
| `set_execute_handler(fn)` / `set_state_change_handler(fn)` | the same two registrations without a decorator |
|
|
917
|
+
| `begin_execution(robot_model=None)` / `end_execution()` | execute lifecycle hooks the SDK calls around `on_execute`; not needed in normal plugins |
|
|
918
|
+
| `start()` / `stop()` / `pause()` | lifecycle |
|
|
919
|
+
| `in_execute`, `exec_robot_model`, `plugin_id`, `started`, `control_topic` | properties |
|
|
920
|
+
|
|
921
|
+
#### Variables
|
|
922
|
+
|
|
923
|
+
- Existing variable: written by name as-is — exchanging values with the program is the point.
|
|
924
|
+
- New variable: must start with `RBP_` (`RuntimeError` otherwise), inside or outside a step. Use `RBP_{plugin}_{name}` to avoid collisions; `RBP_` alone doesn't prevent two plugins overwriting each other.
|
|
925
|
+
- Declare in `manifest.json` → `variables` what the Program page should offer (5.5).
|
|
926
|
+
|
|
927
|
+
`set_variable` targets every running main task:
|
|
928
|
+
|
|
929
|
+
| When | Own program | Other robots' main tasks |
|
|
930
|
+
|---|---|---|
|
|
931
|
+
| During own step | immediate | at their next step boundary |
|
|
932
|
+
| Outside a step | next step boundary | same |
|
|
933
|
+
|
|
934
|
+
`get_variable` options:
|
|
935
|
+
|
|
936
|
+
```python
|
|
937
|
+
await ctx.get_variable("counter") # first value across main tasks
|
|
938
|
+
await ctx.get_variable("counter", robot_model="C500920") # one robot
|
|
939
|
+
await ctx.get_variable("RBP_TEST", plugin_id="A.plugin") # what that plugin last wrote
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
Each variable keeps a per-plugin record (`meta.writers`), so a colliding name is still recoverable: `get_variable(..., plugin_id=...)` from the SDK, `get_var("A.plugin", "RBP_TEST")` in a program expression. A write can rarely be lost when it races the worker's flush — re-check important values.
|
|
943
|
+
|
|
944
|
+
#### Executor state
|
|
945
|
+
|
|
946
|
+
States: `idle | running | paused | waiting | stopped | post_start | completed | error`. A model with no running task is `None` / absent.
|
|
947
|
+
|
|
948
|
+
- Published every 0.8 s on `muscat/executor/state` — up to ~1 s latency.
|
|
949
|
+
- With `IS_DEV=true` there is no publish; always `None`.
|
|
950
|
+
- `on_executor_state_change` fires once per per-model change; handler exceptions are only logged.
|
|
951
|
+
|
|
952
|
+
Scope of `check_stop` / `is_paused` / `is_stopped`:
|
|
105
953
|
|
|
106
|
-
|
|
954
|
+
1. `robot_model` given → that model
|
|
955
|
+
2. omitted inside `on_execute` → the model executing this step
|
|
956
|
+
3. omitted elsewhere → all models
|
|
957
|
+
|
|
958
|
+
### 7.4 Robot SDKs from the backend
|
|
959
|
+
|
|
960
|
+
```python
|
|
961
|
+
manipulate = RBManipulateSDK()
|
|
962
|
+
manipulate.move.call_move_j(
|
|
963
|
+
robot_model="C500920",
|
|
964
|
+
target={"tar_values": [0, 0, 0, 0, 0, 0, 0], "tar_frame": -1, "tar_unit": 0},
|
|
965
|
+
)
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
Add your own safety checks before moving the robot. Exact signatures: `muscat-pk mcp` (`search_rb_sdk`, `get_rb_sdk_api`).
|
|
969
|
+
|
|
970
|
+
### 7.5 Remote dev (laptop → robot)
|
|
971
|
+
|
|
972
|
+
```bash
|
|
973
|
+
export MUSCAT_ROBOT_HOST=192.168.1.100
|
|
974
|
+
export MUSCAT_ROBOT_TOKEN=<service token> # only when the robot's login policy is on
|
|
975
|
+
muscat-pk dev:back
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
```python
|
|
979
|
+
async def main():
|
|
980
|
+
host = os.getenv("MUSCAT_ROBOT_HOST")
|
|
981
|
+
if host:
|
|
982
|
+
opts = ConnectOptions()
|
|
983
|
+
opts.host = host
|
|
984
|
+
opts.token = os.getenv("MUSCAT_ROBOT_TOKEN", "")
|
|
985
|
+
manipulate.connect(opts)
|
|
986
|
+
register_handlers() # after connect() — connect() reopens the session and drops earlier handlers
|
|
987
|
+
...
|
|
988
|
+
if host:
|
|
989
|
+
await manipulate.disconnect()
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
The scaffolded `py/main.py` already follows this.
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## 8. Other backend runtimes (C++ / ROS 2)
|
|
997
|
+
|
|
998
|
+
All non-`none` runtimes run in Docker during `dev:back`, `dev` and `build`.
|
|
999
|
+
|
|
1000
|
+
### 8.1 C++ (`cpp`)
|
|
1001
|
+
|
|
1002
|
+
- Source in `cpp/`, CMake build in Docker against the C++ SDK (apt `rainbow-rb-sdk-dev`).
|
|
1003
|
+
- CMake: `find_package(rb_sdk <ver> EXACT REQUIRED)` + `target_link_libraries(<target> rb_sdk::rb_sdk)` (C++20).
|
|
1004
|
+
- Headers: `<rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>`, `<rainbow/rb_sdk/plugin/rb_plugin.hpp>` (`rb_plugin::RBPluginCall`), `<rainbow/rb_sdk/program/plugin_program_context.hpp>` (`rb_plugin::PluginProgramContext`).
|
|
1005
|
+
- Dynamic link; `build` copies `librb_sdk.so` into `dist/<arch>/lib/`.
|
|
1006
|
+
|
|
1007
|
+
Building outside the container (Ubuntu 24.04):
|
|
1008
|
+
|
|
1009
|
+
```bash
|
|
1010
|
+
curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/rb-sdk-apt.gpg
|
|
1011
|
+
echo 'deb [signed-by=/usr/share/keyrings/rb-sdk-apt.gpg] https://rainbowrobotics.github.io/rb-sdk stable main' | sudo tee /etc/apt/sources.list.d/rb-sdk.list
|
|
1012
|
+
sudo apt update && sudo apt install rainbow-rb-sdk-dev
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
### 8.2 ROS 2 (`ros2` + `rclpy` / `rclcpp`)
|
|
1016
|
+
|
|
1017
|
+
- Source in `ros2_ws/src/<package>/`; `runtime.package` = colcon package, `runtime.launch` = node executable.
|
|
1018
|
+
- Relative topic names live under the plugin's namespace: id with `.` → `/`, `-` → `_` (`com.example.my-plugin` → `/com/example/my_plugin`). Use absolute names (`/cmd_vel`) for shared topics.
|
|
1019
|
+
- `RBPluginCall` works in an rclpy node too.
|
|
1020
|
+
- ROS discovery stays on the robot (`ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST`). Allow external PCs from the UI with `rb.rosPeers.set([...])`; Muscat restarts the backend with `ROS_STATIC_PEERS`. In dev the list is in `.muscat-dev/ros-peers.json`.
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## 9. Development environment
|
|
1025
|
+
|
|
1026
|
+
### 9.1 Local Muscat runtime
|
|
1027
|
+
|
|
1028
|
+
Without a robot IP (or against a robot that runs no Muscat), `dev` / `dev:ui` need a Muscat on `localhost:3000` (API) and `:10000` (plugin gateway):
|
|
1029
|
+
|
|
1030
|
+
- reuses any container already publishing `:10000` (`muscat`, `muscat-dev`, …). A container not started by the kit does not publish the plugin's ports;
|
|
1031
|
+
- uses a Muscat already listening on those ports without a container (robot PC, host networking);
|
|
1032
|
+
- otherwise starts `muscat-pk-muscat` — the S3 runtime image (`muscat-pk-runtime:<version>`, from `version.json`) running `common` (plus the services `--sim` needs) and MediaMTX (`8554`, `8889`, `8189`), cached in `~/.muscat-pk/muscat/`. It is recreated when the published plugin ports or services change, and a new Muscat version replaces the old image. Its data (DB, logs, programs) lives in the container only, so a new container starts empty.
|
|
1033
|
+
- `dev` removes its own `muscat-pk-muscat` on exit; started by `dev:ui` alone it keeps running.
|
|
1034
|
+
- The backend joins the local Muscat's network namespace, so `execute` and the SDK reach it like on the robot.
|
|
1035
|
+
|
|
1036
|
+
```bash
|
|
1037
|
+
docker rm -f muscat-pk-muscat # stop it
|
|
1038
|
+
MUSCAT_PK_NO_LOCAL_MUSCAT=1 muscat-pk dev # don't start it
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
Its API docs are at `http://localhost:3000/common/docs`.
|
|
1042
|
+
|
|
1043
|
+
### 9.2 Developing on the robot PC
|
|
1044
|
+
|
|
1045
|
+
On the robot PC Muscat already serves the gateway on `10000`; the CLI uses it and starts no local Muscat.
|
|
1046
|
+
|
|
1047
|
+
### 9.3 Several developers on one robot
|
|
1048
|
+
|
|
1049
|
+
Against a remote robot, `dev` / `dev:back` run the plugin as `<manifest id>.dev-<user>-<random>` (random part kept in `~/.muscat-pk/dev-id`), so two machines don't answer each other's UI. UI, backend, ROS namespace and external routes all use the dev id. `--keep-id` uses the manifest id (for program `execute` or external clients) — then only one machine may connect.
|
|
1050
|
+
|
|
1051
|
+
### 9.4 Local vs global install
|
|
1052
|
+
|
|
1053
|
+
If the project has its own `muscat-plugin-kit` in `node_modules`, any `muscat-pk` hands off to it, so the CLI, `vite` preset and `preview-host` are always the project's pinned version. `muscat-pk version` prints the copy that actually ran.
|
|
1054
|
+
|
|
1055
|
+
### 9.5 Offline / air-gapped
|
|
1056
|
+
|
|
1057
|
+
Docker images are built only when missing or when the kit's Dockerfile / build args changed (a rebuild contacts the registry even when cached).
|
|
1058
|
+
|
|
1059
|
+
```bash
|
|
1060
|
+
docker save muscat-pk-python-dev muscat-pk-runtime | gzip > muscat-pk-images.tar.gz # online machine
|
|
1061
|
+
gunzip -c muscat-pk-images.tar.gz | docker load # offline machine
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
C++ / ROS 2 projects also need `muscat-pk-cpp-dev` / `muscat-pk-ros2-dev` (and the per-arch `-amd64` / `-arm64` images used by `build`). Offline, the local Muscat uses the cached `version.json` and the simulators an already pulled image.
|
|
1065
|
+
|
|
1066
|
+
Force a rebuild with `MUSCAT_PK_REBUILD_IMAGE=1`.
|
|
1067
|
+
|
|
1068
|
+
### 9.6 IDE support (Python)
|
|
1069
|
+
|
|
1070
|
+
`muscat-pk new` creates `.venv` with `requirements.txt` installed — for editor linting only; the backend runs in Docker. Without `uv` or Python 3 it prints `Could not create .venv (no local Python/uv?) — skipping, Docker dev still works.`; install one and re-run `new` or `type change`.
|
|
1071
|
+
|
|
1072
|
+
### 9.7 JavaScript dependencies
|
|
1073
|
+
|
|
1074
|
+
`build` runs the project's own Vite build, so packages must be installed. Don't `npm install --omit=dev` before building — `vite`, `typescript`, `tailwindcss`, `sass`, `less` are devDependencies. The ZIP ships no `node_modules`.
|
|
1075
|
+
|
|
1076
|
+
---
|
|
1077
|
+
|
|
1078
|
+
## 10. Styles
|
|
1079
|
+
|
|
1080
|
+
- `muscat()` auto-loads `ui/style.{css,scss,sass,less}` / `ui/styles.*` for every entry and `ui/{entry}.*` for a matching entry, and injects the CSS into the JS bundle.
|
|
1081
|
+
- Built selectors are scoped under `[data-muscat-plugin-id="..."]`, so plugin CSS can't touch the host or other plugins.
|
|
1082
|
+
- Use design-token classes (`bg-bg-*`, `text-fg-*`, `typo-*`) rather than hex colors.
|
|
1083
|
+
|
|
1084
|
+
Tailwind (default, no preflight):
|
|
1085
|
+
|
|
1086
|
+
```css
|
|
1087
|
+
@layer theme, utilities;
|
|
1088
|
+
@import "tailwindcss/theme.css" layer(theme);
|
|
1089
|
+
@import "tailwindcss/utilities.css" layer(utilities);
|
|
1090
|
+
@source "./**/*.{ts,tsx}";
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
SCSS / Less: `npm i -D sass` (or `less`) and create `ui/style.scss` (`ui/style.less`).
|
|
1094
|
+
|
|
1095
|
+
Emotion `styled` works as is. For the `css` prop:
|
|
1096
|
+
|
|
1097
|
+
```bash
|
|
1098
|
+
npm i @emotion/react @emotion/styled && npm i -D @emotion/babel-plugin
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
```ts
|
|
1102
|
+
muscat({ react: { jsxImportSource: "@emotion/react", babel: { plugins: ["@emotion/babel-plugin"] } } })
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
and `"jsxImportSource": "@emotion/react"` in `tsconfig.json`. Emotion `Global` is not scoped — avoid it.
|
|
1106
|
+
|
|
1107
|
+
---
|
|
1108
|
+
|
|
1109
|
+
## 11. AI coding agent setup (`muscat-pk mcp`)
|
|
1110
|
+
|
|
1111
|
+
An MCP server (stdio) with grounded info on rb-components (props, Storybook examples), design tokens, and the Python `rainbow-rb-sdk` (introspected signatures/docstrings), plus `check_project` for upgrading old projects.
|
|
1112
|
+
|
|
1113
|
+
| Tool | Returns |
|
|
1114
|
+
|---|---|
|
|
1115
|
+
| `list_components`, `get_component_props`, `get_component_example` | plugin-ui components, their prop types, real Storybook usage |
|
|
1116
|
+
| `list_design_tokens`, `search_design_tokens` | design-token Tailwind classes (`bg-bg-*`, `text-fg-*`, `typo-*`) |
|
|
1117
|
+
| `list_rb_sdk`, `search_rb_sdk`, `get_rb_sdk_api` | Python `rainbow-rb-sdk` and C++ SDK classes / signatures / docstrings |
|
|
1118
|
+
| `list_program_commands` | built-in Program page commands (category, `funcName`, default args, nesting rules) for `rb.program.setSteps` or `POST /common/program/steps/upsert` |
|
|
1119
|
+
| `get_program_api` | guide to building/running programs (`rb.program`, `/common/program` REST) plus live endpoint schemas from a running Muscat (default `http://localhost:3000`) |
|
|
1120
|
+
| `check_project` | what an older project must change after `muscat-pk update` |
|
|
1121
|
+
|
|
1122
|
+
Claude Code — `.mcp.json` at the plugin root (written by `new`, or by `update` when missing):
|
|
1123
|
+
|
|
1124
|
+
```json
|
|
1125
|
+
{ "mcpServers": { "muscat": { "command": "muscat-pk", "args": ["mcp"] } } }
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
Codex — `muscat-pk mcp --install codex` (on Windows it writes `muscat-pk.cmd`), or `~/.codex/config.toml`:
|
|
1129
|
+
|
|
1130
|
+
```toml
|
|
1131
|
+
[mcp_servers.muscat]
|
|
1132
|
+
command = "muscat-pk"
|
|
1133
|
+
args = ["mcp"]
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
The agent starts the server itself.
|
|
1137
|
+
|
|
1138
|
+
---
|
|
1139
|
+
|
|
1140
|
+
## 12. Environment variables
|
|
1141
|
+
|
|
1142
|
+
| Variable | Effect |
|
|
1143
|
+
|---|---|
|
|
1144
|
+
| `MUSCAT_ROBOT_HOST` | robot IP for `dev` / `dev:ui` / `dev:back` (skips the prompt) |
|
|
1145
|
+
| `MUSCAT_ROBOT_TOKEN` | service token when the robot's login policy is on |
|
|
1146
|
+
| `MUSCAT_PK_NO_LOCAL_MUSCAT=1` | don't start the local Muscat runtime |
|
|
1147
|
+
| `MUSCAT_PK_REBUILD_IMAGE=1` | rebuild Docker images |
|
|
1148
|
+
| `MUSCAT_PK_RB_SDK_VERSION` | C++ SDK version baked into the C++ images (with `MUSCAT_PK_REBUILD_IMAGE=1`) |
|
|
1149
|
+
| `MUSCAT_PK_NO_BUILD_CACHE=1` | build Docker images with `--no-cache` (CI) |
|
|
1150
|
+
| `MUSCAT_PK_HOST_NETWORK=0` | on native Linux, use the Docker Desktop (bridge) path instead of `--network host` (CI) |
|
|
1151
|
+
| `MUSCAT_PK_RBY1_CPUS` | CPU cap of the rby1 simulator (default `1.5`) |
|
|
1152
|
+
| `MUSCAT_PK_MUSCAT_S3` | where the local Muscat runtime is downloaded from (default `https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat/main`) |
|
|
1153
|
+
| `MUSCAT_HOST_AGENT_DISABLE=1` | don't start the host Zenoh byte-proxy (macOS / Windows / WSL against a remote robot) |
|
|
1154
|
+
| `MUSCAT_PROXY_PORT` | port of that proxy (default `47100`) |
|
|
1155
|
+
| `MUSCAT_ZENOH_ADVERTISE_HOST` / `MUSCAT_ZENOH_PORT` | LAN IP / port the remote robot dials back to (default: the route to the robot / `53917` or the next free port) |
|
|
1156
|
+
| `MUSCAT_PK_NO_UPDATE_CHECK=1` | skip the update check on `dev` / `dev:ui` |
|
|
1157
|
+
| `RB_REGISTRY` | npm registry for the update check (default `https://registry.npmjs.org`) |
|
|
1158
|
+
| `MUSCAT_PK_TAG` | install script channel / version |
|
|
1159
|
+
| `CI` | set: `dev:ui` doesn't open the browser |
|
|
1160
|
+
| `NO_COLOR=1` / `FORCE_COLOR` | output colors |
|
|
1161
|
+
|
|
1162
|
+
Inside the backend container: `RB_PLUGIN_ID` (the plugin / dev id), `RB_PLUGIN_PORTS` (declared `ports`, comma-separated), `MUSCAT_ROBOT_HOST` / `MUSCAT_ROBOT_TOKEN` (remote dev). On the robot Muscat also sets `RB_PLUGIN_DATA_DIR` (see `data_dir`, 7.1).
|
|
1163
|
+
|
|
1164
|
+
---
|
|
1165
|
+
|
|
1166
|
+
## 13. Troubleshooting
|
|
1167
|
+
|
|
1168
|
+
| Symptom | Fix |
|
|
1169
|
+
|---|---|
|
|
1170
|
+
| `manifest.json errors: …` | fix each listed field (section 5) |
|
|
1171
|
+
| `manifest.json variable errors: …` / `manifest.json port errors: …` on `dev` | fix `variables` (5.5) / `ports` (5.6) |
|
|
1172
|
+
| `A Muscat plugin dev environment is already running: …` | answer `y` to stop it, or stop the other `dev` first |
|
|
1173
|
+
| `Docker is installed but the daemon is not running` | start Docker Desktop (or `sudo systemctl start docker`) |
|
|
1174
|
+
| `contributes declares ui "<entry>" but ui/<entry>.tsx … does not exist.` | add the file or fix `contributes[].ui` |
|
|
1175
|
+
| `wheel download failed for <arch>` | a dependency has no cp312 binary wheel for that arch — pick another version or package |
|
|
1176
|
+
| `can't build <arch> on this <host>: Docker has no <arch> emulation.` | `docker run --privileged --rm tonistiigi/binfmt --install <arch>`, or `--arch <host arch>` |
|
|
1177
|
+
| `Permission denied when deleting dist/` | an old build left root-owned files: `sudo rm -rf dist` once |
|
|
1178
|
+
| backend stops with an undeclared port | add the port (28600-28999) to `ports` |
|
|
1179
|
+
| UI preview says "no default export" | entry must `export default App`; `vite.config.ts` must use `muscat()`; run `check_project` via `muscat-pk mcp` |
|
|
1180
|
+
| `vite not found — UI will not load` | run `npm install` in the project |
|
|
1181
|
+
| `AttributeError: router` | robot SDK older than 0.0.9.dev5 |
|
|
1182
|
+
| `RBPluginGatewayError` from `plugin.call` | the gateway is disconnected, or no reply within `timeout` (is a UI `rb.serve` registered?) |
|
|
1183
|
+
| step never finishes | `on_execute` must `await ctx.done()` |
|
|
1184
|
+
| Stop doesn't stop the plugin | call `await ctx.check_stop()` in every loop |
|
|
1185
|
+
| `RuntimeError` on `set_variable` | new variable names need `RBP_` |
|
|
1186
|
+
| `get_variable` returns `None` | no program running (or `IS_DEV=true` for executor state) |
|
|
1187
|
+
| external call 504 after restart | wait up to a minute for the gateway to drop the old socket |
|
|
1188
|
+
| a newer/older local install answers | expected — the project's local kit wins; `muscat-pk update` |
|