@rainbow-robotics/muscat-plugin-kit 1.10.1-dev.1 → 1.10.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.md +282 -282
- package/cli/README.md +274 -274
- package/cli/bin/create-muscat-plugin.mjs +8 -8
- package/cli/bin/muscat-pk.mjs +155 -155
- package/cli/bundle-templates.mjs +31 -31
- package/cli/docker/Dockerfile.cpp +28 -28
- package/cli/docker/Dockerfile.dev +30 -30
- package/cli/docker/Dockerfile.python +21 -21
- package/cli/m5-e2e.mjs +69 -69
- package/cli/prepack.mjs +10 -10
- package/cli/src/ai-rules.mjs +70 -70
- package/cli/src/build.mjs +447 -447
- package/cli/src/check-package.mjs +13 -13
- package/cli/src/completion.mjs +166 -166
- package/cli/src/cpp.mjs +78 -78
- package/cli/src/cppIde.mjs +75 -75
- package/cli/src/dev.mjs +165 -165
- package/cli/src/devBack.mjs +734 -727
- package/cli/src/devUi.mjs +247 -243
- package/cli/src/esbuild.mjs +56 -56
- package/cli/src/host_agent.py +223 -223
- package/cli/src/manifest.mjs +287 -287
- package/cli/src/mcp.mjs +1071 -1071
- package/cli/src/muscatLocal.mjs +209 -209
- package/cli/src/new.mjs +343 -343
- package/cli/src/pluginDevServer.mjs +111 -111
- package/cli/src/ports.mjs +193 -193
- package/cli/src/python.mjs +112 -112
- package/cli/src/ros2.mjs +109 -109
- package/cli/src/sdk.mjs +117 -117
- package/cli/src/storybook.mjs +168 -168
- package/cli/src/type.mjs +141 -141
- package/cli/src/uninstall.mjs +81 -81
- package/cli/src/update.mjs +295 -295
- package/cli/src/util.mjs +441 -441
- package/cli/src/zenohDev.mjs +83 -81
- package/cli/storybook-dist/_design-tokens.css +4957 -4957
- package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -27
- package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -28
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -46
- package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -73
- package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -93
- package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -74
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -28
- package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -84
- package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -57
- package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -41
- package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -60
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -44
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -56
- package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -51
- package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -55
- package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -52
- package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -93
- package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -49
- package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -108
- package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -106
- package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -89
- package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -64
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -65
- package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -68
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -216
- package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -90
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -45
- package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -50
- package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -102
- package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -31
- package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -36
- package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -49
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -89
- package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -116
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -74
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -99
- package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -123
- package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -135
- package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -69
- package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -49
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -91
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -38
- package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -112
- package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -75
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -51
- package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -84
- package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -102
- package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -78
- package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -57
- package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -177
- package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -64
- package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -117
- package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -50
- package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -30
- package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -54
- package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -57
- package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -48
- package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -103
- package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -27
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -67
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -45
- package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -27
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -48
- package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -46
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-D6JqZzDJ.js → Color-YHDXOIA2-B4IER17l.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-ESb7JAHg.js → Docs-C8GCHFtK.js} +3 -3
- package/cli/storybook-dist/assets/{Docs-6gD2kUo1.js → Docs-CgdEMPQP.js} +2 -2
- package/cli/storybook-dist/assets/{Docs-B9wd8Ok7.js → Docs-Ck3WHvk6.js} +3 -3
- package/cli/storybook-dist/assets/{Docs-CgFbWWXM.js → Docs-D9NHsoeZ.js} +2 -2
- package/cli/storybook-dist/assets/{Docs-DBQup8Bu.js → Docs-DNYQC7nl.js} +3 -3
- package/cli/storybook-dist/assets/{Docs-CMjovnSY.js → Docs-DUfeiM4I.js} +4 -4
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-BOUI1AHi.js → DocsRenderer-CFRXHY34-C0WhyDoR.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-DsYFMUh3.js → NxAvatar.stories-CwnK8AUF.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-CHfNcqsc.js → NxButton.stories-ueYdosfQ.js} +8 -8
- package/cli/storybook-dist/assets/{NxNumberInput.stories-BFw_4vFE.js → NxNumberInput.stories-M5ZCLbrK.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-DVfxyw4M.js → NxPagination.stories-bxxUnHEL.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BzyxUWoZ.js → NxPointerHoldButton.stories-BHxB2kr5.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-DmRdzBc1.js → NxRangeInput.stories-BKfr-fp4.js} +6 -6
- package/cli/storybook-dist/assets/{NxSelectBox.stories-B1KkNUOf.js → NxSelectBox.stories-CG8FuTBq.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-CyOdvfQ6.js → NxSlideRange.stories-BRTFtMTL.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-DrDBw0qp.js → NxSwitch.stories-Bwuyzn-R.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-DTB2aqNS.js → NxTextArea.stories-COq2sGqu.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-DU0rucmM.js → NxTextInput.stories-CjsBLz05.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-DTfOtXtp.js → RBAccordion.stories-BARwuZ0Q.js} +3 -3
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-9y9BjrUe.js → RBAccordionGroup.stories-COrPxETW.js} +10 -10
- package/cli/storybook-dist/assets/{RBAlert.stories-DLTAv9fT.js → RBAlert.stories-BQrkjZPt.js} +3 -3
- package/cli/storybook-dist/assets/{RBBadge.stories-BNMACewA.js → RBBadge.stories-BTsLL-lK.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-B3kXMAt5.js → RBBattery.stories-DAeQdJbc.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-Y-s8R2n7.js → RBCard.stories-HyhOE80R.js} +39 -39
- package/cli/storybook-dist/assets/{RBCheckBox.stories-DBuT9tL4.js → RBCheckBox.stories-KFk0WqX_.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-CWu1TL56.js → RBCheckIconButton.stories-CXbbktF5.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-sCu2MGCX.js → RBCheckLabelButton.stories-DWfYz_9Z.js} +17 -17
- package/cli/storybook-dist/assets/{RBChipTab.stories-BX9qLJkR.js → RBChipTab.stories-JPEgVFAS.js} +5 -5
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-35u1aJ-P.js → RBChipsBoxTab.stories-CTPuWXrQ.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-DAOZ5Ies.js → RBColorIcons.stories-DiFRZ5jk.js} +1 -1
- package/cli/storybook-dist/assets/RBCommand.stories-Pf1HAVIR.js +73 -0
- package/cli/storybook-dist/assets/{RBConfirm.stories-iMXcUh7Z.js → RBConfirm.stories-D1cEPUXk.js} +3 -3
- package/cli/storybook-dist/assets/{RBDateInput.stories-hK5rXECf.js → RBDateInput.stories-RPYpCjig.js} +1 -1
- package/cli/storybook-dist/assets/RBDialog.stories-DJH-0XSR.js +38 -0
- package/cli/storybook-dist/assets/{RBDivider.stories-BAK-iZ1Z.js → RBDivider.stories-uEj1vOgK.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-B2nqf2Nh.js → RBDrawer.stories-BiMzElb2.js} +12 -12
- package/cli/storybook-dist/assets/RBDropdownMenu.stories-Da_TBkla.js +86 -0
- package/cli/storybook-dist/assets/{RBEmptyView.stories-nCenrgBh.js → RBEmptyView.stories-BHeKGfPS.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-DRH7O3sp.js → RBIconButton.stories-NEsRFoYp.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-D3PyJXG8.js → RBIcons.stories-CcZ-TTLS.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-modQbt10.js → RBInlineLabelButton.stories-B0wpdQyR.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-BY14qVgR.js → RBJoyStick.stories-DEgBou3K.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-DzF7vGwm.js → RBLabelButton.stories-DT8drIsF.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-lF4FRmVh.js → RBLight.stories-Dh-aoeyG.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-XhrI7bsV.js → RBMenubar.stories-D-H7UqVh.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-DprllQDl.js → RBMultiRangeInput.stories-hlclv6jI.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-2v2gBJfC.js → RBPagination.stories-DWCeoZ_S.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-C8iLIZpv.js → RBPopover.stories-C514M09_.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-Th_HrWHI.js → RBPrompt.stories-BSItvfiT.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-CoHuLaN3.js → RBRadioBox.stories-BBb4YbFU.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-DULxE_ff.js → RBRadioButton.stories-CXyOGJCv.js} +1 -1
- package/cli/storybook-dist/assets/{RBRangeDateInput.stories-D8A9KBSS.js → RBRangeDateInput.stories-qvhubu0e.js} +1 -1
- package/cli/storybook-dist/assets/{RBResizable.stories-C41QfrQ3.js → RBResizable.stories-CZKeyGjH.js} +14 -14
- package/cli/storybook-dist/assets/{RBSelectBox.stories-C1gNmwzw.js → RBSelectBox.stories-CRKP3ybf.js} +1 -1
- package/cli/storybook-dist/assets/RBSheet.stories-sCM3ZJUa.js +39 -0
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-9LlgQ8nh.js → RBSideNumberInput.stories-Bb7gw8RF.js} +3 -3
- package/cli/storybook-dist/assets/{RBSlider.stories-oUgo5K9d.js → RBSlider.stories-23OAQU5z.js} +3 -3
- package/cli/storybook-dist/assets/{RBSpinner.stories-DDpohbY2.js → RBSpinner.stories-2xcQdcmt.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-gW-GN7QO.js → RBSwitch.stories-BACUaJm_.js} +7 -7
- package/cli/storybook-dist/assets/{RBTab.stories-CN-PV3HH.js → RBTab.stories-CDHE3sVs.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-BAZLpxtM.js → RBTable.stories-C0Cfhfl7.js} +29 -29
- package/cli/storybook-dist/assets/{RBTableRow.stories-CwjVrszB.js → RBTableRow.stories-BF8kIkpp.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-JuEo2sUL.js → RBTabs.stories-BdJWRq1o.js} +7 -7
- package/cli/storybook-dist/assets/{RBTextArea.stories-D0_Pv4qZ.js → RBTextArea.stories-CGy2xeYH.js} +3 -3
- package/cli/storybook-dist/assets/{RBTextField.stories-CCsOvZqV.js → RBTextField.stories-IfUOzxCM.js} +15 -15
- package/cli/storybook-dist/assets/{RBTextInput.stories-Cccn3VNm.js → RBTextInput.stories-VjXNFo4R.js} +3 -3
- package/cli/storybook-dist/assets/{RBToast.stories-I5617t0K.js → RBToast.stories-BzcobblV.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-DsgKjGsJ.js → RBToggle.stories-DuLE5mKv.js} +4 -4
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-BZEzSDuz.js → RBToggleGroup.stories-CuR0N0GJ.js} +5 -5
- package/cli/storybook-dist/assets/{RBTooltip.stories-4oSyAWsA.js → RBTooltip.stories--aAgNC8_.js} +1 -1
- package/cli/storybook-dist/assets/{SideNumberInput-DSKbd0bG.js → SideNumberInput-DyWQxYUj.js} +743 -743
- package/cli/storybook-dist/assets/{entry-preview-B35XREGs.js → entry-preview-D7IHsza_.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-BB797lmL.js → iframe-CbaIp5I-.js} +2 -2
- package/cli/storybook-dist/assets/{index-BU2567EP.js → index-C6CBgMuG.js} +3 -3
- package/cli/storybook-dist/assets/{preview-C7DaSWyc.js → preview-B-iYLDuW.js} +2 -2
- package/cli/storybook-dist/assets/{preview-CbuRHit4.js → preview-CRWqc9DH.js} +1 -1
- package/cli/storybook-dist/assets/{react-resizable-panels.browser-ChIvlwo7.js → react-resizable-panels.browser-8hC8dr-6.js} +8 -8
- package/cli/storybook-dist/iframe.html +2 -2
- package/cli/storybook-dist/index.html +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/.mcp.json +8 -8
- package/cli/templates/common/README.md +1231 -1231
- package/cli/templates/common/assets/icon.svg +45 -45
- package/cli/templates/common/manifest.json +26 -26
- package/cli/templates/common/package.json +22 -22
- package/cli/templates/common/py/main.py +125 -125
- package/cli/templates/common/tsconfig.json +13 -13
- package/cli/templates/common/ui/content.tsx +216 -216
- package/cli/templates/common/ui/style.css +6 -6
- package/cli/templates/common/vite.config.ts +10 -10
- package/cli/templates/cpp/cpp/main.cpp +73 -73
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -25
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -10
- package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -18
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -11
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +23 -23
- package/cli/vite.d.mts +25 -25
- package/cli/vite.mjs +292 -292
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBCommand.stories-BSqXDkKw.js +0 -73
- package/cli/storybook-dist/assets/RBDialog.stories-BAktz-ku.js +0 -38
- package/cli/storybook-dist/assets/RBDropdownMenu.stories-DbW2fWAj.js +0 -86
- package/cli/storybook-dist/assets/RBSheet.stories-_6VH-RUm.js +0 -39
package/cli/src/cppIde.mjs
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
// Editor (IntelliSense) support for C++ backends. The rb_sdk headers only exist inside the dev image,
|
|
2
|
-
// so a host editor flags every SDK name. Copy them to .muscat-dev/sysroot and point
|
|
3
|
-
// .vscode/c_cpp_properties.json at them, using the include dirs / defines rb_sdk's CMake target exports.
|
|
4
|
-
// The build never reads these copies — it runs in the image.
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { resolve } from "node:path";
|
|
8
|
-
import { cppSdkFiles, log } from "./util.mjs";
|
|
9
|
-
|
|
10
|
-
const SYSROOT = ".muscat-dev/sysroot";
|
|
11
|
-
const CONFIG_NAME = "Muscat";
|
|
12
|
-
const COPY = ["/usr/include/rainbow", "/usr/include/eigen3", "/usr/lib/cmake/rb_sdk/rb_sdkTargets.cmake"];
|
|
13
|
-
|
|
14
|
-
function imageId(image) {
|
|
15
|
-
const r = spawnSync("docker", ["image", "inspect", "-f", "{{.Id}}", image], { encoding: "utf8" });
|
|
16
|
-
return r.status === 0 ? r.stdout.trim() : null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Copy the SDK headers out of `image` (once per image build) and write the VS Code C/C++ config. */
|
|
20
|
-
export function syncCppIde(root, image) {
|
|
21
|
-
if (!cppSdkFiles(root).length) return;
|
|
22
|
-
const id = imageId(image);
|
|
23
|
-
if (!id) return;
|
|
24
|
-
const sysroot = resolve(root, SYSROOT);
|
|
25
|
-
const stamp = resolve(sysroot, ".image-id");
|
|
26
|
-
if (!existsSync(stamp) || readFileSync(stamp, "utf8") !== id) {
|
|
27
|
-
rmSync(sysroot, { recursive: true, force: true });
|
|
28
|
-
const create = spawnSync("docker", ["create", image], { encoding: "utf8" });
|
|
29
|
-
if (create.status !== 0) return log.warn("could not copy SDK headers for the editor (docker create failed).");
|
|
30
|
-
const container = create.stdout.trim();
|
|
31
|
-
try {
|
|
32
|
-
for (const src of COPY) {
|
|
33
|
-
const dest = resolve(sysroot, src.slice(1));
|
|
34
|
-
mkdirSync(resolve(dest, ".."), { recursive: true });
|
|
35
|
-
spawnSync("docker", ["cp", `${container}:${src}`, dest], { stdio: "ignore" });
|
|
36
|
-
}
|
|
37
|
-
} finally {
|
|
38
|
-
spawnSync("docker", ["rm", container], { stdio: "ignore" });
|
|
39
|
-
}
|
|
40
|
-
writeFileSync(stamp, id);
|
|
41
|
-
log.step(`SDK headers for the editor → ${SYSROOT}`);
|
|
42
|
-
}
|
|
43
|
-
writeCppProperties(root, readTarget(sysroot));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// INTERFACE_INCLUDE_DIRECTORIES / INTERFACE_COMPILE_DEFINITIONS of rb_sdk::rb_sdk, mapped into the sysroot.
|
|
47
|
-
function readTarget(sysroot) {
|
|
48
|
-
const file = resolve(sysroot, "usr/lib/cmake/rb_sdk/rb_sdkTargets.cmake");
|
|
49
|
-
const text = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
50
|
-
const list = (key) => (text.match(new RegExp(`${key} "([^"]*)"`))?.[1] ?? "").split(";").filter(Boolean);
|
|
51
|
-
const prefix = `\${workspaceFolder}/${SYSROOT}/usr`;
|
|
52
|
-
const includes = list("INTERFACE_INCLUDE_DIRECTORIES").map((d) => d.replace("${_IMPORT_PREFIX}", prefix));
|
|
53
|
-
return {
|
|
54
|
-
includePath: [...(includes.length ? includes : [`${prefix}/include`]), `${prefix}/include/eigen3`],
|
|
55
|
-
defines: list("INTERFACE_COMPILE_DEFINITIONS"),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Keeps other configurations; ours is replaced in place or put first so VS Code picks it by default.
|
|
60
|
-
function writeCppProperties(root, { includePath, defines }) {
|
|
61
|
-
const file = resolve(root, ".vscode", "c_cpp_properties.json");
|
|
62
|
-
let props = { configurations: [], version: 4 };
|
|
63
|
-
if (existsSync(file)) {
|
|
64
|
-
try {
|
|
65
|
-
props = JSON.parse(readFileSync(file, "utf8"));
|
|
66
|
-
} catch {
|
|
67
|
-
return log.warn(".vscode/c_cpp_properties.json is not plain JSON — add the SDK include paths yourself.");
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const config = { name: CONFIG_NAME, includePath: ["${workspaceFolder}/**", ...includePath], defines, cppStandard: "c++20" };
|
|
71
|
-
const configs = (props.configurations ?? []).filter((c) => c.name !== CONFIG_NAME);
|
|
72
|
-
props.configurations = [config, ...configs];
|
|
73
|
-
mkdirSync(resolve(root, ".vscode"), { recursive: true });
|
|
74
|
-
writeFileSync(file, JSON.stringify(props, null, 2) + "\n");
|
|
75
|
-
}
|
|
1
|
+
// Editor (IntelliSense) support for C++ backends. The rb_sdk headers only exist inside the dev image,
|
|
2
|
+
// so a host editor flags every SDK name. Copy them to .muscat-dev/sysroot and point
|
|
3
|
+
// .vscode/c_cpp_properties.json at them, using the include dirs / defines rb_sdk's CMake target exports.
|
|
4
|
+
// The build never reads these copies — it runs in the image.
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { cppSdkFiles, log } from "./util.mjs";
|
|
9
|
+
|
|
10
|
+
const SYSROOT = ".muscat-dev/sysroot";
|
|
11
|
+
const CONFIG_NAME = "Muscat";
|
|
12
|
+
const COPY = ["/usr/include/rainbow", "/usr/include/eigen3", "/usr/lib/cmake/rb_sdk/rb_sdkTargets.cmake"];
|
|
13
|
+
|
|
14
|
+
function imageId(image) {
|
|
15
|
+
const r = spawnSync("docker", ["image", "inspect", "-f", "{{.Id}}", image], { encoding: "utf8" });
|
|
16
|
+
return r.status === 0 ? r.stdout.trim() : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Copy the SDK headers out of `image` (once per image build) and write the VS Code C/C++ config. */
|
|
20
|
+
export function syncCppIde(root, image) {
|
|
21
|
+
if (!cppSdkFiles(root).length) return;
|
|
22
|
+
const id = imageId(image);
|
|
23
|
+
if (!id) return;
|
|
24
|
+
const sysroot = resolve(root, SYSROOT);
|
|
25
|
+
const stamp = resolve(sysroot, ".image-id");
|
|
26
|
+
if (!existsSync(stamp) || readFileSync(stamp, "utf8") !== id) {
|
|
27
|
+
rmSync(sysroot, { recursive: true, force: true });
|
|
28
|
+
const create = spawnSync("docker", ["create", image], { encoding: "utf8" });
|
|
29
|
+
if (create.status !== 0) return log.warn("could not copy SDK headers for the editor (docker create failed).");
|
|
30
|
+
const container = create.stdout.trim();
|
|
31
|
+
try {
|
|
32
|
+
for (const src of COPY) {
|
|
33
|
+
const dest = resolve(sysroot, src.slice(1));
|
|
34
|
+
mkdirSync(resolve(dest, ".."), { recursive: true });
|
|
35
|
+
spawnSync("docker", ["cp", `${container}:${src}`, dest], { stdio: "ignore" });
|
|
36
|
+
}
|
|
37
|
+
} finally {
|
|
38
|
+
spawnSync("docker", ["rm", container], { stdio: "ignore" });
|
|
39
|
+
}
|
|
40
|
+
writeFileSync(stamp, id);
|
|
41
|
+
log.step(`SDK headers for the editor → ${SYSROOT}`);
|
|
42
|
+
}
|
|
43
|
+
writeCppProperties(root, readTarget(sysroot));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// INTERFACE_INCLUDE_DIRECTORIES / INTERFACE_COMPILE_DEFINITIONS of rb_sdk::rb_sdk, mapped into the sysroot.
|
|
47
|
+
function readTarget(sysroot) {
|
|
48
|
+
const file = resolve(sysroot, "usr/lib/cmake/rb_sdk/rb_sdkTargets.cmake");
|
|
49
|
+
const text = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
50
|
+
const list = (key) => (text.match(new RegExp(`${key} "([^"]*)"`))?.[1] ?? "").split(";").filter(Boolean);
|
|
51
|
+
const prefix = `\${workspaceFolder}/${SYSROOT}/usr`;
|
|
52
|
+
const includes = list("INTERFACE_INCLUDE_DIRECTORIES").map((d) => d.replace("${_IMPORT_PREFIX}", prefix));
|
|
53
|
+
return {
|
|
54
|
+
includePath: [...(includes.length ? includes : [`${prefix}/include`]), `${prefix}/include/eigen3`],
|
|
55
|
+
defines: list("INTERFACE_COMPILE_DEFINITIONS"),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Keeps other configurations; ours is replaced in place or put first so VS Code picks it by default.
|
|
60
|
+
function writeCppProperties(root, { includePath, defines }) {
|
|
61
|
+
const file = resolve(root, ".vscode", "c_cpp_properties.json");
|
|
62
|
+
let props = { configurations: [], version: 4 };
|
|
63
|
+
if (existsSync(file)) {
|
|
64
|
+
try {
|
|
65
|
+
props = JSON.parse(readFileSync(file, "utf8"));
|
|
66
|
+
} catch {
|
|
67
|
+
return log.warn(".vscode/c_cpp_properties.json is not plain JSON — add the SDK include paths yourself.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const config = { name: CONFIG_NAME, includePath: ["${workspaceFolder}/**", ...includePath], defines, cppStandard: "c++20" };
|
|
71
|
+
const configs = (props.configurations ?? []).filter((c) => c.name !== CONFIG_NAME);
|
|
72
|
+
props.configurations = [config, ...configs];
|
|
73
|
+
mkdirSync(resolve(root, ".vscode"), { recursive: true });
|
|
74
|
+
writeFileSync(file, JSON.stringify(props, null, 2) + "\n");
|
|
75
|
+
}
|
package/cli/src/dev.mjs
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
// muscat-pk dev — run the local Muscat runtime (gateway, MediaMTX, common), dev:ui, and dev:back together.
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { connect as netConnect } from "node:net";
|
|
5
|
-
import { resolve } from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { runDevUi } from "./devUi.mjs";
|
|
8
|
-
import { stopDevBackContainers } from "./devBack.mjs";
|
|
9
|
-
import { ensureLocalMuscat, GATEWAY_PORT, isPortInUse, robotHasMuscat } from "./muscatLocal.mjs";
|
|
10
|
-
import { assertManifestPorts, assertManifestVariables } from "./manifest.mjs";
|
|
11
|
-
import {
|
|
12
|
-
ensureDocker,
|
|
13
|
-
ensureIdeVenv,
|
|
14
|
-
findPluginRoot,
|
|
15
|
-
log,
|
|
16
|
-
prompt,
|
|
17
|
-
promptRobotConnection,
|
|
18
|
-
venvPython,
|
|
19
|
-
} from "./util.mjs";
|
|
20
|
-
import { ensureRemoteZenohEnv, isRemoteRobot, isWsl } from "./zenohDev.mjs";
|
|
21
|
-
|
|
22
|
-
const HOST_AGENT_PATH = fileURLToPath(new URL("./host_agent.py", import.meta.url));
|
|
23
|
-
const canAutoStartHostAgent = () => process.platform === "darwin" || process.platform === "win32" || isWsl();
|
|
24
|
-
|
|
25
|
-
function waitForHostAgent(port, { timeoutMs = 30_000 } = {}) {
|
|
26
|
-
const deadline = Date.now() + timeoutMs;
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
const probe = () => {
|
|
29
|
-
const sock = netConnect(port, "127.0.0.1");
|
|
30
|
-
sock.once("connect", () => { sock.destroy(); resolve(true); });
|
|
31
|
-
sock.once("error", () => {
|
|
32
|
-
sock.destroy();
|
|
33
|
-
if (Date.now() >= deadline) {
|
|
34
|
-
reject(new Error(`host SDK agent did not open port ${port} in time`));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
setTimeout(probe, 300);
|
|
38
|
-
});
|
|
39
|
-
};
|
|
40
|
-
probe();
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const DEFAULT_PROXY_PORT = 47100;
|
|
45
|
-
|
|
46
|
-
// Start the host-side Zenoh byte-proxy (host_agent.py) for a remote robot —
|
|
47
|
-
// see host_agent.py for why a Docker Desktop container can't join the
|
|
48
|
-
// robot's Zenoh mesh itself. Sets MUSCAT_PROXY_ENDPOINT for dev:back.
|
|
49
|
-
// Returns the child process, or null if not applicable / disabled.
|
|
50
|
-
async function maybeStartHostAgent(root, { robotIp, robotToken }) {
|
|
51
|
-
if (process.env.MUSCAT_PROXY_ENDPOINT) {
|
|
52
|
-
log.step(`Zenoh via host byte-proxy → ${process.env.MUSCAT_PROXY_ENDPOINT}`);
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
if (process.env.MUSCAT_HOST_AGENT_DISABLE === "1") return null;
|
|
56
|
-
if (!canAutoStartHostAgent() || !isRemoteRobot(robotIp)) return null;
|
|
57
|
-
|
|
58
|
-
const agentPath = process.env.MUSCAT_HOST_AGENT_SCRIPT || HOST_AGENT_PATH;
|
|
59
|
-
if (!existsSync(agentPath)) return null;
|
|
60
|
-
|
|
61
|
-
if (!ensureIdeVenv(root)) {
|
|
62
|
-
log.warn("host Zenoh byte-proxy skipped: could not prepare .venv");
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// advertiseHost/zenohPort = the host_agent's OWN robot-facing Zenoh session
|
|
67
|
-
// (host LAN IP + port the robot reverse-dials into). The container never
|
|
68
|
-
// sees or needs these — it only talks to the proxyPort below.
|
|
69
|
-
const zenoh = await ensureRemoteZenohEnv(robotIp, { log });
|
|
70
|
-
const advertiseHost = zenoh?.advertiseHost || process.env.MUSCAT_ZENOH_ADVERTISE_HOST;
|
|
71
|
-
const zenohPort = zenoh?.zenohPort || Number(process.env.MUSCAT_ZENOH_PORT);
|
|
72
|
-
if (!advertiseHost || !zenohPort) {
|
|
73
|
-
log.warn("host Zenoh byte-proxy skipped: could not determine host LAN IP / port");
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
const proxyPort = Number(process.env.MUSCAT_PROXY_PORT) || DEFAULT_PROXY_PORT;
|
|
77
|
-
|
|
78
|
-
log.step("starting host Zenoh byte-proxy (Docker Desktop query-routing workaround)...");
|
|
79
|
-
const py = venvPython(resolve(root, ".venv"));
|
|
80
|
-
const child = spawn(py, [agentPath], {
|
|
81
|
-
cwd: root,
|
|
82
|
-
stdio: ["ignore", "inherit", "inherit"],
|
|
83
|
-
env: {
|
|
84
|
-
...process.env,
|
|
85
|
-
MUSCAT_ROBOT_HOST: robotIp,
|
|
86
|
-
MUSCAT_ROBOT_TOKEN: robotToken,
|
|
87
|
-
MUSCAT_ZENOH_ADVERTISE_HOST: advertiseHost,
|
|
88
|
-
MUSCAT_ZENOH_PORT: String(zenohPort),
|
|
89
|
-
MUSCAT_PROXY_PORT: String(proxyPort),
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
await waitForHostAgent(proxyPort);
|
|
95
|
-
} catch (e) {
|
|
96
|
-
child.kill();
|
|
97
|
-
throw e;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// advertiseHost (not host.docker.internal): a Docker Desktop container run
|
|
101
|
-
// under `dev` shares the local Muscat container's netns (--network container:),
|
|
102
|
-
// which has no host-gateway alias configured — the host's real LAN IP
|
|
103
|
-
// always resolves regardless of netns mode.
|
|
104
|
-
const proxyEndpoint = `${advertiseHost}:${proxyPort}`;
|
|
105
|
-
process.env.MUSCAT_PROXY_ENDPOINT = proxyEndpoint;
|
|
106
|
-
process.env.MUSCAT_PROXY_PORT = String(proxyPort);
|
|
107
|
-
log.step(`Zenoh via host byte-proxy → ${proxyEndpoint}`);
|
|
108
|
-
return child;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export async function runDev(args = []) {
|
|
112
|
-
const root = findPluginRoot();
|
|
113
|
-
assertManifestVariables(root);
|
|
114
|
-
const portRanges = assertManifestPorts(root);
|
|
115
|
-
ensureDocker();
|
|
116
|
-
const { robotIp, robotToken } = await promptRobotConnection(args);
|
|
117
|
-
process.env.MUSCAT_ROBOT_HOST = robotIp;
|
|
118
|
-
if (robotToken) process.env.MUSCAT_ROBOT_TOKEN = robotToken;
|
|
119
|
-
|
|
120
|
-
const remoteRobot = isRemoteRobot(robotIp);
|
|
121
|
-
const hostAgent = await maybeStartHostAgent(root, { robotIp, robotToken });
|
|
122
|
-
const proxyEndpoint = process.env.MUSCAT_PROXY_ENDPOINT || "";
|
|
123
|
-
// No proxy (Linux, disabled, ...): fall back to the container reverse-dialing
|
|
124
|
-
// the robot itself — the local Muscat then publishes the zenoh port for it.
|
|
125
|
-
if (!proxyEndpoint) await ensureRemoteZenohEnv(robotIp, { log });
|
|
126
|
-
|
|
127
|
-
// A robot running Muscat is the gateway (:10000) and common for both the UI and the backend.
|
|
128
|
-
// Otherwise the local Muscat runtime is; the backend joins its netns, so the plugin's declared
|
|
129
|
-
// ports are published from it too.
|
|
130
|
-
const muscat = (await robotHasMuscat(robotIp)) ? null : await ensureLocalMuscat({
|
|
131
|
-
pluginPorts: portRanges,
|
|
132
|
-
zenohPort: remoteRobot && !proxyEndpoint ? Number(process.env.MUSCAT_ZENOH_PORT) || null : null,
|
|
133
|
-
}).catch((e) => { log.warn(`local Muscat: ${e.message}`); return null; });
|
|
134
|
-
if (muscat) {
|
|
135
|
-
process.env.MUSCAT_PK_MUSCAT_CONTAINER = muscat;
|
|
136
|
-
} else if (remoteRobot || (await isPortInUse(GATEWAY_PORT))) {
|
|
137
|
-
// e.g. developing on the robot PC: its Muscat already serves the gateway; the backend reaches it via the host.
|
|
138
|
-
} else {
|
|
139
|
-
log.warn(`no local Muscat on :${GATEWAY_PORT} — the plugin UI cannot reach the backend.`);
|
|
140
|
-
const answer = await prompt(" Proceed anyway? [y/N]: ");
|
|
141
|
-
if (answer.toLowerCase() !== "y") return;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// dev:back in its own process: its docker build / pip install / cmake build are synchronous and would
|
|
145
|
-
// freeze dev:ui's server on this event loop until they finish (or Ctrl+C killed them).
|
|
146
|
-
// A Bun binary re-runs itself with just the command; under Node argv[1] is the script.
|
|
147
|
-
const backend = spawn(process.execPath, [...(typeof Bun === "undefined" ? [process.argv[1]] : []), "dev:back"], {
|
|
148
|
-
stdio: ["ignore", "inherit", "inherit"],
|
|
149
|
-
});
|
|
150
|
-
backend.on("exit", (code) => code && log.warn(`dev:back exited (${code})`));
|
|
151
|
-
|
|
152
|
-
process.env.MUSCAT_PK_MANAGED_DEV = "1";
|
|
153
|
-
|
|
154
|
-
const cleanup = () => {
|
|
155
|
-
if (hostAgent && hostAgent.exitCode === null) hostAgent.kill();
|
|
156
|
-
if (backend.exitCode === null) backend.kill();
|
|
157
|
-
stopDevBackContainers();
|
|
158
|
-
};
|
|
159
|
-
process.once("SIGINT", cleanup);
|
|
160
|
-
process.once("SIGTERM", cleanup);
|
|
161
|
-
process.once("SIGHUP", cleanup); // terminal closed (VS Code re-running Dev disposes it)
|
|
162
|
-
process.once("exit", cleanup);
|
|
163
|
-
|
|
164
|
-
await runDevUi(args);
|
|
165
|
-
}
|
|
1
|
+
// muscat-pk dev — run the local Muscat runtime (gateway, MediaMTX, common), dev:ui, and dev:back together.
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { connect as netConnect } from "node:net";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { runDevUi } from "./devUi.mjs";
|
|
8
|
+
import { stopDevBackContainers } from "./devBack.mjs";
|
|
9
|
+
import { ensureLocalMuscat, GATEWAY_PORT, isPortInUse, robotHasMuscat } from "./muscatLocal.mjs";
|
|
10
|
+
import { assertManifestPorts, assertManifestVariables } from "./manifest.mjs";
|
|
11
|
+
import {
|
|
12
|
+
ensureDocker,
|
|
13
|
+
ensureIdeVenv,
|
|
14
|
+
findPluginRoot,
|
|
15
|
+
log,
|
|
16
|
+
prompt,
|
|
17
|
+
promptRobotConnection,
|
|
18
|
+
venvPython,
|
|
19
|
+
} from "./util.mjs";
|
|
20
|
+
import { ensureRemoteZenohEnv, isRemoteRobot, isWsl } from "./zenohDev.mjs";
|
|
21
|
+
|
|
22
|
+
const HOST_AGENT_PATH = fileURLToPath(new URL("./host_agent.py", import.meta.url));
|
|
23
|
+
const canAutoStartHostAgent = () => process.platform === "darwin" || process.platform === "win32" || isWsl();
|
|
24
|
+
|
|
25
|
+
function waitForHostAgent(port, { timeoutMs = 30_000 } = {}) {
|
|
26
|
+
const deadline = Date.now() + timeoutMs;
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const probe = () => {
|
|
29
|
+
const sock = netConnect(port, "127.0.0.1");
|
|
30
|
+
sock.once("connect", () => { sock.destroy(); resolve(true); });
|
|
31
|
+
sock.once("error", () => {
|
|
32
|
+
sock.destroy();
|
|
33
|
+
if (Date.now() >= deadline) {
|
|
34
|
+
reject(new Error(`host SDK agent did not open port ${port} in time`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
setTimeout(probe, 300);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
probe();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_PROXY_PORT = 47100;
|
|
45
|
+
|
|
46
|
+
// Start the host-side Zenoh byte-proxy (host_agent.py) for a remote robot —
|
|
47
|
+
// see host_agent.py for why a Docker Desktop container can't join the
|
|
48
|
+
// robot's Zenoh mesh itself. Sets MUSCAT_PROXY_ENDPOINT for dev:back.
|
|
49
|
+
// Returns the child process, or null if not applicable / disabled.
|
|
50
|
+
async function maybeStartHostAgent(root, { robotIp, robotToken }) {
|
|
51
|
+
if (process.env.MUSCAT_PROXY_ENDPOINT) {
|
|
52
|
+
log.step(`Zenoh via host byte-proxy → ${process.env.MUSCAT_PROXY_ENDPOINT}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (process.env.MUSCAT_HOST_AGENT_DISABLE === "1") return null;
|
|
56
|
+
if (!canAutoStartHostAgent() || !isRemoteRobot(robotIp)) return null;
|
|
57
|
+
|
|
58
|
+
const agentPath = process.env.MUSCAT_HOST_AGENT_SCRIPT || HOST_AGENT_PATH;
|
|
59
|
+
if (!existsSync(agentPath)) return null;
|
|
60
|
+
|
|
61
|
+
if (!ensureIdeVenv(root)) {
|
|
62
|
+
log.warn("host Zenoh byte-proxy skipped: could not prepare .venv");
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// advertiseHost/zenohPort = the host_agent's OWN robot-facing Zenoh session
|
|
67
|
+
// (host LAN IP + port the robot reverse-dials into). The container never
|
|
68
|
+
// sees or needs these — it only talks to the proxyPort below.
|
|
69
|
+
const zenoh = await ensureRemoteZenohEnv(robotIp, { log });
|
|
70
|
+
const advertiseHost = zenoh?.advertiseHost || process.env.MUSCAT_ZENOH_ADVERTISE_HOST;
|
|
71
|
+
const zenohPort = zenoh?.zenohPort || Number(process.env.MUSCAT_ZENOH_PORT);
|
|
72
|
+
if (!advertiseHost || !zenohPort) {
|
|
73
|
+
log.warn("host Zenoh byte-proxy skipped: could not determine host LAN IP / port");
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const proxyPort = Number(process.env.MUSCAT_PROXY_PORT) || DEFAULT_PROXY_PORT;
|
|
77
|
+
|
|
78
|
+
log.step("starting host Zenoh byte-proxy (Docker Desktop query-routing workaround)...");
|
|
79
|
+
const py = venvPython(resolve(root, ".venv"));
|
|
80
|
+
const child = spawn(py, [agentPath], {
|
|
81
|
+
cwd: root,
|
|
82
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
83
|
+
env: {
|
|
84
|
+
...process.env,
|
|
85
|
+
MUSCAT_ROBOT_HOST: robotIp,
|
|
86
|
+
MUSCAT_ROBOT_TOKEN: robotToken,
|
|
87
|
+
MUSCAT_ZENOH_ADVERTISE_HOST: advertiseHost,
|
|
88
|
+
MUSCAT_ZENOH_PORT: String(zenohPort),
|
|
89
|
+
MUSCAT_PROXY_PORT: String(proxyPort),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await waitForHostAgent(proxyPort);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
child.kill();
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// advertiseHost (not host.docker.internal): a Docker Desktop container run
|
|
101
|
+
// under `dev` shares the local Muscat container's netns (--network container:),
|
|
102
|
+
// which has no host-gateway alias configured — the host's real LAN IP
|
|
103
|
+
// always resolves regardless of netns mode.
|
|
104
|
+
const proxyEndpoint = `${advertiseHost}:${proxyPort}`;
|
|
105
|
+
process.env.MUSCAT_PROXY_ENDPOINT = proxyEndpoint;
|
|
106
|
+
process.env.MUSCAT_PROXY_PORT = String(proxyPort);
|
|
107
|
+
log.step(`Zenoh via host byte-proxy → ${proxyEndpoint}`);
|
|
108
|
+
return child;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function runDev(args = []) {
|
|
112
|
+
const root = findPluginRoot();
|
|
113
|
+
assertManifestVariables(root);
|
|
114
|
+
const portRanges = assertManifestPorts(root);
|
|
115
|
+
ensureDocker();
|
|
116
|
+
const { robotIp, robotToken } = await promptRobotConnection(args);
|
|
117
|
+
process.env.MUSCAT_ROBOT_HOST = robotIp;
|
|
118
|
+
if (robotToken) process.env.MUSCAT_ROBOT_TOKEN = robotToken;
|
|
119
|
+
|
|
120
|
+
const remoteRobot = isRemoteRobot(robotIp);
|
|
121
|
+
const hostAgent = await maybeStartHostAgent(root, { robotIp, robotToken });
|
|
122
|
+
const proxyEndpoint = process.env.MUSCAT_PROXY_ENDPOINT || "";
|
|
123
|
+
// No proxy (Linux, disabled, ...): fall back to the container reverse-dialing
|
|
124
|
+
// the robot itself — the local Muscat then publishes the zenoh port for it.
|
|
125
|
+
if (!proxyEndpoint) await ensureRemoteZenohEnv(robotIp, { log });
|
|
126
|
+
|
|
127
|
+
// A robot running Muscat is the gateway (:10000) and common for both the UI and the backend.
|
|
128
|
+
// Otherwise the local Muscat runtime is; the backend joins its netns, so the plugin's declared
|
|
129
|
+
// ports are published from it too.
|
|
130
|
+
const muscat = (await robotHasMuscat(robotIp)) ? null : await ensureLocalMuscat({
|
|
131
|
+
pluginPorts: portRanges,
|
|
132
|
+
zenohPort: remoteRobot && !proxyEndpoint ? Number(process.env.MUSCAT_ZENOH_PORT) || null : null,
|
|
133
|
+
}).catch((e) => { log.warn(`local Muscat: ${e.message}`); return null; });
|
|
134
|
+
if (muscat) {
|
|
135
|
+
process.env.MUSCAT_PK_MUSCAT_CONTAINER = muscat;
|
|
136
|
+
} else if (remoteRobot || (await isPortInUse(GATEWAY_PORT))) {
|
|
137
|
+
// e.g. developing on the robot PC: its Muscat already serves the gateway; the backend reaches it via the host.
|
|
138
|
+
} else {
|
|
139
|
+
log.warn(`no local Muscat on :${GATEWAY_PORT} — the plugin UI cannot reach the backend.`);
|
|
140
|
+
const answer = await prompt(" Proceed anyway? [y/N]: ");
|
|
141
|
+
if (answer.toLowerCase() !== "y") return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// dev:back in its own process: its docker build / pip install / cmake build are synchronous and would
|
|
145
|
+
// freeze dev:ui's server on this event loop until they finish (or Ctrl+C killed them).
|
|
146
|
+
// A Bun binary re-runs itself with just the command; under Node argv[1] is the script.
|
|
147
|
+
const backend = spawn(process.execPath, [...(typeof Bun === "undefined" ? [process.argv[1]] : []), "dev:back"], {
|
|
148
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
149
|
+
});
|
|
150
|
+
backend.on("exit", (code) => code && log.warn(`dev:back exited (${code})`));
|
|
151
|
+
|
|
152
|
+
process.env.MUSCAT_PK_MANAGED_DEV = "1";
|
|
153
|
+
|
|
154
|
+
const cleanup = () => {
|
|
155
|
+
if (hostAgent && hostAgent.exitCode === null) hostAgent.kill();
|
|
156
|
+
if (backend.exitCode === null) backend.kill();
|
|
157
|
+
stopDevBackContainers();
|
|
158
|
+
};
|
|
159
|
+
process.once("SIGINT", cleanup);
|
|
160
|
+
process.once("SIGTERM", cleanup);
|
|
161
|
+
process.once("SIGHUP", cleanup); // terminal closed (VS Code re-running Dev disposes it)
|
|
162
|
+
process.once("exit", cleanup);
|
|
163
|
+
|
|
164
|
+
await runDevUi(args);
|
|
165
|
+
}
|