@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.ko.md
ADDED
|
@@ -0,0 +1,1188 @@
|
|
|
1
|
+
# @rainbow-robotics/muscat-plugin-kit
|
|
2
|
+
|
|
3
|
+
Muscat 플러그인 개발 CLI(`muscat-pk`)입니다. 프로젝트 생성, 개발 서버(UI + 백엔드), 빌드/패키징, 그리고 플러그인 작성에 필요한 SDK 사용법을 다룹니다.
|
|
4
|
+
|
|
5
|
+
[English (README.md)](./README.md)
|
|
6
|
+
|
|
7
|
+
## 목차
|
|
8
|
+
|
|
9
|
+
1. [설치](#1-설치)
|
|
10
|
+
2. [빠른 시작](#2-빠른-시작)
|
|
11
|
+
3. [명령어](#3-명령어)
|
|
12
|
+
4. [프로젝트 구조](#4-프로젝트-구조)
|
|
13
|
+
5. [manifest.json](#5-manifestjson)
|
|
14
|
+
6. [플러그인 UI SDK (`useRb`)](#6-플러그인-ui-sdk-userb)
|
|
15
|
+
7. [Python 플러그인 SDK](#7-python-플러그인-sdk)
|
|
16
|
+
8. [다른 백엔드 런타임 (C++ / ROS 2)](#8-다른-백엔드-런타임-c--ros-2)
|
|
17
|
+
9. [개발 환경](#9-개발-환경)
|
|
18
|
+
10. [스타일](#10-스타일)
|
|
19
|
+
11. [AI 코딩 에이전트 설정 (`muscat-pk mcp`)](#11-ai-코딩-에이전트-설정-muscat-pk-mcp)
|
|
20
|
+
12. [환경 변수](#12-환경-변수)
|
|
21
|
+
13. [문제 해결](#13-문제-해결)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. 설치
|
|
26
|
+
|
|
27
|
+
### npm (Node.js 20 이상)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit # 최신 stable
|
|
31
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit@dev # dev 채널
|
|
32
|
+
npm install -g @rainbow-robotics/muscat-plugin-kit@1.2.3 # 특정 버전
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 설치 스크립트 (Node.js 없어도 됨)
|
|
36
|
+
|
|
37
|
+
macOS / Linux / WSL:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
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
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Windows PowerShell:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
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
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Node.js 20 이상이 있으면 npm으로 설치하고, 없으면 단일 실행 바이너리를 내려받습니다.
|
|
53
|
+
|
|
54
|
+
### 바이너리 직접 다운로드
|
|
55
|
+
|
|
56
|
+
```
|
|
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}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| OS | 파일 |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Linux x64 | `muscat-pk-linux-x64` |
|
|
65
|
+
| macOS ARM64 | `muscat-pk-darwin-arm64` |
|
|
66
|
+
| macOS x64 | `muscat-pk-darwin-x64` |
|
|
67
|
+
| Windows x64 | `muscat-pk-win-x64.exe` |
|
|
68
|
+
|
|
69
|
+
채널별 최신 버전: `.../muscat-plugin-kit/latest-version.txt` (stable), `.../muscat-plugin-kit/dev-version.txt` (dev).
|
|
70
|
+
|
|
71
|
+
### 필요한 도구
|
|
72
|
+
|
|
73
|
+
| 도구 | 용도 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Node.js 20 이상 (또는 바이너리) | CLI, UI 빌드 |
|
|
76
|
+
| Docker | `none`을 제외한 모든 백엔드 런타임 (`dev:back`, `dev`, `build`) |
|
|
77
|
+
| `uv` 또는 Python 3.12 (선택) | Python 린트용 IDE 전용 `.venv` |
|
|
78
|
+
|
|
79
|
+
### 삭제
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
muscat-pk uninstall # npm·바이너리 설치 모두 가능
|
|
83
|
+
npm uninstall -g @rainbow-robotics/muscat-plugin-kit # 수동, npm
|
|
84
|
+
sudo rm -f /usr/local/bin/muscat-pk # 수동, 바이너리
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 2. 빠른 시작
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
muscat-pk new my-plugin # 프로젝트 생성 (작성자, 이메일, 설명, 타입, 패키지 매니저를 물어봄)
|
|
93
|
+
cd my-plugin
|
|
94
|
+
muscat-pk dev # UI 프리뷰 + 백엔드 동시 실행
|
|
95
|
+
muscat-pk build # dist/ + 배포용 ZIP
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
만들어진 ZIP은 로봇 Muscat의 플러그인 페이지에서 설치합니다.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 3. 명령어
|
|
103
|
+
|
|
104
|
+
| 명령 | 설명 |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `muscat-pk new [dir]` | 플러그인 프로젝트 생성 |
|
|
107
|
+
| `muscat-pk type change` | 백엔드 타입 변경 |
|
|
108
|
+
| `muscat-pk dev` | 로컬 Muscat 런타임 + `dev:ui` + `dev:back` |
|
|
109
|
+
| `muscat-pk dev:ui` | mini-Muscat(preview-host)에서 UI 프리뷰 |
|
|
110
|
+
| `muscat-pk dev:back` | 백엔드만 Docker에서 실행 (hot reload) |
|
|
111
|
+
| `muscat-pk build` | 빌드, Python 의존성 lock, wheel 동봉, ZIP |
|
|
112
|
+
| `muscat-pk update` | kit, `plugin-ui`, `preview-host` 업데이트 |
|
|
113
|
+
| `muscat-pk sdk <list\|update [version]>` | `rainbow-rb-sdk` 버전 관리 |
|
|
114
|
+
| `muscat-pk sim list` | `dev --sim`에 쓸 수 있는 로봇 시뮬레이터 목록 |
|
|
115
|
+
| `muscat-pk storybook` | 번들된 rb-components Storybook 보기 |
|
|
116
|
+
| `muscat-pk mcp` | AI 코딩 에이전트용 MCP 서버 |
|
|
117
|
+
| `muscat-pk uninstall` | CLI 삭제 |
|
|
118
|
+
| `muscat-pk version` | 실행 중인 버전 출력 (`-v`, `--version`) |
|
|
119
|
+
| `muscat-pk completion [shell]` | 셸 자동완성 스크립트 출력 |
|
|
120
|
+
|
|
121
|
+
`muscat-pk <command> --help`로 각 명령의 옵션을 볼 수 있습니다.
|
|
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
|
+
| 옵션 | 의미 |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `dir` | 생성할 디렉터리 (비어 있어야 함). 생략하면 물어봄 |
|
|
134
|
+
| `--type` | 백엔드 타입: `python` (기본), `cpp`, `ros2` (rclpy), `ros2-cpp` (rclcpp) |
|
|
135
|
+
| `--pm` | 프론트 패키지 매니저. Yarn 워크스페이스 안이면 기본 `yarn`, 아니면 `npm` |
|
|
136
|
+
| `--author` / `--email` / `--description` | manifest 필드 (email은 manifest 필수값) |
|
|
137
|
+
| `--yes` | 질문 없이 진행. 지정 안 한 값은 기본값 |
|
|
138
|
+
| `--no-install` | `<pm> install`, IDE `.venv`, C++ dev 이미지 준비를 생략 |
|
|
139
|
+
| `--help` | 사용법 출력 |
|
|
140
|
+
|
|
141
|
+
플러그인 id는 `com.example.<dir>`(소문자)로 만들어지며 `manifest.json`에서 바꿉니다. `new`는 `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `.mcp.json`(11장 참고)도 만들고, `git init` 후 첫 커밋을 남깁니다 (원치 않으면 `.git`을 지우세요).
|
|
142
|
+
|
|
143
|
+
`--no-install`이 아니면:
|
|
144
|
+
|
|
145
|
+
- python / ros2 (rclpy): IDE 전용 `.venv`를 만듭니다 (9.6 참고).
|
|
146
|
+
- cpp / ros2-cpp: 에디터가 SDK 헤더를 찾도록 C++ dev 이미지를 빌드합니다 (Docker가 없으면 경고 후 건너뜀).
|
|
147
|
+
|
|
148
|
+
### 3.2 `type change`
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
muscat-pk type change [--to python|cpp|ros2|ros2-cpp] [--readme]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| 옵션 | 의미 |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `--to` | 새 타입. 생략하면 번호로 물어봄 |
|
|
157
|
+
| `--readme` | 묻지 않고 `README.md`를 새 타입용으로 교체 (기존 파일 → `README.md.bak`) |
|
|
158
|
+
|
|
159
|
+
`manifest.json`의 `runtime`을 바꾸고, 해당 디렉터리가 없으면 새 백엔드 파일(`cpp/` 또는 `ros2_ws/`)을 추가합니다. 이전 타입의 파일(`cpp/`, `ros2_ws/` 등)은 지우지 않습니다. Python ↔ C++ 백엔드를 오갈 때는 README 교체를 물어봅니다 (`--readme`면 예로 처리). C++ 타입으로 바꾸면 `new`처럼 C++ dev 이미지를 준비합니다.
|
|
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
|
+
| 명령 | 실행되는 것 |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `dev` | 먼저 dev 이미지와 백엔드를 빌드하고(빌드 실패는 경고만), 로봇이 없거나 로봇에 Muscat이 없으면 로컬 Muscat 런타임, 그리고 `dev:ui` + `dev:back` |
|
|
173
|
+
| `dev:ui` | 플러그인 Vite 개발 서버 + preview-host `http://localhost:4180` (사용 중이면 다음 빈 포트). 브라우저로 엶 |
|
|
174
|
+
| `dev:back` | 로봇 환경과 같은 Docker 컨테이너에서 백엔드 실행. Python은 `py/**/*.py`가 바뀌면 재시작 (watchfiles) |
|
|
175
|
+
|
|
176
|
+
| 옵션 | 의미 |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `--robot-ip` | 개발 대상 로봇. 없으면 `MUSCAT_ROBOT_HOST`, 그것도 없으면 프롬프트 (비우면 로봇 없음) |
|
|
179
|
+
| `--robot-token` | 로그인 정책이 켜진 로봇의 service token. 없으면 `MUSCAT_ROBOT_TOKEN`, 그것도 없으면 프롬프트 (비우면 없음) |
|
|
180
|
+
| `--keep-id` | dev id 대신 manifest id로 실행 (9.3 참고) |
|
|
181
|
+
| `--sim` | `dev` 전용, 로봇 없음: 로봇 시뮬레이터 실행 (아래). 로봇 IP 프롬프트를 건너뜀 |
|
|
182
|
+
| `--port` | preview-host 포트 (기본 `4180`, 사용 중이면 다음 빈 포트) |
|
|
183
|
+
| `--no-open` | 브라우저를 열지 않음 (CI이거나 TTY가 아니어도 열지 않음) |
|
|
184
|
+
|
|
185
|
+
- dev 환경은 PC당 하나입니다. 백엔드 컨테이너, 로컬 Muscat과 그 포트를 같이 쓰기 때문입니다. 두 번째 `dev` / `dev:ui` / `dev:back`은 실행 중인 것을 멈출지 묻습니다 (`Stop it and start this one? [y/N]`).
|
|
186
|
+
- 원격 로봇에 붙으면 플러그인이 dev id로 실행됩니다 (9.3 참고). `--keep-id`는 manifest id를 그대로 씁니다.
|
|
187
|
+
- 시작 전에 `dev`와 `dev:back`이 `manifest.json`의 `variables`, `ports`를 검사합니다 (오류면 중단). `dev:back`은 나머지 manifest 오류도 경고로 출력합니다. 전체 검증은 `build`에서 합니다. 선언하지 않은 포트를 열면 백엔드를 멈춥니다 (5.6 참고).
|
|
188
|
+
- Muscat이 도는 로봇에 붙으면 UI가 그 로봇의 Muscat 화면이 되고 백엔드는 그 gateway를 씁니다. 로컬에 `muscat-dev` 컨테이너가 있거나 `--sim`이면 로컬 실제 Muscat 화면, 아니면 mock 프리뷰입니다.
|
|
189
|
+
- `dev` / `dev:ui`는 새 kit이 배포돼 있으면 알려 줍니다 (`MUSCAT_PK_NO_UPDATE_CHECK=1`로 생략).
|
|
190
|
+
- `dev`를 멈추면(Ctrl+C) 백엔드 컨테이너와, 자신이 띄운 로컬 Muscat·시뮬레이터를 지웁니다.
|
|
191
|
+
|
|
192
|
+
#### 로봇 없이 개발 (`--sim`)
|
|
193
|
+
|
|
194
|
+
로봇 IP 없이 `dev --sim <기종>`을 실행하면 로컬 Muscat 옆에 로봇 시뮬레이터를 같이 띄웁니다. 플러그인 SDK의 로봇 호출이 실제 coreSW에 닿습니다.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
muscat-pk sim list # 한 줄에 "<name>\t<실행되는 것>"
|
|
198
|
+
muscat-pk dev --sim 500920
|
|
199
|
+
muscat-pk dev --sim rby1-m_v1.2
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
| `--sim` | 함께 뜨는 Muscat 서비스 | 시뮬레이터 |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `500920` | common, log, manipulate | `rainbowroboticsofficial/cobot-sim` 1개 |
|
|
205
|
+
| `RBY2` | common, log, manipulate | cobot-sim 3개 (C500880, C501880, C502880) |
|
|
206
|
+
| `rby1-a`, `rby1-m`, `rby1-ub` (`rby1-m_v1.2`처럼 버전 지정 가능) | common, log, rby1 | `rainbowroboticsofficial/rby1-sim` + Xvfb (로컬 빌드) |
|
|
207
|
+
|
|
208
|
+
- 시뮬레이터 기종이 로컬 Muscat의 로봇 모델로 등록됩니다(`500920`은 C500920, `rby1-m`은 m). UI는 mock 프리뷰가 아니라 실제 Muscat 화면(대시보드, 컴포넌트, Move, Log, Settings)으로 뜹니다.
|
|
209
|
+
- 시뮬레이터는 `--sim`을 줄 때만 뜹니다. 그 밖의 값이면 `--sim takes one of: 500920, RBY2, rby1-a, rby1-m, rby1-ub (rby1 also takes rby1-m_v1.2)` 오류가 납니다. manifest의 `targetModels`에 없는 기종이면 경고만 합니다.
|
|
210
|
+
- 시뮬레이터 이미지는 Docker Hub의 최신 태그를 씁니다 (rby1에 버전을 주면 그 버전 태그). 오프라인이면 이미 받아 둔 이미지를 씁니다.
|
|
211
|
+
- rby1-sim은 화면을 계속 렌더링하므로 CPU를 1코어 정도 씁니다. 상한은 `MUSCAT_PK_RBY1_CPUS`(기본 1.5)입니다.
|
|
212
|
+
- `muscat-dev` 같은 다른 Muscat이 떠 있으면 시뮬레이터를 붙이지 않습니다.
|
|
213
|
+
- amr 시뮬레이터는 아직 없습니다.
|
|
214
|
+
- 로컬 Muscat, 시뮬레이터, 백엔드는 로봇처럼 모두 `--ipc host`로 돕니다. 따라서 zenoh SHM과 `shm_open`이 서로 통합니다.
|
|
215
|
+
|
|
216
|
+
### 3.4 `build`
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
muscat-pk build [--arch amd64|arm64|both]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
순서:
|
|
223
|
+
|
|
224
|
+
1. `manifest.json`의 새 버전을 물어봅니다 (비우면 그대로, TTY일 때만).
|
|
225
|
+
2. 대상 아키텍처를 묻고(`--arch`가 없을 때) 같은 버전의 기존 ZIP을 지웁니다. 빌드가 실패해도 예전 ZIP이 남지 않습니다.
|
|
226
|
+
3. manifest를 검증하고(5장 전체), `contributes[].ui`마다 `ui/<entry>.tsx`(또는 `.ts` / `.jsx` / `.js`)가 있는지 확인합니다.
|
|
227
|
+
4. `contributes[].ui` 엔트리마다 `vite build` → `dist/<entry>.js`. `contributes`가 없는 플러그인은 UI를 빌드하지 않습니다. Node.js 20 이상이 없으면(바이너리 CLI) `Build the plugin without its UI? [y/N]`를 묻고, 예면 `dist/manifest.json`에서 `contributes`를 뺍니다.
|
|
228
|
+
5. `manifest.json`, `assets/`, `README.md`, `README.<lang>.md`, `CHANGELOG.md`, `py/`, `requirements.txt`를 `dist/`에 복사합니다.
|
|
229
|
+
6. 백엔드:
|
|
230
|
+
- python / ros2 (rclpy): `requirements.txt`를 `dist/requirements.lock`으로 해석하고(`uv pip compile`, Python 3.12, 고정된 줄 중 pre-release가 있으면 pre-release 허용), arch별 바이너리 wheel(cp312, `--only-binary=:all:`)을 `dist/wheels/`에 받습니다. 맞는 wheel이 없는 패키지가 있으면 빌드가 멈춥니다.
|
|
231
|
+
- cpp: Docker에서 CMake Release 빌드 → `dist/<arch>/<entry>` + `dist/<arch>/lib/` (`librb_sdk.so`). CMake 타깃 이름이 `runtime.entry`와 같아야 합니다.
|
|
232
|
+
- ros2: Docker에서 colcon 빌드 → `dist/ros2_ws/install/` (rclpy, 한 번) 또는 `dist/ros2_ws/install-<arch>/` (rclcpp, arch별).
|
|
233
|
+
7. `installedSize`(`wheels/`를 뺀 `dist/` 바이트 수)를 `manifest.json`과 `dist/manifest.json`에 씁니다.
|
|
234
|
+
8. `dist/`를 프로젝트 루트의 `<프로젝트 디렉터리>-<version>.zip`으로 묶습니다.
|
|
235
|
+
|
|
236
|
+
| 옵션 | 의미 |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `--arch` | `amd64`(`x86_64`), `arm64`(`aarch64`), `both`. TTY면 물어보고(기본 both), 아니면 `both` |
|
|
239
|
+
|
|
240
|
+
`none`을 제외한 모든 런타임은 Docker가 필요합니다. 다른 arch를 빌드하려면 Docker 에뮬레이션이 필요하며, 네이티브 Linux에서는 `docker run --privileged --rm tonistiigi/binfmt --install <arch>`로 한 번 켜 둡니다.
|
|
241
|
+
|
|
242
|
+
### 3.5 `update`
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
muscat-pk update # 현재 프로젝트만 (로컬 설치가 있을 때)
|
|
246
|
+
muscat-pk update --global # 현재 프로젝트 + 전역 CLI
|
|
247
|
+
muscat-pk update dev # dev 채널 따라가기
|
|
248
|
+
muscat-pk update 1.2.3 # 특정 버전 (--tag <tag> / --version <v>도 가능)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| 옵션 | 의미 |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `[tag]` | npm dist-tag 또는 버전 (`latest`, `dev`, `1.2.3`). 생략하면 `latest`. dev 빌드에서는 dev 유지(기본)와 stable 전환 중 물어봄 |
|
|
254
|
+
| `--global` | 프로젝트 로컬 설치에서 실행해도 전역 CLI까지 업데이트 |
|
|
255
|
+
|
|
256
|
+
| 실행 위치 | 업데이트 대상 |
|
|
257
|
+
|---|---|
|
|
258
|
+
| 로컬 설치가 있는 프로젝트 안 | 그 프로젝트의 `muscat-plugin-kit` + `plugin-ui` (+ `package.json`에 있으면 `preview-host`) |
|
|
259
|
+
| 위와 같고 `--global` | 위 + 전역 CLI |
|
|
260
|
+
| 그 밖 | 전역 CLI (npm `-g` 또는 S3 바이너리), 프로젝트 안이면 로컬 의존성도 |
|
|
261
|
+
|
|
262
|
+
- 프로젝트 로컬 설치가 실행한 CLI보다 오래됐으면, 실행한 CLI가 대신 프로젝트를 업데이트합니다 (프로젝트만).
|
|
263
|
+
- 패키지는 원래 있던 `dependencies` / `devDependencies` 구역에 그대로 둡니다. 패키지 매니저는 `packageManager` 필드나 lock 파일로 판단합니다.
|
|
264
|
+
- 예전 프로젝트 구조는 `[y/N]` 확인 후 옮깁니다: `src/` → `ui/` (+ `vite.config.ts`, `tsconfig.json`), `main.py` → `py/main.py`, `backend/` → `py/`.
|
|
265
|
+
- `.mcp.json`, `AGENTS.md`, `CLAUDE.md`가 없으면 추가하고, 있으면 그대로 둡니다.
|
|
266
|
+
- Windows에서는 새 바이너리가 다음 실행 때 적용됩니다.
|
|
267
|
+
|
|
268
|
+
업데이트 후 새 `plugin-ui`에 맞춰 고쳐야 할 코드를 목록으로 보여 줍니다 (``This project needs changes for the updated plugin-ui. Fix these before `muscat-pk dev` / `build`:``). 목록대로 고치거나 `muscat-pk mcp`의 `check_project`로 확인하세요.
|
|
269
|
+
|
|
270
|
+
### 3.6 `sdk`
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
muscat-pk sdk list # 최신 배포 버전 12개
|
|
274
|
+
muscat-pk sdk update [version] # 생략하면 최신 stable
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
| 백엔드 | `list` 출처 | `update`가 바꾸는 것 |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| python, ros2 (rclpy) | PyPI `rainbow-rb-sdk` | `requirements.txt`의 `rainbow-rb-*==` 핀을 모두 같은 버전으로 (서로 정확히 같은 버전을 요구함). 없으면 `rainbow-rb-sdk==<ver>` 추가. `.venv`가 있으면 갱신하고, dev 컨테이너는 다음 `dev` / `dev:back`에서 설치 |
|
|
280
|
+
| C++ (`cpp`, `ros2-cpp`) | apt `rainbow-rb-sdk-dev` (stable, 숫자 버전만) | `cpp/CMakeLists.txt` / `ros2_ws/src/*/CMakeLists.txt`의 `find_package(rb_sdk <ver> EXACT REQUIRED)`. dev 이미지는 다음 `build` / `dev:back`에서 그 버전으로 다시 빌드 |
|
|
281
|
+
|
|
282
|
+
C++ 버전은 숫자와 점만 됩니다 (`"<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
|
+
| 명령 / 옵션 | 의미 |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `storybook --port` | 서비스 포트 (기본 `6007`). 사용 중이면 `port <n> is in use — pass a free one with --port <n>.` |
|
|
297
|
+
| `storybook --rebuild` | Storybook을 다시 빌드 (repo 체크아웃에서만. 배포된 kit에는 빌드된 것이 들어 있음) |
|
|
298
|
+
| `mcp` | stdio MCP 서버 (11장). 실제 서버(`mcp --serve`)로 중계하고 kit이 업데이트되면 서버를 재시작하므로, 실행 중인 에이전트 세션도 새 도구를 씁니다 |
|
|
299
|
+
| `mcp --install codex` | `~/.codex/config.toml`에 서버 등록 (이미 있으면 그대로) |
|
|
300
|
+
| `version` | 실제로 실행된 쪽의 버전 (`-v`, `--version`도 같음) |
|
|
301
|
+
| `uninstall` | npm 전역: `npm uninstall -g`, 바이너리: 실행 파일 삭제. 프로젝트 로컬 설치는 프로젝트 패키지 매니저로 지웁니다 |
|
|
302
|
+
|
|
303
|
+
`completion`은 셸 Tab 자동완성 스크립트를 출력합니다. 한 번 등록하면 `muscat-pk d<Tab>`에서 `dev`, `dev:ui`, `dev:back`이 후보로 나옵니다. 스크립트를 출력만 하므로 아래처럼 셸 rc 파일에 붙여 넣어야 합니다. `[shell]`은 `bash`, `zsh`, `fish`, `powershell` 중 하나이고 생략하면 `$SHELL`을 따릅니다 (없으면 Windows는 `powershell`, 그 밖은 `bash`). Windows `cmd.exe`는 자동완성을 지원하지 않으니 PowerShell, Git Bash, WSL을 쓰세요. 편의 기능이라 쓰지 않아도 됩니다.
|
|
304
|
+
|
|
305
|
+
| OS / 셸 | 자동완성 설정 |
|
|
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. 프로젝트 구조
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
my-plugin/
|
|
318
|
+
├─ ui/
|
|
319
|
+
│ ├─ content.tsx # UI 엔트리 (contributes[].ui 하나당 파일 하나), `export default App`
|
|
320
|
+
│ └─ style.css # 모든 엔트리에 자동 로드
|
|
321
|
+
├─ py/main.py # python 백엔드 엔트리 (runtime.entry)
|
|
322
|
+
├─ cpp/ # cpp 백엔드 (CMake) — cpp 타입
|
|
323
|
+
├─ ros2_ws/ # colcon 워크스페이스 — ros2 / ros2-cpp 타입
|
|
324
|
+
├─ assets/icon.svg
|
|
325
|
+
├─ manifest.json
|
|
326
|
+
├─ requirements.txt # python 의존성 (빌드 시 dist/requirements.lock으로 고정)
|
|
327
|
+
├─ package.json # UI 의존성 (배포물에 포함 안 됨)
|
|
328
|
+
├─ vite.config.ts # muscat() 프리셋 필수
|
|
329
|
+
├─ tsconfig.json
|
|
330
|
+
├─ AGENTS.md, CLAUDE.md # AI 코딩 에이전트 규칙 (CLAUDE.md → @AGENTS.md)
|
|
331
|
+
├─ .mcp.json # Claude Code용 `muscat-pk mcp` 등록
|
|
332
|
+
├─ .gitignore
|
|
333
|
+
└─ README.md # dist/에 복사됨 (README.<lang>.md, CHANGELOG.md도)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
스캐폴드된 `requirements.txt`는 `rainbow-rb-sdk`, `rainbow-rb-flat-buffers`, `rainbow-rb-zenoh`를 같은 버전으로 고정합니다 (`muscat-pk sdk update`로 변경). 생성물이라 커밋하지 않는 것: `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
|
+
ZIP에는 플러그인 자체 코드와 에셋만 들어갑니다. `@rainbow-robotics/plugin-ui`, `react`, `react-dom`은 호스트의 import map이 제공합니다.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 5. manifest.json
|
|
353
|
+
|
|
354
|
+
전체 검증은 `muscat-pk build`가 합니다 (오류: `manifest.json errors: …`). `dev` / `dev:back`은 시작 전에 `variables`와 `ports`를 검사하고, `dev:back`은 나머지를 경고로 출력합니다.
|
|
355
|
+
|
|
356
|
+
### 5.1 전체 예시
|
|
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", "ko": "내 플러그인" }, "ui": "content", "opens": "popover" }
|
|
373
|
+
],
|
|
374
|
+
"variables": [
|
|
375
|
+
{ "name": "RBP_grip_force", "type": "number", "init": 0.5, "description": { "en": "Gripper force (0–1)", "ko": "그리퍼 힘 (0~1)" } }
|
|
376
|
+
],
|
|
377
|
+
"ports": [28600],
|
|
378
|
+
"runtime": { "type": "python", "entry": "main.py" }
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 5.2 최상위 필드
|
|
383
|
+
|
|
384
|
+
| 필드 | 필수 | 규칙 |
|
|
385
|
+
|---|---|---|
|
|
386
|
+
| `id` | 예 | 역도메인 형식: 소문자·숫자로 된 부분을 `.`으로 잇고, 부분 안에서는 `-` 하나로만 연결, 각 부분은 문자로 시작 (`com.example.my-plugin`). `_` 불가 |
|
|
387
|
+
| `name`, `version`, `minMuscatVersion` | 예 | 빈 문자열 불가 |
|
|
388
|
+
| `author`, `email` | 예 | 빈 문자열 불가 |
|
|
389
|
+
| `company` | 아니오 | 문자열 |
|
|
390
|
+
| `description` | 예 | 문자열 또는 다국어 객체 |
|
|
391
|
+
| `icons` | 예 | SVG 경로 문자열, 또는 PNG/JPG면 `{ "16", "48", "128" }` (세 개 모두) |
|
|
392
|
+
| `targetModels` / `targetCategorys` | 둘 중 하나 이상 | `"all"` 또는 비어 있지 않은 문자열 배열. 카테고리: `manipulate`, `amr`, `rby1`, `vision` |
|
|
393
|
+
| `contributes` | 아니오 | 5.3. 없거나 `[]`이면 백엔드 전용 플러그인 (화면 없음, UI 빌드 없음) |
|
|
394
|
+
| `variables` | 아니오 | 5.5 |
|
|
395
|
+
| `ports` | 아니오 | 5.6 |
|
|
396
|
+
| `runtime` | 예 | 5.7 |
|
|
397
|
+
| `installedSize` | — | `build`가 기록 (바이트) |
|
|
398
|
+
|
|
399
|
+
다국어 객체: `en` 필수, 허용 키 `ko`, `en`, `ja`, `zh`, `vi`, `th`, `es`.
|
|
400
|
+
|
|
401
|
+
### 5.3 `contributes`
|
|
402
|
+
|
|
403
|
+
| 필드 | 규칙 |
|
|
404
|
+
|---|---|
|
|
405
|
+
| `point` | `toolbar`, `lnb`, `move.panel`, `program.panel`, `program.monitor` |
|
|
406
|
+
| `ui` | `ui/` 아래 엔트리 이름, 확장자 없이 (기본 `content`). 엔트리끼리 중복 불가 |
|
|
407
|
+
| `label` | 필수. 문자열 또는 다국어 객체 |
|
|
408
|
+
| `icon` | 아이콘 경로 |
|
|
409
|
+
| `opens` | toolbar 전용: `popover`, `panel`, `page` |
|
|
410
|
+
| `commandInfo` | `program.panel`이면 필수 (5.4) |
|
|
411
|
+
|
|
412
|
+
`contributes`가 비어 있지 않으면 `toolbar` 항목이 최소 하나 있어야 합니다 (호스트가 플러그인을 표시하는 곳). 각 `ui`에는 같은 이름의 `ui/<entry>.tsx`(또는 `.ts` / `.jsx` / `.js`)가 있어야 하며 `build`에서 확인합니다.
|
|
413
|
+
|
|
414
|
+
| point | 표시 위치 |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `toolbar` | 헤더 툴바 버튼. popover / panel / page로 열림 |
|
|
417
|
+
| `lnb` | 왼쪽 내비게이션 |
|
|
418
|
+
| `move.panel` | Move 페이지 패널 |
|
|
419
|
+
| `program.panel` | Program 페이지 스텝 트리에 추가되는 커스텀 명령. `ui`는 그 스텝의 편집 폼 |
|
|
420
|
+
| `program.monitor` | Program 페이지 모니터 패널의 탭 (General / Digital I/O / Monitor / Log 옆). task별로 렌더링 |
|
|
421
|
+
|
|
422
|
+
### 5.4 `commandInfo` (`program.panel`)
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"point": "program.panel",
|
|
427
|
+
"ui": "program",
|
|
428
|
+
"label": { "en": "My Step", "ko": "내 스텝" },
|
|
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
|
+
| 필드 | 타입 | 의미 |
|
|
439
|
+
|---|---|---|
|
|
440
|
+
| `name` | string, 필수 | 트리에 표시되는 명령 이름 |
|
|
441
|
+
| `icon` | string, 필수 | 아이콘 경로 |
|
|
442
|
+
| `iconClassName` | string | 아이콘 추가 class |
|
|
443
|
+
| `funcName` | string | 실행기가 호출하는 함수 (Python 백엔드는 `rb_program_sdk.plugin_execute`) |
|
|
444
|
+
| `args` | object | 스텝 기본 인자. `plugin_id`와 함께 백엔드로 전달 |
|
|
445
|
+
| `disabled`, `syncGroupId`, `disableCopy`, `disableDelete`, `requireChildren`, `requireParent` | boolean | 트리 편집 규칙 |
|
|
446
|
+
| `allowChildren`, `allowParents`, `notAllowChildren` | string[] | 허용/금지되는 부모·자식 명령 |
|
|
447
|
+
| `childrenCommand` | string | 자식으로 자동 생성할 명령 |
|
|
448
|
+
|
|
449
|
+
### 5.5 `variables` (PyFM)
|
|
450
|
+
|
|
451
|
+
플러그인이 PyFM 프로그램에 노출할 변수입니다. Program 페이지의 변수 선택기에는 여기 선언한 변수만 나옵니다.
|
|
452
|
+
|
|
453
|
+
```json
|
|
454
|
+
{ "name": "RBP_target_pose", "type": "array", "init": [0, 0, 0], "description": { "en": "Target pose", "ko": "목표 자세" } }
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
| 필드 | 필수 | 규칙 |
|
|
458
|
+
|---|---|---|
|
|
459
|
+
| `name` | 예 | `RBP_` + 영문자/숫자/밑줄. 플러그인 안에서 중복 불가 |
|
|
460
|
+
| `type` | 예 | `number`, `string`, `boolean`, `array` |
|
|
461
|
+
| `init` | 예 | `type`에 맞는 초기값 |
|
|
462
|
+
| `description` | 아니오 | 문자열 또는 다국어 객체. 없으면 UI에 설명이 안 나오므로 적는 것을 권장 |
|
|
463
|
+
|
|
464
|
+
### 5.6 `ports`
|
|
465
|
+
|
|
466
|
+
TCP 포트를 여는 백엔드는 모든 포트를 선언해야 합니다:
|
|
467
|
+
|
|
468
|
+
```json
|
|
469
|
+
{ "ports": [28600, 28601] }
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
| 규칙 | 검사 주체 |
|
|
473
|
+
|---|---|
|
|
474
|
+
| `28600-28999`만, `number[]`, 있으면 비어 있으면 안 됨, 중복 불가 | manifest 검증 |
|
|
475
|
+
| 예약 포트 금지: `502`, `2222`, `4840`, `5170-5179`, `8000-8999`, `10000`, `30004`, `34964`, `44818`, `50000-50005`, `50051` | manifest 검증 |
|
|
476
|
+
| 열린 포트는 전부 선언돼야 함 | `dev` / `dev:back` 런타임 모니터가 선언 안 된 포트 발견 즉시 백엔드 중지 (`backend opened undeclared TCP port(s): …`) |
|
|
477
|
+
| 활성 플러그인끼리 포트 중복 불가 | 로봇, 설치 시 (HTTP 409) |
|
|
478
|
+
|
|
479
|
+
- kit 자체 개발용 포트(gateway `10000`, MediaMTX `8554` / `8889` / `8189`, Zenoh dev peer 포트)와 OS가 배정한 loopback 포트는 검사에서 제외됩니다.
|
|
480
|
+
- 백엔드가 다른 서비스와 network namespace를 같이 쓰면 플러그인 포트를 구분할 수 없어서, 네이티브 Linux(`--network host`)와 백엔드가 로컬 Muscat 컨테이너에 붙는 경우(로봇 없는 일반적인 `dev`)에는 모니터가 꺼집니다. 그래도 선언하세요 — 로봇이 설치 시 확인합니다.
|
|
481
|
+
- 선언한 포트는 로봇처럼 LAN의 장비가 접근할 수 있게 호스트의 모든 인터페이스에 TCP·UDP로 노출되며(`dev`에서는 로컬 Muscat에서), 호스트에서 이미 쓰는 포트는 경고 후 건너뜁니다. 백엔드는 이 목록을 `RB_PLUGIN_PORTS`로 받습니다.
|
|
482
|
+
|
|
483
|
+
### 5.7 `runtime`
|
|
484
|
+
|
|
485
|
+
| runtime | 소스 | 필수 필드 |
|
|
486
|
+
|---|---|---|
|
|
487
|
+
| `{ "type": "none" }` | — | UI 전용 플러그인 |
|
|
488
|
+
| `{ "type": "python", "entry": "main.py" }` | `py/` | `entry` |
|
|
489
|
+
| `{ "type": "cpp", "entry": "<executable>" }` | `cpp/` | `entry` = 빌드된 실행 파일 이름 |
|
|
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/` | 동일 |
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 6. 플러그인 UI SDK (`useRb`)
|
|
496
|
+
|
|
497
|
+
패키지 `@rainbow-robotics/plugin-ui`:
|
|
498
|
+
|
|
499
|
+
| import 경로 | 내용 |
|
|
500
|
+
|---|---|
|
|
501
|
+
| `@rainbow-robotics/plugin-ui/react` | Muscat 컴포넌트(`RB*`), `useRb`, `useRbFormData`, `useRbSave`, `useRbStorage`, `createRbStore`, `RbSdk` / `RbStore` / `RbSetState` 타입 |
|
|
502
|
+
| `@rainbow-robotics/plugin-ui/sdk` | 컴포넌트를 뺀 같은 훅들, 그리고 `RbContext`와 `RbNotifyType` 타입 |
|
|
503
|
+
| `@rainbow-robotics/plugin-ui/manifest` | manifest 타입(`PluginManifest`, `PluginContribution`, `PluginVariable`, `PluginRuntime`), `CONTRIBUTION_POINTS`, `VARIABLE_TYPES`, `validateManifest` |
|
|
504
|
+
|
|
505
|
+
Muscat 호스트 밖(단위 테스트, Storybook)에서는 `useRb()`가 stub을 돌려줍니다: 호출하면 `[plugin-ui] rb.<name> called outside of a host context`를 로그로 남기고 빈 값으로 resolve합니다.
|
|
506
|
+
|
|
507
|
+
### 6.1 엔트리 컴포넌트
|
|
508
|
+
|
|
509
|
+
`ui/` 최상위의 각 `*.tsx`는 React 컴포넌트를 default export 합니다. 호스트가 이를 마운트하고 context로 `rb`를 넘겨 줍니다.
|
|
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
|
+
네이티브 `<button>`, `<input>`, `<select>`, `<textarea>`, `<table>`, `<dialog>` 대신 plugin-ui 컴포넌트를 씁니다. 대응표: button → `RBLabelButton` / `RBIconButton`, 텍스트 입력 → `RBTextInput` / `RBTextField`, 숫자 → `RBSideNumberInput`, select → `RBSelectBox` + `Option`, checkbox → `RBCheckBox`, switch → `RBSwitch`, slider → `RBSlider`, table → `RBTable`, modal → `RBModal`, tabs → `RBTabs`. props와 예시는 `muscat-pk storybook`, `muscat-pk mcp`에서 확인합니다.
|
|
522
|
+
|
|
523
|
+
### 6.2 백엔드와 메시지 주고받기
|
|
524
|
+
|
|
525
|
+
| UI | 백엔드 (Python) | 패턴 |
|
|
526
|
+
|---|---|---|
|
|
527
|
+
| `rb.pub(topic, payload)` | `@plugin.subscribe(topic)` | UI → 백엔드 단방향 |
|
|
528
|
+
| `rb.sub(topic, handler)` | `plugin.publish(topic, payload)` | 백엔드 → UI 스트림 |
|
|
529
|
+
| `rb.action(topic, payload?)` | `@plugin.serve(topic)` | UI 요청, 백엔드 응답 |
|
|
530
|
+
| `rb.serve(topic, handler)` | `plugin.call(topic, payload=...)` | 백엔드 요청, UI 응답 |
|
|
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
|
+
// 정리: 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
|
+
- 토픽은 플러그인 id 범위로 격리됩니다: `"state"` → `{pluginId}/state`. 앞에 `/`를 붙이면 절대 경로입니다 (`"/broadcast/x"`).
|
|
546
|
+
- 네 메서드 모두 payload / 결과 타입을 제네릭으로 받습니다.
|
|
547
|
+
- 구독 해제는 `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 호스트 다이얼로그와 토스트
|
|
558
|
+
|
|
559
|
+
| API | 반환 |
|
|
560
|
+
|---|---|
|
|
561
|
+
| `rb.toast({ title?, message, type?, actionLabel?, handleActionClick?, closeOnActionClick?, duration?, onClose? })` | 없음 (`message`: 문자열 또는 ReactElement, `type`: `info`, `danger`, `warning`, `caution`, `success`) |
|
|
562
|
+
| `rb.alert({ title?, message, buttonText? })` | 닫힐 때 resolve |
|
|
563
|
+
| `rb.confirm({ title?, message, buttonText?: { yes, no } })` | `true` / `false` |
|
|
564
|
+
| `rb.prompt({ title?, message, value?, placeholder?, buttonText?: { yes, no } })` | 입력 문자열, 취소 시 `undefined` |
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
567
|
+
if (await rb.confirm({ title: "실행", message: "백엔드 동작을 시작할까요?" })) {
|
|
568
|
+
await rb.action("run");
|
|
569
|
+
rb.toast({ message: "시작됨", type: "success" });
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### 6.4 Program 페이지 (`rb.program`)
|
|
574
|
+
|
|
575
|
+
| API | 의미 |
|
|
576
|
+
|---|---|
|
|
577
|
+
| `rb.program.getSteps(scope?)` | Program 페이지에 열린 프로그램의 메인 스텝 트리 |
|
|
578
|
+
| `rb.program.setSteps(steps, scope?)` | 메인 스텝 트리 교체. 저장되지 않은 편집으로 들어가고 사용자가 저장 |
|
|
579
|
+
| `rb.program.getCatalog(scope?)` | 로드된 로봇의 명령 카탈로그 (이름, 기본 인자, 중첩 규칙) |
|
|
580
|
+
| `rb.program.getCommandDefaults?(command, scope?)` | 기본값이 실제 로봇 상태를 읽는 명령(카탈로그 `rules.hasFactoryDataFn`)의 기본 step. 선택적이며 호스트에 따라 없을 수 있음 |
|
|
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>; // args 외 스텝 필드, 예: SubTask의 subTaskType
|
|
588
|
+
children?: PluginProgramStep[];
|
|
589
|
+
};
|
|
590
|
+
type PluginProgramScope = { taskId?: string; robotModel?: string };
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
`scope`가 없으면 첫 번째 main task를 씁니다. `program.monitor` 탭에서는 `rb.data`가 `{ point: "program.monitor", taskId, robotModel }`이므로 이를 `scope`로 넘겨야 다중 컴포넌트 로봇에서 맞는 task로 갑니다.
|
|
594
|
+
|
|
595
|
+
### 6.5 스텝 폼 저장/복원 (`program.panel`)
|
|
596
|
+
|
|
597
|
+
| 훅 | 의미 |
|
|
598
|
+
|---|---|
|
|
599
|
+
| `useRbFormData<T>()` | 이 스텝에 저장된 값. 새 스텝이면 `undefined` |
|
|
600
|
+
| `useRbSave(fn)` | 호스트가 저장할 때 호출할 getter 등록. 항상 최신 상태를 봄. 반환값은 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
|
+
저장된 `args`는 백엔드 `on_execute(args)`로 전달됩니다 (7.3 참고).
|
|
624
|
+
|
|
625
|
+
### 6.6 브라우저 저장소 (`rb.storage`, `useRbStorage`, `createRbStore`)
|
|
626
|
+
|
|
627
|
+
플러그인의 브라우저 쪽 데이터는 여기에만 저장합니다. 모든 데이터가 `muscat-plugin:<pluginId>`로 구분되고, 플러그인을 삭제하면 호스트가 지웁니다. 다른 방법(`localStorage` 직접 사용, 자체 IndexedDB)으로 저장한 데이터는 정리되지 않습니다.
|
|
628
|
+
|
|
629
|
+
| 저장소 | 용도 |
|
|
630
|
+
|---|---|
|
|
631
|
+
| `rb.storage` (= `rb.storage.indexedDB`) | 기본값. 구조화된 값(객체, 배열, `Date`, `Blob` 등) |
|
|
632
|
+
| `rb.storage.local` | `localStorage`, JSON 값, 새로고침해도 유지 |
|
|
633
|
+
| `rb.storage.session` | `sessionStorage`, JSON 값, 탭을 닫으면 사라짐 |
|
|
634
|
+
| `rb.storage.cookie` | 값당 약 4 KB, 호스트로 가는 모든 요청에 실려 감 — 아주 작은 값만 |
|
|
635
|
+
|
|
636
|
+
모든 저장소의 API는 같습니다:
|
|
637
|
+
|
|
638
|
+
| 메서드 | 의미 |
|
|
639
|
+
|---|---|
|
|
640
|
+
| `get<T>(key)` | 값, 없으면 `undefined` |
|
|
641
|
+
| `set(key, value)` | 저장 |
|
|
642
|
+
| `delete(key)` | 삭제 |
|
|
643
|
+
| `keys()` | 이 플러그인의 키 목록 |
|
|
644
|
+
| `subscribe(listener, key?)` | 이 API로 바뀐 모든 변경에 `({ kind, key, value }) => void` 호출 — 현재 화면, 같은 플러그인의 다른 화면(툴바 패널, Program 탭 등), 다른 브라우저 탭 포함. 삭제 후에는 `value`가 `undefined`. 구독 해제 함수 반환 |
|
|
645
|
+
| `getItem` / `setItem` / `removeItem` | zustand `StateStorage` (문자열 값) |
|
|
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` — 저장된 값 하나를 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)는 저장, setDraft(undefined)는 삭제
|
|
664
|
+
return <RBTextInput value={draft?.name ?? ""} handleChange={(name: string) => void setDraft({ name })} />;
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
- `useRbStorage<T>(key, { kind?, initial? })` → `[value, setValue]`. `kind` 기본값은 `"indexeddb"`.
|
|
669
|
+
- 값이 어디서 바뀌든(다른 화면, 다른 탭) 다시 렌더링됩니다.
|
|
670
|
+
- 저장된 값을 불러오기 전까지 `value`는 `initial`입니다.
|
|
671
|
+
|
|
672
|
+
#### `createRbStore` — 저장되는 zustand 스타일 store
|
|
673
|
+
|
|
674
|
+
모듈 최상위에서 한 번 만들고 플러그인의 어느 화면에서든 씁니다:
|
|
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); // speed가 바뀔 때만 다시 렌더링
|
|
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
|
+
| 멤버 | 의미 |
|
|
704
|
+
|---|---|
|
|
705
|
+
| `createRbStore<T>(init, { name, storage? })` | `init(set, get)`이 초기 상태 + 액션을 반환. `name`은 저장 키, `storage`는 저장소 종류 (기본 `"indexeddb"`) |
|
|
706
|
+
| `useStore()` / `useStore(selector)` | 전체 상태 / 일부 (그 부분이 바뀔 때만 다시 렌더링) |
|
|
707
|
+
| `useStore.getState()` | React 밖에서 현재 상태 읽기 |
|
|
708
|
+
| `useStore.setState(partial \| (s) => partial)` | React 밖에서 변경 병합 |
|
|
709
|
+
| `useStore.subscribe((next, prev) => …)` | React 밖에서 변경 구독. 해제 함수 반환 |
|
|
710
|
+
|
|
711
|
+
- 상태는 `set` / `setState`로만 바뀝니다. 바뀔 때마다 저장되고, 이 store를 쓰는 모든 화면과 탭이 다시 렌더링됩니다.
|
|
712
|
+
- 함수(액션)는 저장되지 않고 데이터 필드만 저장됩니다.
|
|
713
|
+
- 저장된 데이터는 이 store를 쓰는 첫 컴포넌트가 마운트될 때 불러옵니다 (그때서야 플러그인 id를 알 수 있음). 그 전에는 `getState()`가 초기값을 반환합니다.
|
|
714
|
+
- 변경은 얕게 병합됩니다: 배열/객체는 직접 수정하지 말고 새로 만들어 넣으세요 (`[...s.points, p]`).
|
|
715
|
+
|
|
716
|
+
#### zustand `persist`를 직접 쓰기
|
|
717
|
+
|
|
718
|
+
모든 저장소는 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`를 플러그인 의존성으로 설치해야 합니다. `createRbStore`는 추가 설치가 필요 없습니다.
|
|
726
|
+
|
|
727
|
+
### 6.7 기타
|
|
728
|
+
|
|
729
|
+
| API | 의미 |
|
|
730
|
+
|---|---|
|
|
731
|
+
| `rb.data` | 호스트가 마운트 시 넘기는 데이터 (`program.monitor` scope, 저장된 폼 값). 새로 마운트되면 `undefined` |
|
|
732
|
+
| `rb.onSave(getPayload)` | `useRbSave`의 저수준 형태. 등록 해제 함수 반환 |
|
|
733
|
+
| `rb.rosPeers.get()` | 이 플러그인의 ROS 2 노드에 접근을 허용한 PC 목록 |
|
|
734
|
+
| `rb.rosPeers.set(ips)` | 목록 저장 후 백엔드 재시작. 잘못된 IP면 reject |
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
## 7. Python 플러그인 SDK
|
|
739
|
+
|
|
740
|
+
Python 백엔드(`runtime.type: "python"`, 엔트리 `py/main.py`)는 `rainbow-rb-sdk`를 씁니다. 버전은 `requirements.txt`에 고정하고 `muscat-pk sdk update`로 바꿉니다. 실행 시 SDK는 로봇의 플러그인 컨테이너가 제공합니다.
|
|
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`는 `RBPluginCall`, `PluginProgramContext`, `RBManipulateSDK`, `RBAmrSDK`, `RBRby1SDK`, `RBProgramSDK`, `RBBaseSDK`, `rby1`(벤더링된 rby1-sdk 타입)을 export합니다. 나머지(`ConnectOptions`, `PluginRouter`, `PluginRouteError`, `RBPluginGatewayError` 등)는 `rainbow.rb_sdk.rb_sdk_cpp`에 있습니다.
|
|
748
|
+
|
|
749
|
+
어떤 API를 쓸지는 contribution point로 정해집니다:
|
|
750
|
+
|
|
751
|
+
| contribution point | 백엔드 |
|
|
752
|
+
|---|---|
|
|
753
|
+
| `toolbar` / `lnb` / `move.panel` / `program.monitor` | `RBPluginCall` + 로봇 SDK(`RBManipulateSDK`, `RBAmrSDK`) 직접 호출. 정지/일시정지/변수 컨텍스트 없음 |
|
|
754
|
+
| `program.panel` | `PluginProgramContext` — 프로그램 스텝으로 실행되며 정지/일시정지·프로그램 변수와 연동 |
|
|
755
|
+
|
|
756
|
+
### 7.1 `RBPluginCall` — UI ↔ 백엔드
|
|
757
|
+
|
|
758
|
+
내부 전용입니다 (UI ↔ 백엔드, 플러그인 ↔ 플러그인). Muscat 밖에서는 접근할 수 없습니다. `RBPluginCall(plugin_id=None)`: id는 인자, 없으면 환경 변수 `RB_PLUGIN_ID`, 그것도 없으면 `PLUGIN_ID`입니다 (앞뒤 `/` 제거). 프로세스·플러그인 id마다 인스턴스가 하나라서 `RBPluginCall()`을 다시 불러도 같은 객체가 나옵니다.
|
|
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)
|
|
772
|
+
|
|
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
|
+
| 멤버 | 의미 |
|
|
778
|
+
|---|---|
|
|
779
|
+
| `serve(keyexpr, handler=None)` | 요청/응답 핸들러. 데코레이터 또는 직접 호출. 반환값이 응답. 키마다 핸들러 하나 — 다시 serve하면 이전 것을 교체 |
|
|
780
|
+
| `subscribe(topic, callback=None)` | publish 수신 |
|
|
781
|
+
| `publish(topic, payload=None)` | 구독자에게 발행. 연결이 끊긴 동안에는 조용히 버려짐 |
|
|
782
|
+
| `call(keyexpr, *, payload, timeout=3.0)` | 상대편에 요청하고 응답 대기. 연결이 없거나 시간 초과면 `RBPluginGatewayError` |
|
|
783
|
+
| `unserve(keyexpr=None)` / `undeclare_queryable(keyexpr=None)` | serve 핸들러 하나 / 전체 해제 |
|
|
784
|
+
| `unsubscribe(topic=None, callback=None)` / `unsubscribe_topic(topic=None, callback=None)` | 콜백 하나, 토픽의 구독 전체, 또는 모든 구독 해제 |
|
|
785
|
+
| `plugin_id` | 사용 중인 플러그인 id (`muscat-pk dev`에서는 dev id) |
|
|
786
|
+
| `connected` | 연결 여부 |
|
|
787
|
+
| `data_dir` | 영구 데이터 디렉터리 (접근 시 생성): `RB_PLUGIN_DATA_DIR`(Muscat이 설정), 없으면 `/app/data/runtime/plugins/data/{plugin_id}`. 업데이트·재시작에도 유지되고 플러그인 삭제 시 제거. 설치 디렉터리(cwd)는 업데이트마다 교체되므로 상태·캐시·설정은 여기에 저장 |
|
|
788
|
+
| `router` | 외부 엔드포인트 (7.2) |
|
|
789
|
+
|
|
790
|
+
payload는 JSON 직렬화 가능해야 합니다. 핸들러는 sync/async 모두 됩니다. 토픽은 UI와 같은 규칙으로 격리됩니다: `"state"` → `{plugin_id}/state`, 앞에 `/`를 붙이면 절대 경로.
|
|
791
|
+
|
|
792
|
+
### 7.2 `plugin.router` — 외부 엔드포인트
|
|
793
|
+
|
|
794
|
+
`common/plugin/{plugin_id}/{path}`를 REST, Socket.IO, TCP, MQTT, WebRTC로 노출합니다. 실행 중인 Muscat(`common`)이 재시작 없이 반영하고, 플러그인이 멈추면 사라집니다. 로봇에 `rainbow-rb-sdk` 0.0.9.dev5 이상이 필요합니다 (그보다 낮으면 `AttributeError`).
|
|
795
|
+
|
|
796
|
+
```python
|
|
797
|
+
@plugin.router.on("/hello") # 모든 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만
|
|
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})
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
| API | 의미 | 외부 쪽 |
|
|
815
|
+
|---|---|---|
|
|
816
|
+
| `router.on(path, explicit_routes=None)` | 요청/응답 | `common/plugin/{id}/{path}` 호출 (REST는 `POST`) |
|
|
817
|
+
| `router.subscribe(topic, explicit_events=None)` | 외부 publish 수신 | `common/plugin/{id}/{topic}`에 publish |
|
|
818
|
+
| `router.emit(topic, payload=None)` | 외부 구독자에게 push | `common/plugin/{id}/{topic}` 구독 |
|
|
819
|
+
|
|
820
|
+
핸들러 규칙:
|
|
821
|
+
|
|
822
|
+
- 시그니처 `(payload, meta)`, `(payload)`, `()` — 선언한 인자만 주입됩니다. sync/async 모두 가능.
|
|
823
|
+
- `payload`: 호출자가 보낸 값 (REST는 JSON body + query param 병합).
|
|
824
|
+
- `meta`: `{"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": dict | None}`. 로그인 정책이 켜져 있으면 `principal`에 토큰 주체가 들어 있습니다. 권한 체크는 플러그인이 직접 합니다.
|
|
825
|
+
- 반환값이 응답 body (JSON). `None`이면 빈 응답.
|
|
826
|
+
- `raise PluginRouteError(code, message)`로 상태 코드 지정. 다른 예외는 500 + `str(exc)`.
|
|
827
|
+
- `subscribe` 핸들러는 `(payload)`만 받고 반환값이 없습니다.
|
|
828
|
+
|
|
829
|
+
규칙:
|
|
830
|
+
|
|
831
|
+
- 경로만 적습니다. `plugin_id`는 SDK가 붙입니다. 여러 단계 경로도 됩니다 (`/tool/echo`). 같은 경로를 두 번 선언하면 `ValueError`.
|
|
832
|
+
- `explicit_routes` / `explicit_events`: `None`이면 모든 transport. 이름은 `api | socket | tcp | mqtt | webrtc`이고 `subscribe`는 `tcp | mqtt | webrtc`만. 모르는 이름은 `ValueError`. 허용 안 된 transport로 오면 404.
|
|
833
|
+
- `on`과 `subscribe`는 같은 이름을 써도 됩니다.
|
|
834
|
+
|
|
835
|
+
외부에서 호출하기:
|
|
836
|
+
|
|
837
|
+
| Transport | 방법 |
|
|
838
|
+
|---|---|
|
|
839
|
+
| REST | `POST /common/plugin/{id}/{path}`, JSON body |
|
|
840
|
+
| Socket.IO | `common/plugin/{id}/{path}` 이벤트 emit, 응답은 ack로 |
|
|
841
|
+
| TCP | 토픽 `common/plugin/{id}/{path}` 요청 / publish |
|
|
842
|
+
| MQTT | 요청 `common/plugin/{id}/{path}/__req__`, publish `common/plugin/{id}/{topic}` |
|
|
843
|
+
| WebRTC | data channel 토픽 `common/plugin/{id}/{path}` |
|
|
844
|
+
|
|
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
|
+
# muscat-pk dev 중 (dev id는 시작할 때 출력됨)
|
|
849
|
+
curl -s -X POST http://localhost:3000/common/plugin/<dev-id>/hello -H 'Content-Type: application/json' -d '{}'
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
| 상황 | 상태 코드 |
|
|
853
|
+
|---|---|
|
|
854
|
+
| 경로 없음 / 플러그인 꺼짐 / 허용 안 된 transport | 404 |
|
|
855
|
+
| 10초 안에 응답 없음 | 504 |
|
|
856
|
+
| `common` ↔ gateway 연결 안 됨 | 503 |
|
|
857
|
+
| 핸들러 예외 | 500 (`PluginRouteError`면 그 코드) |
|
|
858
|
+
|
|
859
|
+
인증: 로그인 정책이 켜져 있으면 토큰이 필요합니다 (역할 무관). REST는 다른 Muscat API와 같은 Bearer 토큰을 씁니다. 꺼져 있으면 토큰 없이 됩니다.
|
|
860
|
+
|
|
861
|
+
알려진 제약:
|
|
862
|
+
|
|
863
|
+
- 경로 이름이 `install`이면 REST로 호출할 수 없습니다 (레지스트리 설치 라우트가 먼저 잡음).
|
|
864
|
+
- MQTT 브로커는 보낸 쪽에도 메시지를 돌려줍니다. 같은 토픽에 `emit`과 `subscribe`를 함께 쓰면 자기 push를 `mqtt`로 받습니다. `explicit_events`에서 `mqtt`를 빼거나 토픽을 나누세요.
|
|
865
|
+
- 플러그인 재시작 직후 외부 호출은 gateway가 이전 소켓을 정리할 때까지 최대 1분 정도 504, 그 뒤 404가 날 수 있습니다.
|
|
866
|
+
|
|
867
|
+
### 7.3 `PluginProgramContext` — 프로그램 명령
|
|
868
|
+
|
|
869
|
+
`program.panel`에서만 씁니다. 프로그램이 이 스텝에 도달하면 실행기가 `commandInfo.funcName`을 `plugin_id` + 스텝 인자로 호출하고, SDK가 `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) # 새 변수 → RBP_ 필수
|
|
882
|
+
|
|
883
|
+
for _ in range(100):
|
|
884
|
+
await ctx.check_stop() # 정지 → CancelledError, 일시정지 → 재개까지 대기
|
|
885
|
+
await asyncio.sleep(0.5)
|
|
886
|
+
|
|
887
|
+
await ctx.done() # 반드시 호출. 안 하면 스텝이 끝나지 않음
|
|
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) # 프로그램이 없으면 무시됨
|
|
898
|
+
|
|
899
|
+
async def main() -> None:
|
|
900
|
+
asyncio.create_task(background())
|
|
901
|
+
await ctx.start() # 상주
|
|
902
|
+
|
|
903
|
+
asyncio.run(main())
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
| 멤버 | 의미 |
|
|
907
|
+
|---|---|
|
|
908
|
+
| `on_execute(fn)` | 데코레이터. 이 스텝이 실행될 때마다 스텝 인자로 1회 호출 |
|
|
909
|
+
| `done()` | 스텝 완료. 반드시 호출 |
|
|
910
|
+
| `check_stop(robot_model=None)` | 정지 → `CancelledError`, 일시정지 → 대기. 루프마다 호출 |
|
|
911
|
+
| `get_variable(name, robot_model=None, plugin_id=None)` | 읽기. 프로그램이 없으면 `None` |
|
|
912
|
+
| `set_variable(name, value)` | 실행 중인 모든 main task에 쓰기. 프로그램이 없으면 무시 |
|
|
913
|
+
| `executor_state(robot_model=None)` | 모델 하나의 상태 문자열, 또는 전체 `{model: state}` |
|
|
914
|
+
| `is_paused(robot_model=None)` / `is_stopped(robot_model=None)` | 상태 확인 |
|
|
915
|
+
| `on_executor_state_change(fn)` | 데코레이터. 상태가 바뀔 때마다 `(robot_model, state)`로 1회 |
|
|
916
|
+
| `set_execute_handler(fn)` / `set_state_change_handler(fn)` | 위 두 등록을 데코레이터 없이 |
|
|
917
|
+
| `begin_execution(robot_model=None)` / `end_execution()` | SDK가 `on_execute` 앞뒤로 부르는 실행 수명 주기 hook. 일반 플러그인에서는 필요 없음 |
|
|
918
|
+
| `start()` / `stop()` / `pause()` | 수명 주기 |
|
|
919
|
+
| `in_execute`, `exec_robot_model`, `plugin_id`, `started`, `control_topic` | 속성 |
|
|
920
|
+
|
|
921
|
+
#### 변수
|
|
922
|
+
|
|
923
|
+
- 이미 있는 변수: 이름 그대로 씁니다. 프로그램과 값을 주고받는 것이 원래 용도입니다.
|
|
924
|
+
- 새 변수: 스텝 안이든 밖이든 `RBP_`로 시작해야 합니다 (아니면 `RuntimeError`). 충돌을 피하려면 `RBP_{plugin}_{name}` 형식을 권장합니다. `RBP_`만으로는 두 플러그인이 서로 덮어쓰는 것을 막지 못합니다.
|
|
925
|
+
- Program 페이지에서 고를 수 있게 하려면 `manifest.json` → `variables`에 선언합니다 (5.5).
|
|
926
|
+
|
|
927
|
+
`set_variable`은 실행 중인 모든 main task에 적용됩니다:
|
|
928
|
+
|
|
929
|
+
| 시점 | 자기 프로그램 | 다른 로봇의 main task |
|
|
930
|
+
|---|---|---|
|
|
931
|
+
| 자기 스텝 실행 중 | 즉시 | 각자 다음 스텝 경계에서 |
|
|
932
|
+
| 스텝 밖 | 다음 스텝 경계에서 | 동일 |
|
|
933
|
+
|
|
934
|
+
`get_variable` 옵션:
|
|
935
|
+
|
|
936
|
+
```python
|
|
937
|
+
await ctx.get_variable("counter") # main task 전체에서 처음 찾은 값
|
|
938
|
+
await ctx.get_variable("counter", robot_model="C500920") # 특정 로봇
|
|
939
|
+
await ctx.get_variable("RBP_TEST", plugin_id="A.plugin") # 그 플러그인이 마지막으로 쓴 값
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
변수마다 플러그인별 기록(`meta.writers`)이 남아서 이름이 겹쳐도 값을 되찾을 수 있습니다: SDK에서는 `get_variable(..., plugin_id=...)`, 프로그램 식에서는 `get_var("A.plugin", "RBP_TEST")`. 드물게 worker의 변수 flush와 경합해 쓰기가 유실될 수 있으니 중요한 값은 다시 확인하세요.
|
|
943
|
+
|
|
944
|
+
#### 실행기 상태
|
|
945
|
+
|
|
946
|
+
상태: `idle | running | paused | waiting | stopped | post_start | completed | error`. 실행 중인 task가 없는 모델은 `None` / dict에 없음.
|
|
947
|
+
|
|
948
|
+
- `muscat/executor/state`가 0.8초마다 발행되므로 최대 약 1초 지연됩니다.
|
|
949
|
+
- `IS_DEV=true`이면 발행이 없어서 항상 `None`입니다.
|
|
950
|
+
- `on_executor_state_change`는 모델별로 상태가 바뀔 때 한 번만 호출되고, 핸들러 예외는 로그만 남깁니다.
|
|
951
|
+
|
|
952
|
+
`check_stop` / `is_paused` / `is_stopped`의 대상 범위:
|
|
953
|
+
|
|
954
|
+
1. `robot_model`을 주면 → 그 모델
|
|
955
|
+
2. `on_execute` 안에서 생략 → 이 스텝을 실행 중인 모델
|
|
956
|
+
3. 그 밖에서 생략 → 모든 모델
|
|
957
|
+
|
|
958
|
+
### 7.4 백엔드에서 로봇 SDK 쓰기
|
|
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
|
+
로봇을 움직이기 전에 안전 확인을 직접 넣으세요. 정확한 시그니처는 `muscat-pk mcp`(`search_rb_sdk`, `get_rb_sdk_api`)로 확인합니다.
|
|
969
|
+
|
|
970
|
+
### 7.5 원격 개발 (노트북 → 로봇)
|
|
971
|
+
|
|
972
|
+
```bash
|
|
973
|
+
export MUSCAT_ROBOT_HOST=192.168.1.100
|
|
974
|
+
export MUSCAT_ROBOT_TOKEN=<service token> # 로봇 로그인 정책이 켜져 있을 때만
|
|
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() # connect() 이후에 — connect()가 세션을 다시 열어서 그 전에 등록한 핸들러가 사라짐
|
|
987
|
+
...
|
|
988
|
+
if host:
|
|
989
|
+
await manipulate.disconnect()
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
스캐폴드된 `py/main.py`가 이미 이 구조입니다.
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## 8. 다른 백엔드 런타임 (C++ / ROS 2)
|
|
997
|
+
|
|
998
|
+
`none`이 아닌 런타임은 `dev:back`, `dev`, `build` 때 모두 Docker에서 실행됩니다.
|
|
999
|
+
|
|
1000
|
+
### 8.1 C++ (`cpp`)
|
|
1001
|
+
|
|
1002
|
+
- 소스는 `cpp/`. Docker 안에서 C++ SDK(apt `rainbow-rb-sdk-dev`)로 CMake 빌드합니다.
|
|
1003
|
+
- CMake: `find_package(rb_sdk <ver> EXACT REQUIRED)` + `target_link_libraries(<target> rb_sdk::rb_sdk)` (C++20).
|
|
1004
|
+
- 헤더: `<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
|
+
- 동적 링크. `build`가 `librb_sdk.so`를 `dist/<arch>/lib/`에 복사합니다.
|
|
1006
|
+
|
|
1007
|
+
컨테이너 밖(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
|
+
- 소스는 `ros2_ws/src/<package>/`. `runtime.package` = colcon 패키지, `runtime.launch` = 노드 실행 파일.
|
|
1018
|
+
- 상대 토픽 이름은 플러그인 namespace 아래에 놓입니다: id의 `.` → `/`, `-` → `_` (`com.example.my-plugin` → `/com/example/my_plugin`). 다른 노드와 공유하는 토픽은 절대 이름(`/cmd_vel`)을 씁니다.
|
|
1019
|
+
- rclpy 노드에서도 `RBPluginCall`을 쓸 수 있습니다.
|
|
1020
|
+
- ROS discovery는 로봇 안으로 제한됩니다 (`ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST`). 외부 PC는 UI에서 `rb.rosPeers.set([...])`으로 허용하면 Muscat이 `ROS_STATIC_PEERS`를 넣고 백엔드를 재시작합니다. dev에서는 목록이 `.muscat-dev/ros-peers.json`에 저장됩니다.
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## 9. 개발 환경
|
|
1025
|
+
|
|
1026
|
+
### 9.1 로컬 Muscat 런타임
|
|
1027
|
+
|
|
1028
|
+
로봇 IP 없이(또는 Muscat이 없는 로봇에 붙어) `dev` / `dev:ui`를 돌리면 `localhost:3000`(API)과 `:10000`(플러그인 gateway)에 Muscat이 필요합니다:
|
|
1029
|
+
|
|
1030
|
+
- 이미 `:10000`을 노출하는 컨테이너(`muscat`, `muscat-dev` 등)가 있으면 재사용합니다. kit이 띄우지 않은 컨테이너는 플러그인 포트를 노출하지 않습니다.
|
|
1031
|
+
- 컨테이너 없이 그 포트를 이미 쓰는 Muscat(로봇 PC, host networking)이 있으면 그대로 씁니다.
|
|
1032
|
+
- 없으면 `muscat-pk-muscat`을 띄웁니다 — `common`(+ `--sim`에 필요한 서비스)과 MediaMTX(`8554`, `8889`, `8189`)를 돌리는 S3 런타임 이미지(`muscat-pk-runtime:<version>`, `version.json` 기준)이며 `~/.muscat-pk/muscat/`에 캐시됩니다. 노출할 플러그인 포트나 서비스가 바뀌면 다시 만들고, Muscat 새 버전이 나오면 이전 이미지를 교체합니다. 데이터(DB, 로그, 프로그램)는 컨테이너 안에만 있어서 컨테이너를 새로 만들면 비어 있습니다.
|
|
1033
|
+
- `dev`는 종료할 때 자신이 띄운 `muscat-pk-muscat`을 지웁니다. `dev:ui` 단독으로 띄운 것은 계속 떠 있습니다.
|
|
1034
|
+
- 백엔드는 로컬 Muscat의 network namespace에 붙으므로 `execute`와 SDK가 로봇에서처럼 닿습니다.
|
|
1035
|
+
|
|
1036
|
+
```bash
|
|
1037
|
+
docker rm -f muscat-pk-muscat # 중지
|
|
1038
|
+
MUSCAT_PK_NO_LOCAL_MUSCAT=1 muscat-pk dev # 띄우지 않기
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
API 문서는 `http://localhost:3000/common/docs`에 있습니다.
|
|
1042
|
+
|
|
1043
|
+
### 9.2 로봇 PC에서 직접 개발
|
|
1044
|
+
|
|
1045
|
+
로봇 PC에서는 Muscat이 이미 `10000`에서 gateway를 열고 있으므로 CLI가 그것을 쓰고 로컬 Muscat을 띄우지 않습니다.
|
|
1046
|
+
|
|
1047
|
+
### 9.3 여러 명이 한 로봇에서 개발
|
|
1048
|
+
|
|
1049
|
+
원격 로봇에 붙으면 `dev` / `dev:back`이 플러그인을 `<manifest id>.dev-<user>-<random>`으로 실행합니다 (random 부분은 `~/.muscat-pk/dev-id`에 보관). 그래서 두 PC가 서로의 UI 요청에 응답하지 않습니다. UI, 백엔드, ROS namespace, 외부 라우트가 모두 dev id를 씁니다. `--keep-id`는 manifest id를 그대로 씁니다 (프로그램 `execute`나 실제 id를 쓰는 외부 클라이언트 테스트용). 이때는 한 PC만 붙어야 합니다.
|
|
1050
|
+
|
|
1051
|
+
### 9.4 로컬 설치 vs 전역 설치
|
|
1052
|
+
|
|
1053
|
+
프로젝트 `node_modules`에 `muscat-plugin-kit`이 있으면 어떤 `muscat-pk`를 실행해도 그 버전으로 넘깁니다. 그래서 CLI, `vite` 프리셋, `preview-host`가 항상 프로젝트가 고정한 버전입니다. `muscat-pk version`은 실제로 실행된 쪽의 버전을 출력합니다.
|
|
1054
|
+
|
|
1055
|
+
### 9.5 오프라인 / 폐쇄망
|
|
1056
|
+
|
|
1057
|
+
Docker 이미지는 없거나 kit의 Dockerfile / 빌드 인자가 바뀌었을 때만 빌드합니다 (재빌드는 캐시가 있어도 레지스트리에 접속함).
|
|
1058
|
+
|
|
1059
|
+
```bash
|
|
1060
|
+
docker save muscat-pk-python-dev muscat-pk-runtime | gzip > muscat-pk-images.tar.gz # 인터넷 되는 PC
|
|
1061
|
+
gunzip -c muscat-pk-images.tar.gz | docker load # 오프라인 PC
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
C++ / ROS 2 프로젝트는 `muscat-pk-cpp-dev` / `muscat-pk-ros2-dev`(그리고 `build`가 쓰는 arch별 `-amd64` / `-arm64` 이미지)도 필요합니다. 오프라인에서 로컬 Muscat은 캐시된 `version.json`을, 시뮬레이터는 이미 받아 둔 이미지를 씁니다.
|
|
1065
|
+
|
|
1066
|
+
강제 재빌드: `MUSCAT_PK_REBUILD_IMAGE=1`.
|
|
1067
|
+
|
|
1068
|
+
### 9.6 IDE 지원 (Python)
|
|
1069
|
+
|
|
1070
|
+
`muscat-pk new`가 `requirements.txt`를 설치한 `.venv`를 만듭니다. 에디터 린트용일 뿐이고 백엔드는 Docker에서 돌아갑니다. `uv`나 Python 3가 없으면 `Could not create .venv (no local Python/uv?) — skipping, Docker dev still works.`를 출력하고 넘어갑니다. 설치 후 `new`나 `type change`를 다시 실행하세요.
|
|
1071
|
+
|
|
1072
|
+
### 9.7 JavaScript 의존성
|
|
1073
|
+
|
|
1074
|
+
`build`는 프로젝트의 Vite 빌드를 그대로 돌리므로 패키지가 설치돼 있어야 합니다. 빌드 전에 `npm install --omit=dev`를 하지 마세요 — `vite`, `typescript`, `tailwindcss`, `sass`, `less`는 devDependencies입니다. ZIP에는 `node_modules`가 들어가지 않습니다.
|
|
1075
|
+
|
|
1076
|
+
---
|
|
1077
|
+
|
|
1078
|
+
## 10. 스타일
|
|
1079
|
+
|
|
1080
|
+
- `muscat()`이 모든 엔트리에 `ui/style.{css,scss,sass,less}` / `ui/styles.*`를, 이름이 같은 엔트리에 `ui/{entry}.*`를 자동 로드하고 CSS를 JS 번들에 넣습니다.
|
|
1081
|
+
- 빌드된 셀렉터는 `[data-muscat-plugin-id="..."]` 아래로 스코프되므로 플러그인 CSS가 호스트나 다른 플러그인에 영향을 주지 않습니다.
|
|
1082
|
+
- 색상은 hex 대신 디자인 토큰 class(`bg-bg-*`, `text-fg-*`, `typo-*`)를 씁니다.
|
|
1083
|
+
|
|
1084
|
+
Tailwind (기본, 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` (또는 `less`) 후 `ui/style.scss` (`ui/style.less`) 생성.
|
|
1094
|
+
|
|
1095
|
+
Emotion `styled`는 설정 없이 됩니다. `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
|
+
그리고 `tsconfig.json`에 `"jsxImportSource": "@emotion/react"`. Emotion `Global`은 스코프되지 않으니 쓰지 마세요.
|
|
1106
|
+
|
|
1107
|
+
---
|
|
1108
|
+
|
|
1109
|
+
## 11. AI 코딩 에이전트 설정 (`muscat-pk mcp`)
|
|
1110
|
+
|
|
1111
|
+
stdio MCP 서버입니다. rb-components(실제 props, Storybook 예시), 디자인 토큰, Python `rainbow-rb-sdk`(실제 시그니처/docstring)를 에이전트에 제공하고, 오래된 프로젝트 업그레이드용 `check_project`도 있습니다.
|
|
1112
|
+
|
|
1113
|
+
| 도구 | 반환 |
|
|
1114
|
+
|---|---|
|
|
1115
|
+
| `list_components`, `get_component_props`, `get_component_example` | plugin-ui 컴포넌트, prop 타입, 실제 Storybook 사용 예 |
|
|
1116
|
+
| `list_design_tokens`, `search_design_tokens` | 디자인 토큰 Tailwind class (`bg-bg-*`, `text-fg-*`, `typo-*`) |
|
|
1117
|
+
| `list_rb_sdk`, `search_rb_sdk`, `get_rb_sdk_api` | Python `rainbow-rb-sdk`와 C++ SDK의 클래스 / 시그니처 / docstring |
|
|
1118
|
+
| `list_program_commands` | Program 페이지 내장 명령(카테고리, `funcName`, 기본 인자, 중첩 규칙). `rb.program.setSteps`나 `POST /common/program/steps/upsert`에 사용 |
|
|
1119
|
+
| `get_program_api` | 프로그램 생성·실행 가이드(`rb.program`, `/common/program` REST)와 실행 중인 Muscat의 엔드포인트 스키마(기본 `http://localhost:3000`) |
|
|
1120
|
+
| `check_project` | 오래된 프로젝트가 `muscat-pk update` 후 고쳐야 할 것 |
|
|
1121
|
+
|
|
1122
|
+
Claude Code — 플러그인 루트의 `.mcp.json` (`new`가 만들고, 없으면 `update`가 추가):
|
|
1123
|
+
|
|
1124
|
+
```json
|
|
1125
|
+
{ "mcpServers": { "muscat": { "command": "muscat-pk", "args": ["mcp"] } } }
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
Codex — `muscat-pk mcp --install codex` (Windows에서는 `muscat-pk.cmd`로 기록), 또는 `~/.codex/config.toml`:
|
|
1129
|
+
|
|
1130
|
+
```toml
|
|
1131
|
+
[mcp_servers.muscat]
|
|
1132
|
+
command = "muscat-pk"
|
|
1133
|
+
args = ["mcp"]
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
서버는 에이전트가 직접 띄웁니다.
|
|
1137
|
+
|
|
1138
|
+
---
|
|
1139
|
+
|
|
1140
|
+
## 12. 환경 변수
|
|
1141
|
+
|
|
1142
|
+
| 변수 | 효과 |
|
|
1143
|
+
|---|---|
|
|
1144
|
+
| `MUSCAT_ROBOT_HOST` | `dev` / `dev:ui` / `dev:back`의 로봇 IP (프롬프트 생략) |
|
|
1145
|
+
| `MUSCAT_ROBOT_TOKEN` | 로봇 로그인 정책이 켜져 있을 때 service token |
|
|
1146
|
+
| `MUSCAT_PK_NO_LOCAL_MUSCAT=1` | 로컬 Muscat 런타임을 띄우지 않음 |
|
|
1147
|
+
| `MUSCAT_PK_REBUILD_IMAGE=1` | Docker 이미지 재빌드 |
|
|
1148
|
+
| `MUSCAT_PK_RB_SDK_VERSION` | C++ 이미지에 넣을 C++ SDK 버전 (`MUSCAT_PK_REBUILD_IMAGE=1`과 함께) |
|
|
1149
|
+
| `MUSCAT_PK_NO_BUILD_CACHE=1` | Docker 이미지를 `--no-cache`로 빌드 (CI) |
|
|
1150
|
+
| `MUSCAT_PK_HOST_NETWORK=0` | 네이티브 Linux에서도 `--network host` 대신 Docker Desktop(bridge) 경로 사용 (CI) |
|
|
1151
|
+
| `MUSCAT_PK_RBY1_CPUS` | rby1 시뮬레이터 CPU 상한 (기본 `1.5`) |
|
|
1152
|
+
| `MUSCAT_PK_MUSCAT_S3` | 로컬 Muscat 런타임 다운로드 위치 (기본 `https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat/main`) |
|
|
1153
|
+
| `MUSCAT_HOST_AGENT_DISABLE=1` | 호스트 Zenoh byte-proxy를 띄우지 않음 (macOS / Windows / WSL에서 원격 로봇에 붙을 때) |
|
|
1154
|
+
| `MUSCAT_PROXY_PORT` | 그 proxy의 포트 (기본 `47100`) |
|
|
1155
|
+
| `MUSCAT_ZENOH_ADVERTISE_HOST` / `MUSCAT_ZENOH_PORT` | 원격 로봇이 되접속할 LAN IP / 포트 (기본: 로봇으로 가는 경로의 IP / `53917` 또는 다음 빈 포트) |
|
|
1156
|
+
| `MUSCAT_PK_NO_UPDATE_CHECK=1` | `dev` / `dev:ui`의 업데이트 확인 생략 |
|
|
1157
|
+
| `RB_REGISTRY` | 업데이트 확인용 npm 레지스트리 (기본 `https://registry.npmjs.org`) |
|
|
1158
|
+
| `MUSCAT_PK_TAG` | 설치 스크립트 채널 / 버전 |
|
|
1159
|
+
| `CI` | 설정되면 `dev:ui`가 브라우저를 열지 않음 |
|
|
1160
|
+
| `NO_COLOR=1` / `FORCE_COLOR` | 출력 색상 |
|
|
1161
|
+
|
|
1162
|
+
백엔드 컨테이너 안: `RB_PLUGIN_ID` (플러그인 id / dev id), `RB_PLUGIN_PORTS` (선언한 `ports`, 쉼표 구분), `MUSCAT_ROBOT_HOST` / `MUSCAT_ROBOT_TOKEN` (원격 개발). 로봇에서는 Muscat이 `RB_PLUGIN_DATA_DIR`도 설정합니다 (`data_dir`, 7.1 참고).
|
|
1163
|
+
|
|
1164
|
+
---
|
|
1165
|
+
|
|
1166
|
+
## 13. 문제 해결
|
|
1167
|
+
|
|
1168
|
+
| 증상 | 해결 |
|
|
1169
|
+
|---|---|
|
|
1170
|
+
| `manifest.json errors: …` | 나열된 필드를 고칩니다 (5장) |
|
|
1171
|
+
| `dev`에서 `manifest.json variable errors: …` / `manifest.json port errors: …` | `variables`(5.5) / `ports`(5.6) 수정 |
|
|
1172
|
+
| `A Muscat plugin dev environment is already running: …` | `y`로 멈추거나 다른 `dev`를 먼저 종료 |
|
|
1173
|
+
| `Docker is installed but the daemon is not running` | Docker Desktop 실행 (또는 `sudo systemctl start docker`) |
|
|
1174
|
+
| `contributes declares ui "<entry>" but ui/<entry>.tsx … does not exist.` | 파일을 추가하거나 `contributes[].ui` 수정 |
|
|
1175
|
+
| `wheel download failed for <arch>` | 그 arch용 cp312 바이너리 wheel이 없는 의존성 — 다른 버전이나 패키지 선택 |
|
|
1176
|
+
| `can't build <arch> on this <host>: Docker has no <arch> emulation.` | `docker run --privileged --rm tonistiigi/binfmt --install <arch>`, 또는 `--arch <호스트 arch>` |
|
|
1177
|
+
| `Permission denied when deleting dist/` | 예전 빌드가 root 소유 파일을 남김: `sudo rm -rf dist` 한 번 |
|
|
1178
|
+
| 선언 안 된 포트로 백엔드가 멈춤 | 포트(28600-28999)를 `ports`에 추가 |
|
|
1179
|
+
| UI 프리뷰에 "no default export" | 엔트리가 `export default App`인지, `vite.config.ts`가 `muscat()`을 쓰는지 확인. `muscat-pk mcp`의 `check_project` 실행 |
|
|
1180
|
+
| `vite not found — UI will not load` | 프로젝트에서 `npm install` |
|
|
1181
|
+
| `AttributeError: router` | 로봇 SDK가 0.0.9.dev5보다 낮음 |
|
|
1182
|
+
| `plugin.call`에서 `RBPluginGatewayError` | gateway 연결이 끊겼거나 `timeout` 안에 응답이 없음 (UI에 `rb.serve`가 등록돼 있는지 확인) |
|
|
1183
|
+
| 스텝이 끝나지 않음 | `on_execute`에서 `await ctx.done()` 호출 |
|
|
1184
|
+
| 정지를 눌러도 플러그인이 계속 돎 | 루프마다 `await ctx.check_stop()` 호출 |
|
|
1185
|
+
| `set_variable`에서 `RuntimeError` | 새 변수 이름은 `RBP_`로 시작 |
|
|
1186
|
+
| `get_variable`이 `None` | 실행 중인 프로그램이 없음 (실행기 상태는 `IS_DEV=true`일 때도 `None`) |
|
|
1187
|
+
| 재시작 후 외부 호출 504 | gateway가 이전 소켓을 정리할 때까지 최대 1분 대기 |
|
|
1188
|
+
| 다른 버전의 로컬 설치가 실행됨 | 정상 동작 — 프로젝트 로컬 kit이 우선. `muscat-pk update` |
|