@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.
Files changed (205) hide show
  1. package/README.md +282 -282
  2. package/cli/README.md +274 -274
  3. package/cli/bin/create-muscat-plugin.mjs +8 -8
  4. package/cli/bin/muscat-pk.mjs +155 -155
  5. package/cli/bundle-templates.mjs +31 -31
  6. package/cli/docker/Dockerfile.cpp +28 -28
  7. package/cli/docker/Dockerfile.dev +30 -30
  8. package/cli/docker/Dockerfile.python +21 -21
  9. package/cli/m5-e2e.mjs +69 -69
  10. package/cli/prepack.mjs +10 -10
  11. package/cli/src/ai-rules.mjs +70 -70
  12. package/cli/src/build.mjs +447 -447
  13. package/cli/src/check-package.mjs +13 -13
  14. package/cli/src/completion.mjs +166 -166
  15. package/cli/src/cpp.mjs +78 -78
  16. package/cli/src/cppIde.mjs +75 -75
  17. package/cli/src/dev.mjs +165 -165
  18. package/cli/src/devBack.mjs +734 -727
  19. package/cli/src/devUi.mjs +247 -243
  20. package/cli/src/esbuild.mjs +56 -56
  21. package/cli/src/host_agent.py +223 -223
  22. package/cli/src/manifest.mjs +287 -287
  23. package/cli/src/mcp.mjs +1071 -1071
  24. package/cli/src/muscatLocal.mjs +209 -209
  25. package/cli/src/new.mjs +343 -343
  26. package/cli/src/pluginDevServer.mjs +111 -111
  27. package/cli/src/ports.mjs +193 -193
  28. package/cli/src/python.mjs +112 -112
  29. package/cli/src/ros2.mjs +109 -109
  30. package/cli/src/sdk.mjs +117 -117
  31. package/cli/src/storybook.mjs +168 -168
  32. package/cli/src/type.mjs +141 -141
  33. package/cli/src/uninstall.mjs +81 -81
  34. package/cli/src/update.mjs +295 -295
  35. package/cli/src/util.mjs +441 -441
  36. package/cli/src/zenohDev.mjs +83 -81
  37. package/cli/storybook-dist/_design-tokens.css +4957 -4957
  38. package/cli/storybook-dist/_stories-src/NxAvatar.stories.tsx +27 -27
  39. package/cli/storybook-dist/_stories-src/NxPagination.stories.tsx +28 -28
  40. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordion.stories.tsx +46 -46
  41. package/cli/storybook-dist/_stories-src/RBAccordion/RBAccordionGroup.stories.tsx +73 -73
  42. package/cli/storybook-dist/_stories-src/RBBadge/RBBadge.stories.tsx +93 -93
  43. package/cli/storybook-dist/_stories-src/RBBattery/RBBattery.stories.tsx +74 -74
  44. package/cli/storybook-dist/_stories-src/RBButton/RBCheckIconButton.stories.tsx +28 -28
  45. package/cli/storybook-dist/_stories-src/RBButton/RBCheckLabelButton.stories.tsx +84 -84
  46. package/cli/storybook-dist/_stories-src/RBButton/RBIconButton.stories.tsx +57 -57
  47. package/cli/storybook-dist/_stories-src/RBButton/RBInlineLabelButton.stories.tsx +41 -41
  48. package/cli/storybook-dist/_stories-src/RBButton/RBLabelButton.stories.tsx +60 -60
  49. package/cli/storybook-dist/_stories-src/RBDatePicker/RBDateInput.stories.tsx +44 -44
  50. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +56 -56
  51. package/cli/storybook-dist/_stories-src/RBDialog/RBAlert.stories.tsx +51 -51
  52. package/cli/storybook-dist/_stories-src/RBDialog/RBConfirm.stories.tsx +55 -55
  53. package/cli/storybook-dist/_stories-src/RBDialog/RBPrompt.stories.tsx +52 -52
  54. package/cli/storybook-dist/_stories-src/RBDivider/RBDivider.stories.tsx +93 -93
  55. package/cli/storybook-dist/_stories-src/RBEmptyView/RBEmptyView.stories.tsx +49 -49
  56. package/cli/storybook-dist/_stories-src/RBIcons/RBColorIcons.stories.tsx +108 -108
  57. package/cli/storybook-dist/_stories-src/RBIcons/RBIcons.stories.tsx +106 -106
  58. package/cli/storybook-dist/_stories-src/RBInput/RBCheckBox.stories.tsx +89 -89
  59. package/cli/storybook-dist/_stories-src/RBInput/RBMultiRangeInput.stories.tsx +64 -64
  60. package/cli/storybook-dist/_stories-src/RBInput/RBRadioBox.stories.tsx +65 -65
  61. package/cli/storybook-dist/_stories-src/RBInput/RBRadioButton.stories.tsx +68 -68
  62. package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +216 -216
  63. package/cli/storybook-dist/_stories-src/RBInput/RBSideNumberInput.stories.tsx +90 -90
  64. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextArea.stories.tsx +45 -45
  65. package/cli/storybook-dist/_stories-src/RBInput/RBText/RBTextInput.stories.tsx +50 -50
  66. package/cli/storybook-dist/_stories-src/RBInput/RBTextField.stories.tsx +102 -102
  67. package/cli/storybook-dist/_stories-src/RBJoyStick/RBJoyStick.stories.tsx +31 -31
  68. package/cli/storybook-dist/_stories-src/RBLight/RBLight.stories.tsx +36 -36
  69. package/cli/storybook-dist/_stories-src/RBPagination/RBPagination.stories.tsx +49 -49
  70. package/cli/storybook-dist/_stories-src/RBShadcn/RBCard/RBCard.stories.tsx +89 -89
  71. package/cli/storybook-dist/_stories-src/RBShadcn/RBCommand/RBCommand.stories.tsx +116 -116
  72. package/cli/storybook-dist/_stories-src/RBShadcn/RBDialog/RBDialog.stories.tsx +74 -74
  73. package/cli/storybook-dist/_stories-src/RBShadcn/RBDrawer/RBDrawer.stories.tsx +99 -99
  74. package/cli/storybook-dist/_stories-src/RBShadcn/RBDropdownMenu/RBDropdownMenu.stories.tsx +123 -123
  75. package/cli/storybook-dist/_stories-src/RBShadcn/RBMenubar/RBMenubar.stories.tsx +135 -135
  76. package/cli/storybook-dist/_stories-src/RBShadcn/RBPopover/RBPopover.stories.tsx +69 -69
  77. package/cli/storybook-dist/_stories-src/RBShadcn/RBResizable/RBResizable.stories.tsx +49 -49
  78. package/cli/storybook-dist/_stories-src/RBShadcn/RBSheet/RBSheet.stories.tsx +91 -91
  79. package/cli/storybook-dist/_stories-src/RBShadcn/RBSlider/RBSlider.stories.tsx +38 -38
  80. package/cli/storybook-dist/_stories-src/RBShadcn/RBSwitch/RBSwitch.stories.tsx +112 -112
  81. package/cli/storybook-dist/_stories-src/RBShadcn/RBTabs/RBTabs.stories.tsx +75 -75
  82. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggle/RBToggle.stories.tsx +51 -51
  83. package/cli/storybook-dist/_stories-src/RBShadcn/RBToggleGroup/RBToggleGroup.stories.tsx +84 -84
  84. package/cli/storybook-dist/_stories-src/RBTab/RBChipTab.stories.tsx +102 -102
  85. package/cli/storybook-dist/_stories-src/RBTab/RBChipsBoxTab.stories.tsx +78 -78
  86. package/cli/storybook-dist/_stories-src/RBTab/RBTab.stories.tsx +57 -57
  87. package/cli/storybook-dist/_stories-src/RBTable/RBTable.stories.tsx +177 -177
  88. package/cli/storybook-dist/_stories-src/RBTable/RBTableRow.stories.tsx +64 -64
  89. package/cli/storybook-dist/_stories-src/RBToast/RBToast.stories.tsx +117 -117
  90. package/cli/storybook-dist/_stories-src/RBTooltip/RBTooltip.stories.tsx +50 -50
  91. package/cli/storybook-dist/_stories-src/loading/RBSpinner.stories.tsx +30 -30
  92. package/cli/storybook-dist/_stories-src/nx-button/NxButton.stories.tsx +54 -54
  93. package/cli/storybook-dist/_stories-src/nx-button/NxPointerHoldButton.stories.tsx +57 -57
  94. package/cli/storybook-dist/_stories-src/nx-icons/NxIcons.stories.tsx +48 -48
  95. package/cli/storybook-dist/_stories-src/nx-inputs/NxNumberInput.stories.tsx +103 -103
  96. package/cli/storybook-dist/_stories-src/nx-inputs/NxRangeInput.stories.tsx +27 -27
  97. package/cli/storybook-dist/_stories-src/nx-inputs/NxSelectBox.stories.tsx +67 -67
  98. package/cli/storybook-dist/_stories-src/nx-inputs/NxSlideRange.stories.tsx +45 -45
  99. package/cli/storybook-dist/_stories-src/nx-inputs/NxSwitch.stories.tsx +27 -27
  100. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextArea.stories.tsx +48 -48
  101. package/cli/storybook-dist/_stories-src/nx-inputs/NxTextInput.stories.tsx +46 -46
  102. package/cli/storybook-dist/assets/{Color-YHDXOIA2-D6JqZzDJ.js → Color-YHDXOIA2-B4IER17l.js} +1 -1
  103. package/cli/storybook-dist/assets/{Docs-ESb7JAHg.js → Docs-C8GCHFtK.js} +3 -3
  104. package/cli/storybook-dist/assets/{Docs-6gD2kUo1.js → Docs-CgdEMPQP.js} +2 -2
  105. package/cli/storybook-dist/assets/{Docs-B9wd8Ok7.js → Docs-Ck3WHvk6.js} +3 -3
  106. package/cli/storybook-dist/assets/{Docs-CgFbWWXM.js → Docs-D9NHsoeZ.js} +2 -2
  107. package/cli/storybook-dist/assets/{Docs-DBQup8Bu.js → Docs-DNYQC7nl.js} +3 -3
  108. package/cli/storybook-dist/assets/{Docs-CMjovnSY.js → Docs-DUfeiM4I.js} +4 -4
  109. package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-BOUI1AHi.js → DocsRenderer-CFRXHY34-C0WhyDoR.js} +1 -1
  110. package/cli/storybook-dist/assets/{NxAvatar.stories-DsYFMUh3.js → NxAvatar.stories-CwnK8AUF.js} +1 -1
  111. package/cli/storybook-dist/assets/{NxButton.stories-CHfNcqsc.js → NxButton.stories-ueYdosfQ.js} +8 -8
  112. package/cli/storybook-dist/assets/{NxNumberInput.stories-BFw_4vFE.js → NxNumberInput.stories-M5ZCLbrK.js} +1 -1
  113. package/cli/storybook-dist/assets/{NxPagination.stories-DVfxyw4M.js → NxPagination.stories-bxxUnHEL.js} +1 -1
  114. package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BzyxUWoZ.js → NxPointerHoldButton.stories-BHxB2kr5.js} +1 -1
  115. package/cli/storybook-dist/assets/{NxRangeInput.stories-DmRdzBc1.js → NxRangeInput.stories-BKfr-fp4.js} +6 -6
  116. package/cli/storybook-dist/assets/{NxSelectBox.stories-B1KkNUOf.js → NxSelectBox.stories-CG8FuTBq.js} +1 -1
  117. package/cli/storybook-dist/assets/{NxSlideRange.stories-CyOdvfQ6.js → NxSlideRange.stories-BRTFtMTL.js} +1 -1
  118. package/cli/storybook-dist/assets/{NxSwitch.stories-DrDBw0qp.js → NxSwitch.stories-Bwuyzn-R.js} +1 -1
  119. package/cli/storybook-dist/assets/{NxTextArea.stories-DTB2aqNS.js → NxTextArea.stories-COq2sGqu.js} +1 -1
  120. package/cli/storybook-dist/assets/{NxTextInput.stories-DU0rucmM.js → NxTextInput.stories-CjsBLz05.js} +1 -1
  121. package/cli/storybook-dist/assets/{RBAccordion.stories-DTfOtXtp.js → RBAccordion.stories-BARwuZ0Q.js} +3 -3
  122. package/cli/storybook-dist/assets/{RBAccordionGroup.stories-9y9BjrUe.js → RBAccordionGroup.stories-COrPxETW.js} +10 -10
  123. package/cli/storybook-dist/assets/{RBAlert.stories-DLTAv9fT.js → RBAlert.stories-BQrkjZPt.js} +3 -3
  124. package/cli/storybook-dist/assets/{RBBadge.stories-BNMACewA.js → RBBadge.stories-BTsLL-lK.js} +1 -1
  125. package/cli/storybook-dist/assets/{RBBattery.stories-B3kXMAt5.js → RBBattery.stories-DAeQdJbc.js} +1 -1
  126. package/cli/storybook-dist/assets/{RBCard.stories-Y-s8R2n7.js → RBCard.stories-HyhOE80R.js} +39 -39
  127. package/cli/storybook-dist/assets/{RBCheckBox.stories-DBuT9tL4.js → RBCheckBox.stories-KFk0WqX_.js} +1 -1
  128. package/cli/storybook-dist/assets/{RBCheckIconButton.stories-CWu1TL56.js → RBCheckIconButton.stories-CXbbktF5.js} +1 -1
  129. package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-sCu2MGCX.js → RBCheckLabelButton.stories-DWfYz_9Z.js} +17 -17
  130. package/cli/storybook-dist/assets/{RBChipTab.stories-BX9qLJkR.js → RBChipTab.stories-JPEgVFAS.js} +5 -5
  131. package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-35u1aJ-P.js → RBChipsBoxTab.stories-CTPuWXrQ.js} +1 -1
  132. package/cli/storybook-dist/assets/{RBColorIcons.stories-DAOZ5Ies.js → RBColorIcons.stories-DiFRZ5jk.js} +1 -1
  133. package/cli/storybook-dist/assets/RBCommand.stories-Pf1HAVIR.js +73 -0
  134. package/cli/storybook-dist/assets/{RBConfirm.stories-iMXcUh7Z.js → RBConfirm.stories-D1cEPUXk.js} +3 -3
  135. package/cli/storybook-dist/assets/{RBDateInput.stories-hK5rXECf.js → RBDateInput.stories-RPYpCjig.js} +1 -1
  136. package/cli/storybook-dist/assets/RBDialog.stories-DJH-0XSR.js +38 -0
  137. package/cli/storybook-dist/assets/{RBDivider.stories-BAK-iZ1Z.js → RBDivider.stories-uEj1vOgK.js} +1 -1
  138. package/cli/storybook-dist/assets/{RBDrawer.stories-B2nqf2Nh.js → RBDrawer.stories-BiMzElb2.js} +12 -12
  139. package/cli/storybook-dist/assets/RBDropdownMenu.stories-Da_TBkla.js +86 -0
  140. package/cli/storybook-dist/assets/{RBEmptyView.stories-nCenrgBh.js → RBEmptyView.stories-BHeKGfPS.js} +1 -1
  141. package/cli/storybook-dist/assets/{RBIconButton.stories-DRH7O3sp.js → RBIconButton.stories-NEsRFoYp.js} +1 -1
  142. package/cli/storybook-dist/assets/{RBIcons.stories-D3PyJXG8.js → RBIcons.stories-CcZ-TTLS.js} +1 -1
  143. package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-modQbt10.js → RBInlineLabelButton.stories-B0wpdQyR.js} +1 -1
  144. package/cli/storybook-dist/assets/{RBJoyStick.stories-BY14qVgR.js → RBJoyStick.stories-DEgBou3K.js} +1 -1
  145. package/cli/storybook-dist/assets/{RBLabelButton.stories-DzF7vGwm.js → RBLabelButton.stories-DT8drIsF.js} +1 -1
  146. package/cli/storybook-dist/assets/{RBLight.stories-lF4FRmVh.js → RBLight.stories-Dh-aoeyG.js} +1 -1
  147. package/cli/storybook-dist/assets/{RBMenubar.stories-XhrI7bsV.js → RBMenubar.stories-D-H7UqVh.js} +1 -1
  148. package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-DprllQDl.js → RBMultiRangeInput.stories-hlclv6jI.js} +1 -1
  149. package/cli/storybook-dist/assets/{RBPagination.stories-2v2gBJfC.js → RBPagination.stories-DWCeoZ_S.js} +1 -1
  150. package/cli/storybook-dist/assets/{RBPopover.stories-C8iLIZpv.js → RBPopover.stories-C514M09_.js} +1 -1
  151. package/cli/storybook-dist/assets/{RBPrompt.stories-Th_HrWHI.js → RBPrompt.stories-BSItvfiT.js} +1 -1
  152. package/cli/storybook-dist/assets/{RBRadioBox.stories-CoHuLaN3.js → RBRadioBox.stories-BBb4YbFU.js} +1 -1
  153. package/cli/storybook-dist/assets/{RBRadioButton.stories-DULxE_ff.js → RBRadioButton.stories-CXyOGJCv.js} +1 -1
  154. package/cli/storybook-dist/assets/{RBRangeDateInput.stories-D8A9KBSS.js → RBRangeDateInput.stories-qvhubu0e.js} +1 -1
  155. package/cli/storybook-dist/assets/{RBResizable.stories-C41QfrQ3.js → RBResizable.stories-CZKeyGjH.js} +14 -14
  156. package/cli/storybook-dist/assets/{RBSelectBox.stories-C1gNmwzw.js → RBSelectBox.stories-CRKP3ybf.js} +1 -1
  157. package/cli/storybook-dist/assets/RBSheet.stories-sCM3ZJUa.js +39 -0
  158. package/cli/storybook-dist/assets/{RBSideNumberInput.stories-9LlgQ8nh.js → RBSideNumberInput.stories-Bb7gw8RF.js} +3 -3
  159. package/cli/storybook-dist/assets/{RBSlider.stories-oUgo5K9d.js → RBSlider.stories-23OAQU5z.js} +3 -3
  160. package/cli/storybook-dist/assets/{RBSpinner.stories-DDpohbY2.js → RBSpinner.stories-2xcQdcmt.js} +1 -1
  161. package/cli/storybook-dist/assets/{RBSwitch.stories-gW-GN7QO.js → RBSwitch.stories-BACUaJm_.js} +7 -7
  162. package/cli/storybook-dist/assets/{RBTab.stories-CN-PV3HH.js → RBTab.stories-CDHE3sVs.js} +1 -1
  163. package/cli/storybook-dist/assets/{RBTable.stories-BAZLpxtM.js → RBTable.stories-C0Cfhfl7.js} +29 -29
  164. package/cli/storybook-dist/assets/{RBTableRow.stories-CwjVrszB.js → RBTableRow.stories-BF8kIkpp.js} +1 -1
  165. package/cli/storybook-dist/assets/{RBTabs.stories-JuEo2sUL.js → RBTabs.stories-BdJWRq1o.js} +7 -7
  166. package/cli/storybook-dist/assets/{RBTextArea.stories-D0_Pv4qZ.js → RBTextArea.stories-CGy2xeYH.js} +3 -3
  167. package/cli/storybook-dist/assets/{RBTextField.stories-CCsOvZqV.js → RBTextField.stories-IfUOzxCM.js} +15 -15
  168. package/cli/storybook-dist/assets/{RBTextInput.stories-Cccn3VNm.js → RBTextInput.stories-VjXNFo4R.js} +3 -3
  169. package/cli/storybook-dist/assets/{RBToast.stories-I5617t0K.js → RBToast.stories-BzcobblV.js} +1 -1
  170. package/cli/storybook-dist/assets/{RBToggle.stories-DsgKjGsJ.js → RBToggle.stories-DuLE5mKv.js} +4 -4
  171. package/cli/storybook-dist/assets/{RBToggleGroup.stories-BZEzSDuz.js → RBToggleGroup.stories-CuR0N0GJ.js} +5 -5
  172. package/cli/storybook-dist/assets/{RBTooltip.stories-4oSyAWsA.js → RBTooltip.stories--aAgNC8_.js} +1 -1
  173. package/cli/storybook-dist/assets/{SideNumberInput-DSKbd0bG.js → SideNumberInput-DyWQxYUj.js} +743 -743
  174. package/cli/storybook-dist/assets/{entry-preview-B35XREGs.js → entry-preview-D7IHsza_.js} +1 -1
  175. package/cli/storybook-dist/assets/{iframe-BB797lmL.js → iframe-CbaIp5I-.js} +2 -2
  176. package/cli/storybook-dist/assets/{index-BU2567EP.js → index-C6CBgMuG.js} +3 -3
  177. package/cli/storybook-dist/assets/{preview-C7DaSWyc.js → preview-B-iYLDuW.js} +2 -2
  178. package/cli/storybook-dist/assets/{preview-CbuRHit4.js → preview-CRWqc9DH.js} +1 -1
  179. package/cli/storybook-dist/assets/{react-resizable-panels.browser-ChIvlwo7.js → react-resizable-panels.browser-8hC8dr-6.js} +8 -8
  180. package/cli/storybook-dist/iframe.html +2 -2
  181. package/cli/storybook-dist/index.html +1 -1
  182. package/cli/storybook-dist/project.json +1 -1
  183. package/cli/templates/common/.mcp.json +8 -8
  184. package/cli/templates/common/README.md +1231 -1231
  185. package/cli/templates/common/assets/icon.svg +45 -45
  186. package/cli/templates/common/manifest.json +26 -26
  187. package/cli/templates/common/package.json +22 -22
  188. package/cli/templates/common/py/main.py +125 -125
  189. package/cli/templates/common/tsconfig.json +13 -13
  190. package/cli/templates/common/ui/content.tsx +216 -216
  191. package/cli/templates/common/ui/style.css +6 -6
  192. package/cli/templates/common/vite.config.ts +10 -10
  193. package/cli/templates/cpp/cpp/main.cpp +73 -73
  194. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/__PLUGIN_SLUG__/node.py +25 -25
  195. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/package.xml +10 -10
  196. package/cli/templates/ros2/ros2_ws/src/__PLUGIN_SLUG__/setup.py +18 -18
  197. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/package.xml +11 -11
  198. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +23 -23
  199. package/cli/vite.d.mts +25 -25
  200. package/cli/vite.mjs +292 -292
  201. package/package.json +3 -3
  202. package/cli/storybook-dist/assets/RBCommand.stories-BSqXDkKw.js +0 -73
  203. package/cli/storybook-dist/assets/RBDialog.stories-BAktz-ku.js +0 -38
  204. package/cli/storybook-dist/assets/RBDropdownMenu.stories-DbW2fWAj.js +0 -86
  205. package/cli/storybook-dist/assets/RBSheet.stories-_6VH-RUm.js +0 -39
@@ -1,223 +1,223 @@
1
- """Host-side Zenoh byte-proxy for Docker Desktop containers (Mac/WSL remote dev).
2
-
3
- Joins the robot's Zenoh mesh with eclipse-zenoh (no rainbow SDK) and relays
4
- {query, put, subscribe} bytes over TCP to the dev container.
5
-
6
- Env: MUSCAT_ROBOT_HOST, MUSCAT_ROBOT_TOKEN, MUSCAT_ZENOH_ADVERTISE_HOST,
7
- MUSCAT_ZENOH_PORT (robot-facing), MUSCAT_PROXY_PORT (container-facing).
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import contextlib
13
- import json
14
- import os
15
- import signal
16
- import socket
17
- import struct
18
- import sys
19
- import threading
20
- import urllib.error
21
- import urllib.request
22
-
23
- import zenoh
24
-
25
- PEER_TTL = 30
26
- QUERY_TARGETS = {0: zenoh.QueryTarget.BEST_MATCHING, 1: zenoh.QueryTarget.ALL, 2: zenoh.QueryTarget.ALL_COMPLETE}
27
-
28
-
29
- def _send_frame(conn: socket.socket, lock: threading.Lock, obj: dict) -> None:
30
- body = json.dumps(obj).encode()
31
- with lock:
32
- conn.sendall(struct.pack(">I", len(body)) + body)
33
-
34
-
35
- def _recv_frame(conn: socket.socket) -> dict | None:
36
- hdr = b""
37
- while len(hdr) < 4:
38
- chunk = conn.recv(4 - len(hdr))
39
- if not chunk:
40
- return None
41
- hdr += chunk
42
- n = struct.unpack(">I", hdr)[0]
43
- buf = b""
44
- while len(buf) < n:
45
- chunk = conn.recv(n - len(buf))
46
- if not chunk:
47
- return None
48
- buf += chunk
49
- return json.loads(buf.decode())
50
-
51
-
52
- def _att(sample) -> str:
53
- return sample.attachment.to_string() if sample.attachment is not None else ""
54
-
55
-
56
- def _query(session: zenoh.Session, req: dict) -> list[dict]:
57
- payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else None
58
- replies = session.get(
59
- req["keyexpr"],
60
- payload=payload,
61
- timeout=req.get("timeout_ms", 2000) / 1000,
62
- target=QUERY_TARGETS.get(req.get("target", 0), zenoh.QueryTarget.BEST_MATCHING),
63
- )
64
- out = []
65
- for reply in replies:
66
- if reply.ok is not None:
67
- s = reply.ok
68
- out.append({"key": str(s.key_expr), "payload_hex": s.payload.to_bytes().hex(),
69
- "att": _att(s), "encoding": str(s.encoding), "is_error": False})
70
- else:
71
- e = reply.err
72
- out.append({"key": req["keyexpr"], "payload_hex": e.payload.to_bytes().hex(),
73
- "att": "", "encoding": str(e.encoding), "is_error": True})
74
- return out
75
-
76
-
77
- def _handle_conn(conn: socket.socket, session: zenoh.Session) -> None:
78
- send_lock = threading.Lock()
79
- subs: dict[str, zenoh.Subscriber] = {}
80
- try:
81
- while True:
82
- req = _recv_frame(conn)
83
- if req is None:
84
- return
85
- op = req.get("type")
86
- rid = req.get("id")
87
-
88
- try:
89
- if op == "query":
90
- out = {"type": "query_resp", "id": rid, "ok": True, "replies": _query(session, req)}
91
-
92
- elif op == "put":
93
- payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else b""
94
- session.put(req["keyexpr"], payload)
95
- out = {"type": "put_resp", "id": rid, "ok": True}
96
-
97
- elif op == "subscribe":
98
- keyexpr = req["keyexpr"]
99
-
100
- def on_sample(s, _ke: str = keyexpr) -> None:
101
- _send_frame(conn, send_lock, {
102
- "type": "sample", "keyexpr": _ke, "key": str(s.key_expr),
103
- "payload_hex": s.payload.to_bytes().hex(), "att": _att(s),
104
- })
105
-
106
- old = subs.pop(keyexpr, None)
107
- if old is not None:
108
- old.undeclare()
109
- subs[keyexpr] = session.declare_subscriber(keyexpr, on_sample)
110
- out = {"type": "subscribe_resp", "id": rid, "ok": True}
111
-
112
- elif op == "unsubscribe":
113
- sub = subs.pop(req["keyexpr"], None)
114
- if sub is not None:
115
- with contextlib.suppress(Exception):
116
- sub.undeclare()
117
- out = {"type": "unsubscribe_resp", "id": rid, "ok": True}
118
-
119
- else:
120
- out = {"type": "error", "error": f"unknown op {op!r}"}
121
- except Exception as exc:
122
- out = {"type": f"{op}_resp", "id": rid, "ok": False, "error": f"{type(exc).__name__}: {exc}"}
123
- _send_frame(conn, send_lock, out)
124
- except (ConnectionResetError, BrokenPipeError, OSError):
125
- pass
126
- finally:
127
- for sub in subs.values():
128
- with contextlib.suppress(Exception):
129
- sub.undeclare()
130
- conn.close()
131
-
132
-
133
- class Peer:
134
- """Registers this host with the robot's common: POST /zenoh/peer, heartbeat, DELETE."""
135
-
136
- def __init__(self, host: str, token: str, ip: str, port: int) -> None:
137
- self.base = f"http://{host}:8000"
138
- self.headers = {"Content-Type": "application/json"}
139
- if token:
140
- self.headers["Authorization"] = f"Bearer {token}"
141
- self.body = {"ip": ip, "port": port, "ttl": PEER_TTL}
142
- self.peer_id: str | None = None
143
- self.stop = threading.Event()
144
-
145
- def _http(self, method: str, path: str, body: dict | None = None) -> dict:
146
- data = json.dumps(body).encode() if body is not None else None
147
- req = urllib.request.Request(self.base + path, data=data, headers=self.headers, method=method)
148
- with urllib.request.urlopen(req, timeout=10) as resp:
149
- raw = resp.read()
150
- return json.loads(raw) if raw else {}
151
-
152
- def register(self) -> None:
153
- self.peer_id = self._http("POST", "/zenoh/peer", self.body)["peer_id"]
154
- print(f"[host-agent] registered with {self.base} as zenoh peer {self.peer_id}", flush=True)
155
-
156
- def maintain(self) -> None:
157
- while True:
158
- wait = PEER_TTL / 2 if self.peer_id else 3
159
- if self.stop.wait(wait):
160
- return
161
- try:
162
- if self.peer_id is None:
163
- self.register()
164
- elif not self._http("POST", f"/zenoh/peer/{self.peer_id}/heartbeat", {"ttl": PEER_TTL}).get("ok"):
165
- print(f"[host-agent] heartbeat rejected for {self.peer_id}, re-registering", flush=True)
166
- self.peer_id = None
167
- except Exception as exc:
168
- print(f"[host-agent] peer maintenance failed ({exc}); retrying", flush=True)
169
- if self.peer_id is not None and isinstance(exc, urllib.error.HTTPError) and exc.code == 404:
170
- self.peer_id = None
171
-
172
- def close(self) -> None:
173
- self.stop.set()
174
- if self.peer_id:
175
- with contextlib.suppress(Exception):
176
- self._http("DELETE", f"/zenoh/peer/{self.peer_id}")
177
-
178
-
179
- def main() -> None:
180
- # dev stops us with SIGTERM; exit via `finally` to deregister.
181
- signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
182
- host = os.environ["MUSCAT_ROBOT_HOST"]
183
- token = os.environ.get("MUSCAT_ROBOT_TOKEN", "")
184
- advertise = os.environ["MUSCAT_ZENOH_ADVERTISE_HOST"]
185
- zenoh_port = int(os.environ["MUSCAT_ZENOH_PORT"])
186
- proxy_port = int(os.environ["MUSCAT_PROXY_PORT"])
187
-
188
- # Listen only; the robot dials in after registration. Router mode so a C++ SDK dev container
189
- # (native Zenoh session, no byte shim) can connect here and be routed to the robot.
190
- conf = zenoh.Config()
191
- conf.insert_json5("mode", '"peer"')
192
- conf.insert_json5("listen/endpoints", json.dumps([f"tcp/0.0.0.0:{zenoh_port}"]))
193
- conf.insert_json5("scouting/multicast/enabled", "false")
194
- session = zenoh.open(conf)
195
-
196
- peer = Peer(host, token, advertise, zenoh_port)
197
- try:
198
- peer.register()
199
- except Exception as exc:
200
- print(f"[host-agent] registration with {peer.base} failed, retrying in background: {exc}", flush=True)
201
- threading.Thread(target=peer.maintain, daemon=True, name="zenoh-peer").start()
202
- print(f"[host-agent] zenoh peer listening :{zenoh_port} (advertised as {advertise})", flush=True)
203
-
204
- srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
205
- srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
206
- srv.bind(("0.0.0.0", proxy_port))
207
- srv.listen(32)
208
- print(f"[host-agent] byte-proxy listening :{proxy_port} (container connects via host.docker.internal)", flush=True)
209
-
210
- try:
211
- while True:
212
- conn, _addr = srv.accept()
213
- threading.Thread(target=_handle_conn, args=(conn, session), daemon=True).start()
214
- except KeyboardInterrupt:
215
- pass
216
- finally:
217
- peer.close()
218
- with contextlib.suppress(Exception):
219
- session.close()
220
-
221
-
222
- if __name__ == "__main__":
223
- main()
1
+ """Host-side Zenoh byte-proxy for Docker Desktop containers (Mac/WSL remote dev).
2
+
3
+ Joins the robot's Zenoh mesh with eclipse-zenoh (no rainbow SDK) and relays
4
+ {query, put, subscribe} bytes over TCP to the dev container.
5
+
6
+ Env: MUSCAT_ROBOT_HOST, MUSCAT_ROBOT_TOKEN, MUSCAT_ZENOH_ADVERTISE_HOST,
7
+ MUSCAT_ZENOH_PORT (robot-facing), MUSCAT_PROXY_PORT (container-facing).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import contextlib
13
+ import json
14
+ import os
15
+ import signal
16
+ import socket
17
+ import struct
18
+ import sys
19
+ import threading
20
+ import urllib.error
21
+ import urllib.request
22
+
23
+ import zenoh
24
+
25
+ PEER_TTL = 30
26
+ QUERY_TARGETS = {0: zenoh.QueryTarget.BEST_MATCHING, 1: zenoh.QueryTarget.ALL, 2: zenoh.QueryTarget.ALL_COMPLETE}
27
+
28
+
29
+ def _send_frame(conn: socket.socket, lock: threading.Lock, obj: dict) -> None:
30
+ body = json.dumps(obj).encode()
31
+ with lock:
32
+ conn.sendall(struct.pack(">I", len(body)) + body)
33
+
34
+
35
+ def _recv_frame(conn: socket.socket) -> dict | None:
36
+ hdr = b""
37
+ while len(hdr) < 4:
38
+ chunk = conn.recv(4 - len(hdr))
39
+ if not chunk:
40
+ return None
41
+ hdr += chunk
42
+ n = struct.unpack(">I", hdr)[0]
43
+ buf = b""
44
+ while len(buf) < n:
45
+ chunk = conn.recv(n - len(buf))
46
+ if not chunk:
47
+ return None
48
+ buf += chunk
49
+ return json.loads(buf.decode())
50
+
51
+
52
+ def _att(sample) -> str:
53
+ return sample.attachment.to_string() if sample.attachment is not None else ""
54
+
55
+
56
+ def _query(session: zenoh.Session, req: dict) -> list[dict]:
57
+ payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else None
58
+ replies = session.get(
59
+ req["keyexpr"],
60
+ payload=payload,
61
+ timeout=req.get("timeout_ms", 2000) / 1000,
62
+ target=QUERY_TARGETS.get(req.get("target", 0), zenoh.QueryTarget.BEST_MATCHING),
63
+ )
64
+ out = []
65
+ for reply in replies:
66
+ if reply.ok is not None:
67
+ s = reply.ok
68
+ out.append({"key": str(s.key_expr), "payload_hex": s.payload.to_bytes().hex(),
69
+ "att": _att(s), "encoding": str(s.encoding), "is_error": False})
70
+ else:
71
+ e = reply.err
72
+ out.append({"key": req["keyexpr"], "payload_hex": e.payload.to_bytes().hex(),
73
+ "att": "", "encoding": str(e.encoding), "is_error": True})
74
+ return out
75
+
76
+
77
+ def _handle_conn(conn: socket.socket, session: zenoh.Session) -> None:
78
+ send_lock = threading.Lock()
79
+ subs: dict[str, zenoh.Subscriber] = {}
80
+ try:
81
+ while True:
82
+ req = _recv_frame(conn)
83
+ if req is None:
84
+ return
85
+ op = req.get("type")
86
+ rid = req.get("id")
87
+
88
+ try:
89
+ if op == "query":
90
+ out = {"type": "query_resp", "id": rid, "ok": True, "replies": _query(session, req)}
91
+
92
+ elif op == "put":
93
+ payload = bytes.fromhex(req["payload_hex"]) if req.get("payload_hex") else b""
94
+ session.put(req["keyexpr"], payload)
95
+ out = {"type": "put_resp", "id": rid, "ok": True}
96
+
97
+ elif op == "subscribe":
98
+ keyexpr = req["keyexpr"]
99
+
100
+ def on_sample(s, _ke: str = keyexpr) -> None:
101
+ _send_frame(conn, send_lock, {
102
+ "type": "sample", "keyexpr": _ke, "key": str(s.key_expr),
103
+ "payload_hex": s.payload.to_bytes().hex(), "att": _att(s),
104
+ })
105
+
106
+ old = subs.pop(keyexpr, None)
107
+ if old is not None:
108
+ old.undeclare()
109
+ subs[keyexpr] = session.declare_subscriber(keyexpr, on_sample)
110
+ out = {"type": "subscribe_resp", "id": rid, "ok": True}
111
+
112
+ elif op == "unsubscribe":
113
+ sub = subs.pop(req["keyexpr"], None)
114
+ if sub is not None:
115
+ with contextlib.suppress(Exception):
116
+ sub.undeclare()
117
+ out = {"type": "unsubscribe_resp", "id": rid, "ok": True}
118
+
119
+ else:
120
+ out = {"type": "error", "error": f"unknown op {op!r}"}
121
+ except Exception as exc:
122
+ out = {"type": f"{op}_resp", "id": rid, "ok": False, "error": f"{type(exc).__name__}: {exc}"}
123
+ _send_frame(conn, send_lock, out)
124
+ except (ConnectionResetError, BrokenPipeError, OSError):
125
+ pass
126
+ finally:
127
+ for sub in subs.values():
128
+ with contextlib.suppress(Exception):
129
+ sub.undeclare()
130
+ conn.close()
131
+
132
+
133
+ class Peer:
134
+ """Registers this host with the robot's common: POST /zenoh/peer, heartbeat, DELETE."""
135
+
136
+ def __init__(self, host: str, token: str, ip: str, port: int) -> None:
137
+ self.base = f"http://{host}:8000"
138
+ self.headers = {"Content-Type": "application/json"}
139
+ if token:
140
+ self.headers["Authorization"] = f"Bearer {token}"
141
+ self.body = {"ip": ip, "port": port, "ttl": PEER_TTL}
142
+ self.peer_id: str | None = None
143
+ self.stop = threading.Event()
144
+
145
+ def _http(self, method: str, path: str, body: dict | None = None) -> dict:
146
+ data = json.dumps(body).encode() if body is not None else None
147
+ req = urllib.request.Request(self.base + path, data=data, headers=self.headers, method=method)
148
+ with urllib.request.urlopen(req, timeout=10) as resp:
149
+ raw = resp.read()
150
+ return json.loads(raw) if raw else {}
151
+
152
+ def register(self) -> None:
153
+ self.peer_id = self._http("POST", "/zenoh/peer", self.body)["peer_id"]
154
+ print(f"[host-agent] registered with {self.base} as zenoh peer {self.peer_id}", flush=True)
155
+
156
+ def maintain(self) -> None:
157
+ while True:
158
+ wait = PEER_TTL / 2 if self.peer_id else 3
159
+ if self.stop.wait(wait):
160
+ return
161
+ try:
162
+ if self.peer_id is None:
163
+ self.register()
164
+ elif not self._http("POST", f"/zenoh/peer/{self.peer_id}/heartbeat", {"ttl": PEER_TTL}).get("ok"):
165
+ print(f"[host-agent] heartbeat rejected for {self.peer_id}, re-registering", flush=True)
166
+ self.peer_id = None
167
+ except Exception as exc:
168
+ print(f"[host-agent] peer maintenance failed ({exc}); retrying", flush=True)
169
+ if self.peer_id is not None and isinstance(exc, urllib.error.HTTPError) and exc.code == 404:
170
+ self.peer_id = None
171
+
172
+ def close(self) -> None:
173
+ self.stop.set()
174
+ if self.peer_id:
175
+ with contextlib.suppress(Exception):
176
+ self._http("DELETE", f"/zenoh/peer/{self.peer_id}")
177
+
178
+
179
+ def main() -> None:
180
+ # dev stops us with SIGTERM; exit via `finally` to deregister.
181
+ signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
182
+ host = os.environ["MUSCAT_ROBOT_HOST"]
183
+ token = os.environ.get("MUSCAT_ROBOT_TOKEN", "")
184
+ advertise = os.environ["MUSCAT_ZENOH_ADVERTISE_HOST"]
185
+ zenoh_port = int(os.environ["MUSCAT_ZENOH_PORT"])
186
+ proxy_port = int(os.environ["MUSCAT_PROXY_PORT"])
187
+
188
+ # Listen only; the robot dials in after registration. Router mode so a C++ SDK dev container
189
+ # (native Zenoh session, no byte shim) can connect here and be routed to the robot.
190
+ conf = zenoh.Config()
191
+ conf.insert_json5("mode", '"peer"')
192
+ conf.insert_json5("listen/endpoints", json.dumps([f"tcp/0.0.0.0:{zenoh_port}"]))
193
+ conf.insert_json5("scouting/multicast/enabled", "false")
194
+ session = zenoh.open(conf)
195
+
196
+ peer = Peer(host, token, advertise, zenoh_port)
197
+ try:
198
+ peer.register()
199
+ except Exception as exc:
200
+ print(f"[host-agent] registration with {peer.base} failed, retrying in background: {exc}", flush=True)
201
+ threading.Thread(target=peer.maintain, daemon=True, name="zenoh-peer").start()
202
+ print(f"[host-agent] zenoh peer listening :{zenoh_port} (advertised as {advertise})", flush=True)
203
+
204
+ srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
205
+ srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
206
+ srv.bind(("0.0.0.0", proxy_port))
207
+ srv.listen(32)
208
+ print(f"[host-agent] byte-proxy listening :{proxy_port} (container connects via host.docker.internal)", flush=True)
209
+
210
+ try:
211
+ while True:
212
+ conn, _addr = srv.accept()
213
+ threading.Thread(target=_handle_conn, args=(conn, session), daemon=True).start()
214
+ except KeyboardInterrupt:
215
+ pass
216
+ finally:
217
+ peer.close()
218
+ with contextlib.suppress(Exception):
219
+ session.close()
220
+
221
+
222
+ if __name__ == "__main__":
223
+ main()