@rainbow-robotics/muscat-plugin-kit 1.2.1 → 1.3.0

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 (106) hide show
  1. package/cli/README.md +8 -3
  2. package/cli/docker/Dockerfile.cpp +11 -6
  3. package/cli/docker/Dockerfile.dev +12 -0
  4. package/cli/src/build.mjs +6 -6
  5. package/cli/src/cpp.mjs +3 -1
  6. package/cli/src/manifest.mjs +3 -1
  7. package/cli/src/mcp.mjs +325 -41
  8. package/cli/src/ros2.mjs +3 -1
  9. package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +13 -1
  10. package/cli/storybook-dist/assets/{Color-YHDXOIA2-CSzM1dKs.js → Color-YHDXOIA2-DixbmWSF.js} +1 -1
  11. package/cli/storybook-dist/assets/{Docs-Bhl2G8dW.js → Docs-Blx_41TT.js} +1 -1
  12. package/cli/storybook-dist/assets/{Docs-DGfEBD4U.js → Docs-C0E3mHmn.js} +1 -1
  13. package/cli/storybook-dist/assets/{Docs-y19ER8GV.js → Docs-CMEiBjLz.js} +1 -1
  14. package/cli/storybook-dist/assets/{Docs-Q256LO6R.js → Docs-D6oFYDre.js} +1 -1
  15. package/cli/storybook-dist/assets/{Docs-CxnHR1EX.js → Docs-DcUa4nGr.js} +1 -1
  16. package/cli/storybook-dist/assets/{Docs-BIcNCkcj.js → Docs-Dv-5qjF4.js} +1 -1
  17. package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-DnUkc2jA.js → DocsRenderer-CFRXHY34-GwWANrWN.js} +1 -1
  18. package/cli/storybook-dist/assets/{NxAvatar.stories-D8_zg1ow.js → NxAvatar.stories-CyZ4AE2b.js} +1 -1
  19. package/cli/storybook-dist/assets/{NxButton.stories-DJ27PRZJ.js → NxButton.stories-Bx8UtGNd.js} +1 -1
  20. package/cli/storybook-dist/assets/{NxIcons.stories-Dg42riT5.js → NxIcons.stories-DeqrVzVs.js} +1 -1
  21. package/cli/storybook-dist/assets/{NxNumberInput.stories-DAhhfwBb.js → NxNumberInput.stories-skizAdd5.js} +1 -1
  22. package/cli/storybook-dist/assets/{NxPagination.stories-B0B2V-we.js → NxPagination.stories-gtIxt2u0.js} +1 -1
  23. package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-Cs9aWGqX.js → NxPointerHoldButton.stories-DSHMk3N5.js} +1 -1
  24. package/cli/storybook-dist/assets/{NxRangeInput.stories-A3qk290L.js → NxRangeInput.stories-C1YtrFZ1.js} +3 -3
  25. package/cli/storybook-dist/assets/{NxSelectBox.stories-BtA0bI0U.js → NxSelectBox.stories-DCySAtBp.js} +1 -1
  26. package/cli/storybook-dist/assets/{NxSlideRange.stories-DaVwMyfI.js → NxSlideRange.stories-gSaMOWo7.js} +1 -1
  27. package/cli/storybook-dist/assets/{NxSwitch.stories-MEpYcafd.js → NxSwitch.stories-DF0DBiyG.js} +1 -1
  28. package/cli/storybook-dist/assets/{NxTextArea.stories-8PqT83b0.js → NxTextArea.stories-Dill0M6z.js} +1 -1
  29. package/cli/storybook-dist/assets/{NxTextInput.stories-DwOvyFAK.js → NxTextInput.stories-Bax14r48.js} +1 -1
  30. package/cli/storybook-dist/assets/{RBAccordion.stories-l_KWmznJ.js → RBAccordion.stories-CCwz_pIV.js} +1 -1
  31. package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BHwMLO_b.js → RBAccordionGroup.stories-BjDxxdPV.js} +1 -1
  32. package/cli/storybook-dist/assets/{RBAlert.stories-XeJozdck.js → RBAlert.stories-5z6GasgP.js} +1 -1
  33. package/cli/storybook-dist/assets/{RBBadge.stories-BoIw5gFT.js → RBBadge.stories-BwcM7i7w.js} +1 -1
  34. package/cli/storybook-dist/assets/{RBBattery.stories-C_qyHveA.js → RBBattery.stories-DOeQH3rK.js} +1 -1
  35. package/cli/storybook-dist/assets/{RBCard.stories-jFfiHYKe.js → RBCard.stories-DcmiOXAV.js} +1 -1
  36. package/cli/storybook-dist/assets/{RBCheckBox.stories-DTfCEweM.js → RBCheckBox.stories-BYru87fX.js} +1 -1
  37. package/cli/storybook-dist/assets/{RBCheckIconButton.stories-CTDnyUqG.js → RBCheckIconButton.stories-CXUjtR1a.js} +1 -1
  38. package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-DmquL7n0.js → RBCheckLabelButton.stories-BSOQV7Du.js} +1 -1
  39. package/cli/storybook-dist/assets/{RBChipTab.stories-B8zl9AG8.js → RBChipTab.stories-ByBmeukU.js} +1 -1
  40. package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-BGRQ_ix8.js → RBChipsBoxTab.stories-GtJ01RTr.js} +1 -1
  41. package/cli/storybook-dist/assets/{RBColorIcons.stories-s5QE7tL1.js → RBColorIcons.stories-C9saET1u.js} +1 -1
  42. package/cli/storybook-dist/assets/{RBCommand.stories-Yrpy_B26.js → RBCommand.stories-CcvnWlWZ.js} +1 -1
  43. package/cli/storybook-dist/assets/{RBConfirm.stories-C9qrp3br.js → RBConfirm.stories-hdBVSdBS.js} +1 -1
  44. package/cli/storybook-dist/assets/{RBDateInput.stories-DZpvxSqb.js → RBDateInput.stories-BE9JQl-E.js} +1 -1
  45. package/cli/storybook-dist/assets/{RBDialog.stories-DSonk9Bx.js → RBDialog.stories-BaWuDgl_.js} +1 -1
  46. package/cli/storybook-dist/assets/{RBDivider.stories-CtuMDRWW.js → RBDivider.stories-D3INKm_Z.js} +1 -1
  47. package/cli/storybook-dist/assets/{RBDrawer.stories-BNjRz9Cf.js → RBDrawer.stories-BZ8V0J6-.js} +1 -1
  48. package/cli/storybook-dist/assets/{RBDropdownMenu.stories-Bjl9aOPU.js → RBDropdownMenu.stories-DxLi8HRw.js} +1 -1
  49. package/cli/storybook-dist/assets/{RBEmptyView.stories-ChMkuohg.js → RBEmptyView.stories-DQcvDt2S.js} +1 -1
  50. package/cli/storybook-dist/assets/{RBIconButton.stories-B5R9unwo.js → RBIconButton.stories-DJfuTpQ0.js} +1 -1
  51. package/cli/storybook-dist/assets/{RBIcons.stories-Cp5JeZNr.js → RBIcons.stories-BSsyb0BK.js} +1 -1
  52. package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-Dl_km9SD.js → RBInlineLabelButton.stories-Di3th--x.js} +1 -1
  53. package/cli/storybook-dist/assets/{RBJoyStick.stories-g1XwqElo.js → RBJoyStick.stories-DfGazm4n.js} +1 -1
  54. package/cli/storybook-dist/assets/{RBLabelButton.stories-iNSl-cdD.js → RBLabelButton.stories-CLbxzW56.js} +1 -1
  55. package/cli/storybook-dist/assets/{RBLight.stories-DptPacw1.js → RBLight.stories-DeiuDm_P.js} +1 -1
  56. package/cli/storybook-dist/assets/{RBMenubar.stories-CTH4jNi6.js → RBMenubar.stories-BZQTV9e5.js} +1 -1
  57. package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-BD138rOz.js → RBMultiRangeInput.stories-DDNmdi_n.js} +1 -1
  58. package/cli/storybook-dist/assets/{RBPagination.stories-DaXD-840.js → RBPagination.stories-CLBCEyCU.js} +1 -1
  59. package/cli/storybook-dist/assets/{RBPopover.stories-BHA1-yYi.js → RBPopover.stories-DRI5Kfa-.js} +1 -1
  60. package/cli/storybook-dist/assets/{RBPrompt.stories-CoEeIK6d.js → RBPrompt.stories-DIAmypoJ.js} +1 -1
  61. package/cli/storybook-dist/assets/{RBRadioBox.stories-CePglWfe.js → RBRadioBox.stories-BS_N4aLf.js} +1 -1
  62. package/cli/storybook-dist/assets/{RBRadioButton.stories-BRNRgcJb.js → RBRadioButton.stories-BTm8cYvm.js} +1 -1
  63. package/cli/storybook-dist/assets/RBRangeDateInput.stories-D5hBsSyB.js +15 -0
  64. package/cli/storybook-dist/assets/{RBResizable.stories-DqoaWeqW.js → RBResizable.stories-CxPEkbXQ.js} +1 -1
  65. package/cli/storybook-dist/assets/{RBSelectBox.stories-8QBhknxC.js → RBSelectBox.stories-FUQ7AcUi.js} +1 -1
  66. package/cli/storybook-dist/assets/{RBSheet.stories-Doaw8VOp.js → RBSheet.stories-mhVHH_71.js} +1 -1
  67. package/cli/storybook-dist/assets/{RBSideNumberInput.stories-BmAEpYFE.js → RBSideNumberInput.stories-5FMkLUim.js} +1 -1
  68. package/cli/storybook-dist/assets/{RBSlider.stories-CH-Hkgd6.js → RBSlider.stories-Bnhcp9pz.js} +1 -1
  69. package/cli/storybook-dist/assets/{RBSpinner.stories-BEceevO_.js → RBSpinner.stories-Bf4416mP.js} +1 -1
  70. package/cli/storybook-dist/assets/{RBSwitch.stories-BoZzoyfx.js → RBSwitch.stories-BNhFjRaV.js} +1 -1
  71. package/cli/storybook-dist/assets/{RBTab.stories-CN3Algxc.js → RBTab.stories-Bsr7jlfb.js} +1 -1
  72. package/cli/storybook-dist/assets/{RBTable.stories-C1UgfrL_.js → RBTable.stories-BkA6pKzs.js} +1 -1
  73. package/cli/storybook-dist/assets/{RBTableRow.stories-ASxrEiHq.js → RBTableRow.stories-D07Z7D5-.js} +1 -1
  74. package/cli/storybook-dist/assets/{RBTabs.stories-0tMRRXl4.js → RBTabs.stories-DHnZiV21.js} +1 -1
  75. package/cli/storybook-dist/assets/{RBTextArea.stories-DhWXTnBK.js → RBTextArea.stories-CtLMLuV_.js} +1 -1
  76. package/cli/storybook-dist/assets/{RBTextField.stories-BLI0HpCu.js → RBTextField.stories-Chz0i1-u.js} +1 -1
  77. package/cli/storybook-dist/assets/{RBTextInput.stories-J8FkNDJo.js → RBTextInput.stories-Dk5cZfUF.js} +1 -1
  78. package/cli/storybook-dist/assets/{RBToast.stories-D8UxgdX3.js → RBToast.stories-tZxbjmr8.js} +1 -1
  79. package/cli/storybook-dist/assets/{RBToggle.stories-CjwzqnV-.js → RBToggle.stories-VTe7a4xZ.js} +1 -1
  80. package/cli/storybook-dist/assets/{RBToggleGroup.stories-3JwuIqYr.js → RBToggleGroup.stories-WiFnGGYx.js} +1 -1
  81. package/cli/storybook-dist/assets/{RBTooltip.stories-Wkx6rfug.js → RBTooltip.stories-B_L4W7Mn.js} +1 -1
  82. package/cli/storybook-dist/assets/{SideNumberInput-_BKR64MG.js → SideNumberInput-os5wKKf7.js} +131 -136
  83. package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
  84. package/cli/storybook-dist/assets/{entry-preview-VQeOKtPr.js → entry-preview-NpzneD8a.js} +1 -1
  85. package/cli/storybook-dist/assets/{iframe-Ci_IjEPD.js → iframe-DsvRKHwc.js} +2 -2
  86. package/cli/storybook-dist/assets/{index-DVR_O3uO.js → index-BzvSJzrT.js} +3 -3
  87. package/cli/storybook-dist/assets/preview-BXXleNPp.css +1 -0
  88. package/cli/storybook-dist/assets/preview-BXxjLQ9M.js +3 -0
  89. package/cli/storybook-dist/assets/{preview-DAZ7L_eg.js → preview-yXaYuZ78.js} +2 -2
  90. package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +120 -0
  91. package/cli/storybook-dist/iframe.html +1 -1
  92. package/cli/storybook-dist/project.json +1 -1
  93. package/cli/templates/common/README.md +123 -5
  94. package/cli/templates/common/py/main.py +24 -1
  95. package/cli/templates/common/requirements.txt +3 -3
  96. package/cli/templates/cpp/cpp/CMakeLists.txt +7 -7
  97. package/cli/templates/cpp/cpp/main.cpp +13 -6
  98. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +4 -0
  99. package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +2 -0
  100. package/package.json +3 -3
  101. package/cli/storybook-dist/assets/RBRangeDateInput.stories-B6ukx3Pl.js +0 -15
  102. package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +0 -1
  103. package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +0 -1
  104. package/cli/storybook-dist/assets/preview-BVJWkKiZ.css +0 -1
  105. package/cli/storybook-dist/assets/preview-CO9TxipX.js +0 -3
  106. package/cli/storybook-dist/assets/react-resizable-panels.browser-BpHwB_WW.js +0 -102
@@ -509,7 +509,7 @@
509
509
  }
510
510
  </script>
511
511
 
512
- <script type="module" crossorigin src="./assets/iframe-Ci_IjEPD.js"></script>
512
+ <script type="module" crossorigin src="./assets/iframe-DsvRKHwc.js"></script>
513
513
  </head>
514
514
 
515
515
  <body>
@@ -1 +1 @@
1
- {"generatedAt":1787300984646,"userSince":1750932757926,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":true,"refCount":0,"testPackages":{"@vitest/browser":"3.2.4","@vitest/coverage-v8":"3.2.4","playwright":"1.59.1","vitest":"3.2.4"},"hasRouterPackage":false,"monorepo":"Turborepo","packageManager":{"type":"yarn","version":"berry","agent":"yarn@berry"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-vite","options":{}},"builder":"@storybook/builder-vite","renderer":"@storybook/react","portableStoriesFileCount":0,"applicationFileCount":0,"storybookVersion":"8.6.18","storybookVersionSpecifier":"^8.6.4","language":"typescript","storybookPackages":{"@chromatic-com/storybook":{"version":"3.2.7"},"@storybook/addon-essentials":{"version":"8.6.18"},"@storybook/addon-links":{"version":"8.6.18"},"@storybook/addon-onboarding":{"version":"8.6.18"},"@storybook/addon-themes":{"version":"8.6.18"},"@storybook/blocks":{"version":"8.6.18"},"@storybook/experimental-addon-test":{"version":"8.6.18"},"@storybook/react":{"version":"8.6.18"},"@storybook/react-vite":{"version":"8.6.18"},"@storybook/test":{"version":"8.6.18"},"@storybook/theming":{"version":"8.6.18"},"eslint-plugin-storybook":{"version":"0.11.6"},"storybook":{"version":"8.6.18"},"storybook-addon-react-docgen":{"version":"1.2.44"}},"addons":{"$SNIP/node_modules/@storybook/addon-links":{"version":null},"$SNIP/node_modules/@storybook/addon-essentials":{"version":null},"$SNIP/node_modules/@chromatic-com/storybook":{"version":null},"$SNIP/node_modules/@storybook/addon-themes":{"version":null},"$SNIP/node_modules/@storybook/experimental-addon-test":{"version":null}}}
1
+ {"generatedAt":1789632370434,"userSince":1750932757926,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":true,"refCount":0,"testPackages":{"@vitest/browser":"3.2.4","@vitest/coverage-v8":"3.2.4","playwright":"1.59.1","vitest":"3.2.4"},"hasRouterPackage":false,"monorepo":"Turborepo","packageManager":{"type":"yarn","version":"berry","agent":"yarn@berry"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-vite","options":{}},"builder":"@storybook/builder-vite","renderer":"@storybook/react","portableStoriesFileCount":0,"applicationFileCount":0,"storybookVersion":"8.6.18","storybookVersionSpecifier":"^8.6.4","language":"typescript","storybookPackages":{"@chromatic-com/storybook":{"version":"3.2.7"},"@storybook/addon-essentials":{"version":"8.6.18"},"@storybook/addon-links":{"version":"8.6.18"},"@storybook/addon-onboarding":{"version":"8.6.18"},"@storybook/addon-themes":{"version":"8.6.18"},"@storybook/blocks":{"version":"8.6.18"},"@storybook/experimental-addon-test":{"version":"8.6.18"},"@storybook/react":{"version":"8.6.18"},"@storybook/react-vite":{"version":"8.6.18"},"@storybook/test":{"version":"8.6.18"},"@storybook/theming":{"version":"8.6.18"},"eslint-plugin-storybook":{"version":"0.11.6"},"storybook":{"version":"8.6.18"},"storybook-addon-react-docgen":{"version":"1.2.44"}},"addons":{"$SNIP/node_modules/@storybook/addon-links":{"version":null},"$SNIP/node_modules/@storybook/addon-essentials":{"version":null},"$SNIP/node_modules/@chromatic-com/storybook":{"version":null},"$SNIP/node_modules/@storybook/addon-themes":{"version":null},"$SNIP/node_modules/@storybook/experimental-addon-test":{"version":null}}}
@@ -398,6 +398,103 @@ if __name__ == "__main__":
398
398
  time.sleep(1)
399
399
  ```
400
400
 
401
+ #### External endpoints (`plugin.router`)
402
+
403
+ `plugin.serve` / `plugin.subscribe` / `plugin.publish` are **internal only** (plugin
404
+ UI ↔ backend, plugin ↔ plugin). To expose an endpoint to the outside world —
405
+ REST, Socket.IO, TCP, MQTT, WebRTC — use `plugin.router`. Declaring one is a
406
+ single decorator; the running Muscat (`common`) picks it up without a restart,
407
+ and it disappears when the plugin stops or is removed.
408
+
409
+ > Requires a robot running `rainbow-rb-sdk` **0.0.9.dev5 or newer** (the SDK is
410
+ > provided by the plugin container at runtime). On older robots `plugin.router`
411
+ > does not exist and raises `AttributeError`.
412
+
413
+ ```python
414
+ from rainbow.rb_sdk import RBPluginCall
415
+ from rainbow.rb_sdk.plugin import PluginRouteError
416
+
417
+ plugin = RBPluginCall() # plugin_id comes from env RB_PLUGIN_ID
418
+
419
+
420
+ @plugin.router.on("/hello") # all 5 transports
421
+ def hello(payload: dict, meta: dict):
422
+ return {"msg": f"hi {payload.get('name')}"} # return value = response body
423
+
424
+
425
+ @plugin.router.on("/admin", explicit_routes=["api"]) # REST only
426
+ def admin(payload, meta):
427
+ if not (meta.get("principal") or {}).get("is_admin"):
428
+ raise PluginRouteError(403, "admin only") # becomes the external status
429
+ ...
430
+
431
+
432
+ @plugin.router.subscribe("/cmd", explicit_events=["tcp"]) # receive external TCP publish
433
+ def on_cmd(payload: dict):
434
+ ...
435
+
436
+
437
+ plugin.router.emit("/state", {"busy": True}) # push to external subscribers
438
+ ```
439
+
440
+ | API | Meaning | External address |
441
+ | --- | --- | --- |
442
+ | `router.on(path, explicit_routes=None)` | request/response handler | `common/plugin/{plugin_id}/{path}` (REST is always `POST`) |
443
+ | `router.subscribe(topic, explicit_events=None)` | receive external publishes | external side publishes to `common/plugin/{plugin_id}/{topic}` |
444
+ | `router.emit(topic, payload)` | plugin → external push | external side subscribes to `common/plugin/{plugin_id}/{topic}` |
445
+
446
+ - The `plugin_id` prefix is added by the SDK — write only the path in plugin code.
447
+ - Paths may have several segments (`/tool/echo`). Declaring the same path twice raises `ValueError`.
448
+ - Transport names: `api | socket | tcp | mqtt | webrtc`. `subscribe` accepts only `tcp | mqtt | webrtc`. Unknown names raise `ValueError`.
449
+ - `explicit_routes` / `explicit_events` of `None` means every transport. A request on a transport not listed gets 404.
450
+
451
+ Handler contract:
452
+
453
+ - Signature `(payload, meta)`, `(payload)` or `()` — only the arguments you declare are injected. Sync and async both work.
454
+ - `payload`: what the caller sent (for REST: JSON body merged with query params). The shape is up to the plugin.
455
+ - `meta`: `{"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": dict | None}`. When the robot's login policy is on, `principal` holds the token subject. **Role checks are the plugin's job** — inspect `meta["principal"]` yourself.
456
+ - The return value is the response body (must be JSON-serialisable). `None` → empty response.
457
+ - Status codes: `raise PluginRouteError(code, message)`. Any other exception → 500 with `str(exc)` as the message.
458
+ - `subscribe` handlers take `(payload)` only and return nothing.
459
+
460
+ #### Calling plugin endpoints from outside
461
+
462
+ | Transport | How |
463
+ | --- | --- |
464
+ | REST | `POST /common/plugin/{plugin_id}/{path}` (through the api-gateway), JSON body |
465
+ | Socket.IO | emit event `common/plugin/{plugin_id}/{path}`, response arrives in the ack |
466
+ | TCP | topic `common/plugin/{plugin_id}/{path}` request / publish |
467
+ | MQTT | request: `common/plugin/{plugin_id}/{path}/__req__`, publish: `common/plugin/{plugin_id}/{topic}` |
468
+ | WebRTC | data channel topic `common/plugin/{plugin_id}/{path}` |
469
+
470
+ Auth: when the robot's login policy is on, a token is **required** (any role — GUEST passes). When it is off, calls go straight through. REST uses the same Bearer token as the other Muscat APIs.
471
+
472
+ ```bash
473
+ curl -s -X POST http://<robot>/common/plugin/my-plugin/hello \
474
+ -H 'Content-Type: application/json' -d '{"name":"kim"}'
475
+ ```
476
+
477
+ | Situation | Status |
478
+ | --- | --- |
479
+ | No plugin declares that path / plugin is down / transport not allowed | 404 |
480
+ | Plugin did not answer within 10 s | 504 |
481
+ | `common` ↔ gateway not connected | 503 |
482
+ | Handler raised | 500 (`PluginRouteError` → its `code`) |
483
+
484
+ Known limits:
485
+
486
+ - A plugin path named `install` cannot be called over REST (`POST /common/plugin/{id}/install` is taken by the registry install route first). Socket/TCP are unaffected.
487
+ - Swagger shows one catch-all route, not per-plugin paths.
488
+ - The MQTT broker echoes to the sender: a plugin that `emit`s and `subscribe`s the same topic receives its own push over `mqtt`. Drop `mqtt` from `explicit_events` or use separate topics. (TCP is filtered by `sender_id`.)
489
+ - `on` and `subscribe` may share a name — they live in separate tables.
490
+
491
+ During `muscat-pk dev` / `dev:back` the local bridge (port `10000`) accepts the
492
+ `router.*` registrations, but **external calls need a running `common`** — it is
493
+ `common` that routes `common/plugin/...` to the gateway. Use a robot (or a local
494
+ `common`) to exercise the external addresses. After restarting the plugin process,
495
+ external calls may return 504 for up to about a minute (until the gateway notices
496
+ the old socket is gone), then 404 — that is expected.
497
+
401
498
  #### Program command backend (`program.panel`)
402
499
 
403
500
  A `program.panel` contribution defines a **custom command** your plugin adds to
@@ -637,9 +734,8 @@ below).
637
734
 
638
735
  ### C++
639
736
 
640
- C++ plugins use `cpp/` and are built with CMake inside Docker. The current C++
641
- template is a build/run skeleton; wire it to the plugin gateway when the C++ SDK
642
- is available.
737
+ C++ plugins use `cpp/` and are built with CMake inside Docker against the
738
+ Rainbow Robotics C++ SDK (see _C++ SDK_ below).
643
739
 
644
740
  ```cpp
645
741
  #include <chrono>
@@ -661,6 +757,25 @@ int main() {
661
757
  }
662
758
  ```
663
759
 
760
+ #### C++ SDK (`rainbow-rb-sdk-dev`)
761
+
762
+ Both C++ runtimes (`cpp`, `ros2` + `rclcpp`) link the Rainbow Robotics C++ SDK
763
+ from the apt package `rainbow-rb-sdk-dev`. The `muscat-pk` dev containers already
764
+ have it, so `muscat-pk dev` / `build` need nothing extra. To build outside the
765
+ container (your own Ubuntu 24.04 machine, or the robot PC):
766
+
767
+ ```bash
768
+ 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
769
+ echo 'deb [signed-by=/usr/share/keyrings/rb-sdk-apt.gpg] https://rainbowrobotics.github.io/rb-sdk dev main' | sudo tee /etc/apt/sources.list.d/rb-sdk.list
770
+ sudo apt update && sudo apt install rainbow-rb-sdk-dev=0.1.2~dev1
771
+ ```
772
+
773
+ - CMake: `find_package(rb_sdk REQUIRED)` + `target_link_libraries(<target> rb_sdk::rb_sdk)` (C++20). pkg-config: `rb_sdk`.
774
+ - Headers: `<rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>` (`RBManipulateSDK`),
775
+ `<rainbow/rb_sdk/program/plugin_program_context.hpp>` (`rb_plugin::PluginProgramContext`, for `program.panel` commands — the header's top comment has a full `main()` example).
776
+ - The binary is linked **dynamically** (`librb_sdk.so.<version>` by SONAME) and the robot provides that `.so`. Pin the SDK to the robot's version: the dev image default is `0.1.2~dev1`; change it with `MUSCAT_PK_RB_SDK_VERSION=<version> MUSCAT_PK_REBUILD_IMAGE=1 muscat-pk dev`. Do not bundle or static-link the SDK.
777
+ - UI ↔ backend messaging (`RBPluginCall` pub/sub/serve) is Python-only for now.
778
+
664
779
  ### ROS2 (rclpy)
665
780
 
666
781
  ROS2 Python plugins use `ros2_ws/` with `runtime.rosClient: "rclpy"`. The node
@@ -684,7 +799,8 @@ class __PLUGIN_CLASS__Node(Node):
684
799
 
685
800
  ### ROS2 (rclcpp)
686
801
 
687
- ROS2 C++ plugins use `ros2_ws/` with `runtime.rosClient: "rclcpp"`.
802
+ ROS2 C++ plugins use `ros2_ws/` with `runtime.rosClient: "rclcpp"`. The node links
803
+ the Rainbow C++ SDK the same way as the `cpp` runtime (see _C++ SDK_ above).
688
804
 
689
805
  ```cpp
690
806
  #include "rclcpp/rclcpp.hpp"
@@ -943,7 +1059,9 @@ letting it guess. `muscat-pk mcp` is an MCP server exposing:
943
1059
  - `search_rb_sdk` — how to use `useRb()`, `rb.*`, `useRbFormData`/`useRbSave`, and
944
1060
  the backend runtimes.
945
1061
  - `list_rb_sdk` / `get_rb_sdk_api` — the installed `rainbow-rb-sdk` (Python
946
- backend) classes and signatures.
1062
+ backend) classes and signatures; with `lang: "cpp"`, the C++ SDK headers
1063
+ (`rainbow-rb-sdk-dev`, read out of the `muscat-pk` cpp dev image — build it
1064
+ once with `muscat-pk dev:back`).
947
1065
 
948
1066
  **Browse the components live** with `muscat-pk storybook`.
949
1067
 
@@ -10,6 +10,13 @@ Remote dev (from your laptop):
10
10
  MUSCAT_ROBOT_HOST=192.168.1.100
11
11
  MUSCAT_ROBOT_TOKEN=<service token>
12
12
  Then: muscat-pk dev:back
13
+
14
+ Handlers:
15
+ plugin.serve / plugin.subscribe / plugin.publish -> plugin UI <-> backend only
16
+ plugin.router.on / subscribe / emit -> external endpoints
17
+ (REST / Socket.IO / TCP / MQTT / WebRTC at common/plugin/<plugin_id>/<path>).
18
+ Needs a robot running rainbow-rb-sdk >= 0.0.9.dev5; older robots have no
19
+ `plugin.router` (AttributeError). See README "External endpoints".
13
20
  """
14
21
 
15
22
  import asyncio
@@ -29,6 +36,8 @@ def register_handlers():
29
36
  # Register handlers here — called after connect() so subscriptions survive
30
37
  # session reconnection in remote-dev mode.
31
38
 
39
+ # --- Plugin UI <-> backend (not reachable from outside Muscat) ---
40
+
32
41
  @plugin.subscribe("event")
33
42
  def on_ui_event(payload: dict[str, Any] | None = None):
34
43
  """Handle messages published from the plugin UI with rb.pub("event", ...)."""
@@ -47,6 +56,19 @@ def register_handlers():
47
56
  """Small query example callable from the UI with rb.action("tool/echo", ...)."""
48
57
  return {"echo": payload, "plugin": PLUGIN_NAME}
49
58
 
59
+ # --- External endpoints (REST / Socket.IO / TCP / MQTT / WebRTC) ---
60
+ # Exposed as common/plugin/<plugin_id>/hello, e.g.
61
+ # curl -X POST http://<robot>/common/plugin/<plugin_id>/hello \
62
+ # -H 'Content-Type: application/json' -d '{"name": "kim"}'
63
+ # Restrict transports with explicit_routes=["api"] (REST only), etc.
64
+ # Raise PluginRouteError(403, "msg") to set the response status.
65
+
66
+ @plugin.router.on("/hello")
67
+ def hello(payload: dict[str, Any] | None = None, meta: dict[str, Any] | None = None):
68
+ """meta = {"transport": "api"|"socket"|"tcp"|"mqtt"|"webrtc", "principal": dict | None}."""
69
+ name = (payload or {}).get("name", "there")
70
+ return {"msg": f"hi {name}", "via": (meta or {}).get("transport")}
71
+
50
72
 
51
73
  def move_j_example():
52
74
  """
@@ -77,7 +99,8 @@ async def main():
77
99
 
78
100
  register_handlers()
79
101
 
80
- plugin.publish("state", {"ready": True, "plugin": PLUGIN_NAME})
102
+ plugin.publish("state", {"ready": True, "plugin": PLUGIN_NAME}) # -> plugin UI
103
+ plugin.router.emit("/state", {"ready": True, "plugin": PLUGIN_NAME}) # -> external subscribers of common/plugin/<plugin_id>/state
81
104
  print(f"[{PLUGIN_NAME}] backend ready")
82
105
 
83
106
  try:
@@ -1,4 +1,4 @@
1
- rainbow-rb-sdk==0.1.0
2
- rainbow-rb-flat-buffers==0.1.0
3
- rainbow-rb-zenoh==0.1.0
1
+ rainbow-rb-sdk==0.1.1
2
+ rainbow-rb-flat-buffers==0.1.1
3
+ rainbow-rb-zenoh==0.1.1
4
4
  requests==2.34.2
@@ -1,14 +1,14 @@
1
1
  cmake_minimum_required(VERSION 3.16)
2
2
  project(__PLUGIN_SLUG__ CXX)
3
3
 
4
- set(CMAKE_CXX_STANDARD 17)
4
+ set(CMAKE_CXX_STANDARD 20)
5
5
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
6
 
7
+ # Rainbow Robotics C++ SDK — provided by the apt package rainbow-rb-sdk-dev
8
+ # (pre-installed in the muscat-pk dev container). Linked dynamically: the robot
9
+ # ships the matching librb_sdk.so, so do NOT bundle or static-link it.
10
+ find_package(rb_sdk REQUIRED)
11
+
7
12
  # The executable name must match runtime.entry in manifest.json.
8
13
  add_executable(__PLUGIN_SLUG__ main.cpp)
9
-
10
- # TODO(cpp-sdk): link the rainbow cpp SDK dynamically (the robot provides the
11
- # .so at runtime — do NOT bundle/static-link it). Once the SDK ships an apt
12
- # package with CMake config, enable:
13
- # find_package(rainbow_cpp_sdk REQUIRED)
14
- # target_link_libraries(__PLUGIN_SLUG__ PRIVATE rainbow::cpp_sdk)
14
+ target_link_libraries(__PLUGIN_SLUG__ PRIVATE rb_sdk::rb_sdk)
@@ -1,9 +1,16 @@
1
1
  // __PLUGIN_NAME__ — C++ plugin back end.
2
2
  //
3
- // The C++ plugin SDK is not available yet, so this is a build/run skeleton.
4
- // Once the SDK lands, connect to the plugin gateway here (the host exposes it
5
- // at the RB_PLUGIN_GATEWAY / RB_PLUGIN_ID environment variables) and wire up
6
- // pub / sub / serve, mirroring the Python rainbow.rb_sdk.RBPluginCall API.
3
+ // The Rainbow Robotics C++ SDK (apt: rainbow-rb-sdk-dev) is installed in the
4
+ // dev container and linked via CMake (rb_sdk::rb_sdk). Headers live under
5
+ // <rainbow/rb_sdk/...>:
6
+ // - manipulate/rb_manipulate_sdk.hpp RBManipulateSDK — control the robot
7
+ // - program/plugin_program_context.hpp rb_plugin::PluginProgramContext —
8
+ // program.panel commands (done/pause/stop,
9
+ // program variables); see the header's
10
+ // top comment for a full main() example
11
+ // UI <-> backend messaging (RBPluginCall pub/sub/serve) is Python-only for now.
12
+
13
+ #include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
7
14
 
8
15
  #include <chrono>
9
16
  #include <cstdlib>
@@ -17,8 +24,8 @@ int main() {
17
24
  << (plugin_id ? std::string(" (id=") + plugin_id + ")" : "")
18
25
  << std::endl;
19
26
 
20
- // TODO: connect to the plugin gateway and register handlers once the
21
- // C++ SDK is available. For now, just keep the process alive.
27
+ // e.g. RBManipulateSDK manipulate; manipulate.move.call_move_j(...);
28
+ // Keep the process alive.
22
29
  while (true) {
23
30
  std::this_thread::sleep_for(std::chrono::seconds(1));
24
31
  }
@@ -7,9 +7,13 @@ endif()
7
7
 
8
8
  find_package(ament_cmake REQUIRED)
9
9
  find_package(rclcpp REQUIRED)
10
+ # Rainbow Robotics C++ SDK (apt: rainbow-rb-sdk-dev, pre-installed in the dev
11
+ # container). Dynamically linked — the robot provides the matching librb_sdk.so.
12
+ find_package(rb_sdk REQUIRED)
10
13
 
11
14
  add_executable(__PLUGIN_SLUG___node src/node.cpp)
12
15
  ament_target_dependencies(__PLUGIN_SLUG___node rclcpp)
16
+ target_link_libraries(__PLUGIN_SLUG___node rb_sdk::rb_sdk)
13
17
 
14
18
  install(TARGETS __PLUGIN_SLUG___node
15
19
  DESTINATION lib/${PROJECT_NAME})
@@ -5,6 +5,8 @@
5
5
  // plain rclcpp node is all you need — relative topic names get the namespace
6
6
  // prefix applied automatically.
7
7
  #include "rclcpp/rclcpp.hpp"
8
+ // Rainbow Robotics C++ SDK (linked as rb_sdk::rb_sdk): RBManipulateSDK etc.
9
+ #include <rainbow/rb_sdk/manipulate/rb_manipulate_sdk.hpp>
8
10
 
9
11
  class __PLUGIN_CLASS__Node : public rclcpp::Node {
10
12
  public:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-robotics/muscat-plugin-kit",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -53,8 +53,8 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@modelcontextprotocol/sdk": "^1.29.0",
56
- "@rainbow-robotics/plugin-ui": "1.2.1",
57
- "@rainbow-robotics/preview-host": "1.2.1",
56
+ "@rainbow-robotics/plugin-ui": "1.3.0",
57
+ "@rainbow-robotics/preview-host": "1.3.0",
58
58
  "postcss": "^8.5.8",
59
59
  "socket.io": "^4.8.1",
60
60
  "typescript": "^5.8.0",
@@ -1,15 +0,0 @@
1
- import{j as r}from"./jsx-runtime-BTJTZTIL.js";import{n}from"./SideNumberInput-_BKR64MG.js";import"./index-Dmy2JGbm.js";import"./react-resizable-panels.browser-BpHwB_WW.js";import"./index-DKFBREX_.js";import"./index-CT3U1fyk.js";import"./index-ChsGqxH_.js";import"./browserUtils-CxJ5gea3.js";import"./index-BRq5TIsn.js";const y={component:n,tags:["autodocs"],argTypes:{size:{control:{type:"select"},options:["xs2","sm","md","lg"],table:{type:{summary:'"xs2" | "sm" | "md" | "lg"'}}},showPresets:{control:"boolean"},showFooter:{control:"boolean"}}},m=a=>r.jsx("div",{style:{height:700},children:r.jsx(n,{...a})}),e={parameters:{docs:{description:{story:""}}},args:{size:"md",showPresets:!0,showFooter:!0},render:m};var t,o,s;e.parameters={...e.parameters,docs:{...(t=e.parameters)==null?void 0:t.docs,source:{originalSource:`{
2
- parameters: {
3
- docs: {
4
- description: {
5
- story: ""
6
- }
7
- }
8
- },
9
- args: {
10
- size: "md",
11
- showPresets: true,
12
- showFooter: true
13
- },
14
- render: Template
15
- }`,...(s=(o=e.parameters)==null?void 0:o.docs)==null?void 0:s.source}}};const w=["Default"];export{e as Default,w as __namedExportsOrder,y as default};
@@ -1 +0,0 @@
1
- const o=()=>typeof window<"u",s=()=>{if(!o())return;const e=window.scrollY,t=document.body;t.setAttribute("data-fz",t.dataset.fz?`${Number(t.dataset.fz)+1}`:"1"),t.style.position!=="fixed"&&t.setAttribute("data-scroll-y",String(e)),t.style.overflow="hidden",t.style.touchAction="none",t.style.position="fixed",t.style.top=`-${t.dataset.scrollY}px`,t.style.left="0",t.style.width="100%"},a=()=>{if(!o())return;const e=document.body;e.dataset.fz&&Number(e.dataset.fz)>1?e.setAttribute("data-fz",`${Number(e.dataset.fz)-1}`):(e.removeAttribute("data-fz"),e.style.position="",e.style.top="",e.style.left="",e.style.width="",e.style.overflow="",e.style.paddingRight="",e.style.backgroundColor="",e.style.touchAction="",window.scrollTo(0,Number(e.dataset.scrollY)))},i=({text:e})=>{if(typeof navigator.clipboard>"u"){const t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",document.body.appendChild(t),t.focus(),t.select();try{document.execCommand("copy")}finally{document.body.removeChild(t)}return}navigator.clipboard.writeText(e)},l=()=>!(navigator.userAgent.match(/iPhone|iPad|iPod/i)==null&&navigator.maxTouchPoints!=5);export{a,i as c,l as i,s};
@@ -1 +0,0 @@
1
- (function(){"use strict";const d=function(t,o,e){const n=Math.pow(2,e),c=Math.floor((t+180)/360*n),a=o*Math.PI/180,r=Math.log(Math.tan(Math.PI/4+a/2)),s=Math.floor((1-(r+Math.PI)/(2*Math.PI))*n),l=i=>Math.max(0,Math.min(n-1,i));return{x:l(c),y:l(s)}},x=function(t,o,e){return`${t}/${o}/${e}`},T=function(t,o){return{lon:t/111320,lat:o/111320}},_=function(t){return t<18?64:t<19?32:t<20?16:t<21?8:t<22?4:t<24?2:1},P=function(t,o,e){var r;const n=new Map;if(!((r=t==null?void 0:t.content)!=null&&r.length))return n;const c=t.content,a=c.length<5e3;for(let s=o;s<=e;s++){const l=a?1:_(s);for(let i=0;i<c.length;i++){if(i%l!==0)continue;const f=c[i],E=f[0],M=f[1],{lon:m,lat:S}=T(E,M),{x:p,y:I}=d(m,S,s),h=x(s,p,I);let u=n.get(h);u===void 0&&(u=[],n.set(h,u)),u.push([E,M])}}return n},g=t=>{const{id:o,keymapContent:e,minZoom:n,maxZoom:c}=t.data,a=P(e,n,c),r={id:o,index:a};self.postMessage(r)},R=self;R.onmessage=g})();