@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.
- package/cli/README.md +8 -3
- package/cli/docker/Dockerfile.cpp +11 -6
- package/cli/docker/Dockerfile.dev +12 -0
- package/cli/src/build.mjs +6 -6
- package/cli/src/cpp.mjs +3 -1
- package/cli/src/manifest.mjs +3 -1
- package/cli/src/mcp.mjs +325 -41
- package/cli/src/ros2.mjs +3 -1
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +13 -1
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-CSzM1dKs.js → Color-YHDXOIA2-DixbmWSF.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-Bhl2G8dW.js → Docs-Blx_41TT.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DGfEBD4U.js → Docs-C0E3mHmn.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-y19ER8GV.js → Docs-CMEiBjLz.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-Q256LO6R.js → Docs-D6oFYDre.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CxnHR1EX.js → Docs-DcUa4nGr.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BIcNCkcj.js → Docs-Dv-5qjF4.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-DnUkc2jA.js → DocsRenderer-CFRXHY34-GwWANrWN.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-D8_zg1ow.js → NxAvatar.stories-CyZ4AE2b.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-DJ27PRZJ.js → NxButton.stories-Bx8UtGNd.js} +1 -1
- package/cli/storybook-dist/assets/{NxIcons.stories-Dg42riT5.js → NxIcons.stories-DeqrVzVs.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-DAhhfwBb.js → NxNumberInput.stories-skizAdd5.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-B0B2V-we.js → NxPagination.stories-gtIxt2u0.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-Cs9aWGqX.js → NxPointerHoldButton.stories-DSHMk3N5.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-A3qk290L.js → NxRangeInput.stories-C1YtrFZ1.js} +3 -3
- package/cli/storybook-dist/assets/{NxSelectBox.stories-BtA0bI0U.js → NxSelectBox.stories-DCySAtBp.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-DaVwMyfI.js → NxSlideRange.stories-gSaMOWo7.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-MEpYcafd.js → NxSwitch.stories-DF0DBiyG.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-8PqT83b0.js → NxTextArea.stories-Dill0M6z.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-DwOvyFAK.js → NxTextInput.stories-Bax14r48.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-l_KWmznJ.js → RBAccordion.stories-CCwz_pIV.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BHwMLO_b.js → RBAccordionGroup.stories-BjDxxdPV.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-XeJozdck.js → RBAlert.stories-5z6GasgP.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-BoIw5gFT.js → RBBadge.stories-BwcM7i7w.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-C_qyHveA.js → RBBattery.stories-DOeQH3rK.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-jFfiHYKe.js → RBCard.stories-DcmiOXAV.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-DTfCEweM.js → RBCheckBox.stories-BYru87fX.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-CTDnyUqG.js → RBCheckIconButton.stories-CXUjtR1a.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-DmquL7n0.js → RBCheckLabelButton.stories-BSOQV7Du.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-B8zl9AG8.js → RBChipTab.stories-ByBmeukU.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-BGRQ_ix8.js → RBChipsBoxTab.stories-GtJ01RTr.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-s5QE7tL1.js → RBColorIcons.stories-C9saET1u.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-Yrpy_B26.js → RBCommand.stories-CcvnWlWZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-C9qrp3br.js → RBConfirm.stories-hdBVSdBS.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-DZpvxSqb.js → RBDateInput.stories-BE9JQl-E.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-DSonk9Bx.js → RBDialog.stories-BaWuDgl_.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-CtuMDRWW.js → RBDivider.stories-D3INKm_Z.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-BNjRz9Cf.js → RBDrawer.stories-BZ8V0J6-.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-Bjl9aOPU.js → RBDropdownMenu.stories-DxLi8HRw.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-ChMkuohg.js → RBEmptyView.stories-DQcvDt2S.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-B5R9unwo.js → RBIconButton.stories-DJfuTpQ0.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-Cp5JeZNr.js → RBIcons.stories-BSsyb0BK.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-Dl_km9SD.js → RBInlineLabelButton.stories-Di3th--x.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-g1XwqElo.js → RBJoyStick.stories-DfGazm4n.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-iNSl-cdD.js → RBLabelButton.stories-CLbxzW56.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-DptPacw1.js → RBLight.stories-DeiuDm_P.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-CTH4jNi6.js → RBMenubar.stories-BZQTV9e5.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-BD138rOz.js → RBMultiRangeInput.stories-DDNmdi_n.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-DaXD-840.js → RBPagination.stories-CLBCEyCU.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-BHA1-yYi.js → RBPopover.stories-DRI5Kfa-.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-CoEeIK6d.js → RBPrompt.stories-DIAmypoJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-CePglWfe.js → RBRadioBox.stories-BS_N4aLf.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-BRNRgcJb.js → RBRadioButton.stories-BTm8cYvm.js} +1 -1
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-D5hBsSyB.js +15 -0
- package/cli/storybook-dist/assets/{RBResizable.stories-DqoaWeqW.js → RBResizable.stories-CxPEkbXQ.js} +1 -1
- package/cli/storybook-dist/assets/{RBSelectBox.stories-8QBhknxC.js → RBSelectBox.stories-FUQ7AcUi.js} +1 -1
- package/cli/storybook-dist/assets/{RBSheet.stories-Doaw8VOp.js → RBSheet.stories-mhVHH_71.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-BmAEpYFE.js → RBSideNumberInput.stories-5FMkLUim.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-CH-Hkgd6.js → RBSlider.stories-Bnhcp9pz.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-BEceevO_.js → RBSpinner.stories-Bf4416mP.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-BoZzoyfx.js → RBSwitch.stories-BNhFjRaV.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-CN3Algxc.js → RBTab.stories-Bsr7jlfb.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-C1UgfrL_.js → RBTable.stories-BkA6pKzs.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-ASxrEiHq.js → RBTableRow.stories-D07Z7D5-.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-0tMRRXl4.js → RBTabs.stories-DHnZiV21.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-DhWXTnBK.js → RBTextArea.stories-CtLMLuV_.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-BLI0HpCu.js → RBTextField.stories-Chz0i1-u.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-J8FkNDJo.js → RBTextInput.stories-Dk5cZfUF.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-D8UxgdX3.js → RBToast.stories-tZxbjmr8.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-CjwzqnV-.js → RBToggle.stories-VTe7a4xZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-3JwuIqYr.js → RBToggleGroup.stories-WiFnGGYx.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories-Wkx6rfug.js → RBTooltip.stories-B_L4W7Mn.js} +1 -1
- package/cli/storybook-dist/assets/{SideNumberInput-_BKR64MG.js → SideNumberInput-os5wKKf7.js} +131 -136
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +1 -0
- package/cli/storybook-dist/assets/{entry-preview-VQeOKtPr.js → entry-preview-NpzneD8a.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-Ci_IjEPD.js → iframe-DsvRKHwc.js} +2 -2
- package/cli/storybook-dist/assets/{index-DVR_O3uO.js → index-BzvSJzrT.js} +3 -3
- package/cli/storybook-dist/assets/preview-BXXleNPp.css +1 -0
- package/cli/storybook-dist/assets/preview-BXxjLQ9M.js +3 -0
- package/cli/storybook-dist/assets/{preview-DAZ7L_eg.js → preview-yXaYuZ78.js} +2 -2
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +120 -0
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +123 -5
- package/cli/templates/common/py/main.py +24 -1
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/cpp/cpp/CMakeLists.txt +7 -7
- package/cli/templates/cpp/cpp/main.cpp +13 -6
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +4 -0
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +2 -0
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-B6ukx3Pl.js +0 -15
- package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +0 -1
- package/cli/storybook-dist/assets/keymapTileIndex.worker-BsNYNbqs.js +0 -1
- package/cli/storybook-dist/assets/preview-BVJWkKiZ.css +0 -1
- package/cli/storybook-dist/assets/preview-CO9TxipX.js +0 -3
- package/cli/storybook-dist/assets/react-resizable-panels.browser-BpHwB_WW.js +0 -102
package/cli/README.md
CHANGED
|
@@ -60,10 +60,10 @@ and `50051` are reserved and must not be used by external plugins.
|
|
|
60
60
|
| runtime | source | notes |
|
|
61
61
|
|---|---|---|
|
|
62
62
|
| `{ "type": "none" }` | — | UI-only plugin; no backend container |
|
|
63
|
-
| `{ "type": "python", "entry": "main.py" }` | `py/` | Uses `rainbow.rb_sdk.RBPluginCall` for UI/backend messaging |
|
|
64
|
-
| `{ "type": "cpp", "entry": "<executable name>" }` | `cpp/` | Built with CMake in Docker
|
|
63
|
+
| `{ "type": "python", "entry": "main.py" }` | `py/` | Uses `rainbow.rb_sdk.RBPluginCall` for UI/backend messaging; `plugin.router` exposes external endpoints (`common/plugin/{id}/{path}`, needs sdk ≥ 0.0.9.dev5 on the robot; the dev bridge only registers routes — external calls need a running `common`) |
|
|
64
|
+
| `{ "type": "cpp", "entry": "<executable name>" }` | `cpp/` | Built with CMake in Docker against the C++ SDK (apt `rainbow-rb-sdk-dev`, `find_package(rb_sdk)`, dynamic link) |
|
|
65
65
|
| `{ "type": "ros2", "rosClient": "rclpy", ... }` | `ros2_ws/` | ROS2 Python node; may use Python SDK alongside ROS2 APIs |
|
|
66
|
-
| `{ "type": "ros2", "rosClient": "rclcpp", ... }` | `ros2_ws/` | ROS2 C++ node built with colcon/CMake |
|
|
66
|
+
| `{ "type": "ros2", "rosClient": "rclcpp", ... }` | `ros2_ws/` | ROS2 C++ node built with colcon/CMake, links the C++ SDK like `cpp` |
|
|
67
67
|
|
|
68
68
|
All non-`none` runtimes run in Docker during `muscat-pk dev:back`, `muscat-pk
|
|
69
69
|
dev`, and build/export flows so development matches the robot environment.
|
|
@@ -257,6 +257,11 @@ would break `dev` offline. Pre-seed an air-gapped machine with
|
|
|
257
257
|
`docker save … | gzip` + `docker load`, or force a rebuild with
|
|
258
258
|
`MUSCAT_PK_REBUILD_IMAGE=1`.
|
|
259
259
|
|
|
260
|
+
The C++ images (`Dockerfile.cpp`, `Dockerfile.dev`) install `rainbow-rb-sdk-dev`
|
|
261
|
+
from `https://rainbowrobotics.github.io/rb-sdk` (suite `dev`), pinned by the
|
|
262
|
+
`RB_SDK_VERSION` build arg. Set `MUSCAT_PK_RB_SDK_VERSION=<version>` (with
|
|
263
|
+
`MUSCAT_PK_REBUILD_IMAGE=1`) to match a robot running a different SDK version.
|
|
264
|
+
|
|
260
265
|
On Linux the dev containers run as the invoking user (`--user`), so build output
|
|
261
266
|
under `dist/` and `cpp/build-*` is not left root-owned.
|
|
262
267
|
|
|
@@ -10,12 +10,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
10
10
|
cmake \
|
|
11
11
|
&& rm -rf /var/lib/apt/lists/*
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
# robot's version
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
# Rainbow Robotics C++ SDK (rainbow-rb-sdk-dev): headers + CMake config for
|
|
14
|
+
# find_package(rb_sdk). Pinned to the robot's SDK version — the binary links
|
|
15
|
+
# librb_sdk.so.<ver> by SONAME and the robot provides that exact .so at runtime.
|
|
16
|
+
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
17
|
+
ARG RB_SDK_VERSION=0.1.2~dev1
|
|
18
|
+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
19
|
+
&& install -d /etc/apt/keyrings \
|
|
20
|
+
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
|
21
|
+
&& echo 'deb [signed-by=/etc/apt/keyrings/rb-sdk.asc] https://rainbowrobotics.github.io/rb-sdk dev main' > /etc/apt/sources.list.d/rb-sdk.list \
|
|
22
|
+
&& apt-get update && apt-get install -y --no-install-recommends "rainbow-rb-sdk-dev=${RB_SDK_VERSION}" \
|
|
23
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
19
24
|
|
|
20
25
|
WORKDIR /plugin
|
|
21
26
|
|
|
@@ -9,6 +9,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
9
9
|
python3-colcon-common-extensions \
|
|
10
10
|
&& rm -rf /var/lib/apt/lists/*
|
|
11
11
|
|
|
12
|
+
# Rainbow Robotics C++ SDK (rainbow-rb-sdk-dev): headers + CMake config for
|
|
13
|
+
# find_package(rb_sdk). Pinned to the robot's SDK version — the binary links
|
|
14
|
+
# librb_sdk.so.<ver> by SONAME and the robot provides that exact .so at runtime.
|
|
15
|
+
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
16
|
+
ARG RB_SDK_VERSION=0.1.2~dev1
|
|
17
|
+
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
18
|
+
&& install -d /etc/apt/keyrings \
|
|
19
|
+
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
|
20
|
+
&& echo 'deb [signed-by=/etc/apt/keyrings/rb-sdk.asc] https://rainbowrobotics.github.io/rb-sdk dev main' > /etc/apt/sources.list.d/rb-sdk.list \
|
|
21
|
+
&& apt-get update && apt-get install -y --no-install-recommends "rainbow-rb-sdk-dev=${RB_SDK_VERSION}" \
|
|
22
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
23
|
+
|
|
12
24
|
WORKDIR /plugin
|
|
13
25
|
|
|
14
26
|
CMD ["bash"]
|
package/cli/src/build.mjs
CHANGED
|
@@ -65,11 +65,11 @@ function runViteBuild(pluginRoot, { mode = "production" } = {}) {
|
|
|
65
65
|
if (r.status !== 0) throw new Error("vite build failed");
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function isMainBranch() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
68
|
+
// function isMainBranch() {
|
|
69
|
+
// const r = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" });
|
|
70
|
+
// const branch = r.stdout?.trim();
|
|
71
|
+
// return branch === "main" || branch === "master";
|
|
72
|
+
// }
|
|
73
73
|
|
|
74
74
|
// For ros2 plugins, compile the colcon workspace inside the Jazzy + Python 3.12 container.
|
|
75
75
|
// rclpy: arch-agnostic (pure Python) → dist/ros2_ws/install/ (built once)
|
|
@@ -169,7 +169,7 @@ function isPrerelease(version) {
|
|
|
169
169
|
// stableOnly=true → skips pre-release (dev/a/b/rc) versions.
|
|
170
170
|
async function latestPypiVersion(pkg, { stableOnly = false } = {}) {
|
|
171
171
|
const res = await fetch(`https://pypi.org/pypi/${pkg}/json`, {
|
|
172
|
-
signal:
|
|
172
|
+
signal: new AbortController().signal.timeout(5000),
|
|
173
173
|
});
|
|
174
174
|
if (!res.ok) throw new Error(`pypi ${pkg}: ${res.status}`);
|
|
175
175
|
const data = await res.json();
|
package/cli/src/cpp.mjs
CHANGED
|
@@ -22,9 +22,11 @@ export async function ensureImage(image = CPP_DEV_IMAGE, { platform = null } = {
|
|
|
22
22
|
const dockerfile = await resolveDockerfile("Dockerfile.cpp");
|
|
23
23
|
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
24
24
|
const platformFlags = platform ? ["--platform", platform] : [];
|
|
25
|
+
const sdkVer = process.env.MUSCAT_PK_RB_SDK_VERSION;
|
|
26
|
+
const buildArgs = sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : [];
|
|
25
27
|
const r = spawnSync(
|
|
26
28
|
"docker",
|
|
27
|
-
["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
29
|
+
["build", ...platformFlags, ...buildArgs, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
28
30
|
{ stdio: "inherit" },
|
|
29
31
|
);
|
|
30
32
|
if (r.status !== 0) throw new Error("docker build failed.");
|
package/cli/src/manifest.mjs
CHANGED
|
@@ -15,7 +15,7 @@ const RUNTIME_TYPES = ["none", "python", "cpp", "ros2"];
|
|
|
15
15
|
const ROS_CLIENTS = ["rclpy", "rclcpp"];
|
|
16
16
|
const I18N_LANGS = ["ko", "en", "ja", "zh", "vi", "th", "es"];
|
|
17
17
|
const TARGET_CATEGORIES = ["manipulate", "amr", "rby1", "vision"];
|
|
18
|
-
const VARIABLE_TYPES = ["number", "string", "array"];
|
|
18
|
+
const VARIABLE_TYPES = ["number", "string", "boolean", "array"];
|
|
19
19
|
const VARIABLE_NAME_PREFIX = "RBP_";
|
|
20
20
|
const VARIABLE_NAME_RE = /^RBP_[A-Za-z0-9_]+$/;
|
|
21
21
|
|
|
@@ -144,6 +144,8 @@ function validateVariables(variables, errors) {
|
|
|
144
144
|
errors.push(`variables[${i}].init must be a number when type is "number"`);
|
|
145
145
|
else if (v.type === "string" && typeof v.init !== "string")
|
|
146
146
|
errors.push(`variables[${i}].init must be a string when type is "string"`);
|
|
147
|
+
else if (v.type === "boolean" && typeof v.init !== "boolean")
|
|
148
|
+
errors.push(`variables[${i}].init must be a boolean when type is "boolean"`);
|
|
147
149
|
else if (v.type === "array" && !Array.isArray(v.init))
|
|
148
150
|
errors.push(`variables[${i}].init must be an array when type is "array"`);
|
|
149
151
|
if (v.description !== undefined && v.description !== null)
|
package/cli/src/mcp.mjs
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Plugin SDK docs + Python backend:
|
|
15
15
|
// - search_rb_sdk search the plugin-author README (useRb(), rb.*, manifest, runtimes).
|
|
16
|
-
// - list_rb_sdk classes/functions of the installed rainbow.rb_sdk
|
|
16
|
+
// - list_rb_sdk classes/functions of the installed rainbow.rb_sdk
|
|
17
|
+
// (lang: "python" | "cpp" — cpp reads the headers of the
|
|
18
|
+
// rainbow-rb-sdk-dev apt package out of the cpp dev image).
|
|
17
19
|
// - get_rb_sdk_api signature + docstring for an rb_sdk class/function.
|
|
18
20
|
//
|
|
19
21
|
// Add to an MCP client config, e.g. Claude Code's .mcp.json:
|
|
@@ -22,13 +24,23 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
22
24
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
25
|
import { z } from "zod";
|
|
24
26
|
import ts from "typescript";
|
|
25
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
appendFileSync,
|
|
29
|
+
existsSync,
|
|
30
|
+
mkdirSync,
|
|
31
|
+
readdirSync,
|
|
32
|
+
readFileSync,
|
|
33
|
+
renameSync,
|
|
34
|
+
statSync,
|
|
35
|
+
writeFileSync,
|
|
36
|
+
} from "node:fs";
|
|
26
37
|
import { spawnSync } from "node:child_process";
|
|
27
38
|
import { homedir } from "node:os";
|
|
28
39
|
import { join, resolve } from "node:path";
|
|
29
40
|
import { fileURLToPath } from "node:url";
|
|
30
41
|
import { kitRoot, kitVersion, findPluginRoot, venvPython } from "./util.mjs";
|
|
31
42
|
import { SHIPPED_STORIES_SRC, SHIPPED_DESIGN_TOKENS } from "./storybook.mjs";
|
|
43
|
+
import { CPP_DEV_IMAGE } from "./cpp.mjs";
|
|
32
44
|
|
|
33
45
|
const TEMPLATE_README = join(kitRoot, "cli/templates/common/README.md");
|
|
34
46
|
// Design tokens: prefer the shipped copy, fall back to the monorepo source.
|
|
@@ -40,14 +52,10 @@ const DESIGN_TOKENS_CSS = existsSync(SHIPPED_DESIGN_TOKENS)
|
|
|
40
52
|
/* react.d.ts introspection — component/hook list + prop types */
|
|
41
53
|
/* -------------------------------------------------------------------------- */
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
// is enough even in an external install.
|
|
48
|
-
async function loadReactTypes() {
|
|
49
|
-
if (typesCache) return typesCache;
|
|
50
|
-
|
|
55
|
+
// plugin-ui/react's dist/react.d.ts. It's self-contained (rb-components types
|
|
56
|
+
// are inlined at build — see plugin-ui/build-dist.mjs), so this one file is
|
|
57
|
+
// enough even in an external install.
|
|
58
|
+
async function reactDtsPath() {
|
|
51
59
|
const entryUrl = await import.meta.resolve("@rainbow-robotics/plugin-ui/react");
|
|
52
60
|
const dtsPath = fileURLToPath(entryUrl).replace(/\.js$/, ".d.ts");
|
|
53
61
|
if (!existsSync(dtsPath)) {
|
|
@@ -55,19 +63,23 @@ async function loadReactTypes() {
|
|
|
55
63
|
`plugin-ui type declarations not found at ${dtsPath} — reinstall or rebuild @rainbow-robotics/plugin-ui.`,
|
|
56
64
|
);
|
|
57
65
|
}
|
|
66
|
+
return dtsPath;
|
|
67
|
+
}
|
|
58
68
|
|
|
69
|
+
function loadReactTypes(dtsPath) {
|
|
70
|
+
// Without moduleResolution the `import ... from "react"` inside react.d.ts
|
|
71
|
+
// never resolves and every React-derived type collapses to `any`.
|
|
59
72
|
const program = ts.createProgram([dtsPath], {
|
|
60
73
|
target: ts.ScriptTarget.ES2022,
|
|
61
74
|
module: ts.ModuleKind.ESNext,
|
|
75
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
62
76
|
skipLibCheck: true,
|
|
63
77
|
});
|
|
64
78
|
const sourceFile = program.getSourceFile(dtsPath);
|
|
65
79
|
const checker = program.getTypeChecker();
|
|
66
80
|
const moduleSymbol = sourceFile && checker.getSymbolAtLocation(sourceFile);
|
|
67
81
|
const exports = moduleSymbol ? checker.getExportsOfModule(moduleSymbol) : [];
|
|
68
|
-
|
|
69
|
-
typesCache = { checker, sourceFile, exports };
|
|
70
|
-
return typesCache;
|
|
82
|
+
return { checker, sourceFile, exports };
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
// The barrel's trailing `export { A, B, ... }` produces Alias symbols whose
|
|
@@ -83,6 +95,124 @@ function exportKind(checker, symbol) {
|
|
|
83
95
|
return resolved.flags & ts.SymbolFlags.Value ? "value" : "type";
|
|
84
96
|
}
|
|
85
97
|
|
|
98
|
+
// typeToString alone often prints an opaque alias (`ForwardRefExoticComponent<PropsType$s>`).
|
|
99
|
+
// The first call signature works for plain components and forwardRef alike; its
|
|
100
|
+
// first parameter is the props object.
|
|
101
|
+
function ownProps(checker, type) {
|
|
102
|
+
const [signature] = type.getCallSignatures?.() ?? [];
|
|
103
|
+
const param = signature?.getParameters?.()[0];
|
|
104
|
+
if (!param) return [];
|
|
105
|
+
const declaration = param.valueDeclaration ?? param.declarations?.[0];
|
|
106
|
+
if (!declaration) return [];
|
|
107
|
+
const propsType = checker.getTypeOfSymbolAtLocation(param, declaration);
|
|
108
|
+
|
|
109
|
+
return checker
|
|
110
|
+
.getPropertiesOfType(propsType)
|
|
111
|
+
// Without this, ~220 inherited React DOM attributes bury the component's own API.
|
|
112
|
+
.filter((prop) =>
|
|
113
|
+
(prop.declarations ?? []).some(
|
|
114
|
+
(d) => !d.getSourceFile().fileName.includes("node_modules"),
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
.map((prop) => {
|
|
118
|
+
const d = prop.valueDeclaration ?? prop.declarations[0];
|
|
119
|
+
const optional = prop.flags & ts.SymbolFlags.Optional ? "?" : "";
|
|
120
|
+
return `${prop.getName()}${optional}: ${checker.typeToString(
|
|
121
|
+
checker.getTypeOfSymbolAtLocation(prop, d),
|
|
122
|
+
)}`;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Kept out of the tool handler so every export can be precomputed and cached.
|
|
127
|
+
function propsText(checker, sourceFile, name, symbol) {
|
|
128
|
+
const resolved = resolveExport(checker, symbol);
|
|
129
|
+
const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0] ?? sourceFile;
|
|
130
|
+
const type = checker.getTypeOfSymbolAtLocation(resolved, declaration);
|
|
131
|
+
const signature = checker.typeToString(
|
|
132
|
+
type,
|
|
133
|
+
undefined,
|
|
134
|
+
ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.MultilineObjectLiterals,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const props = ownProps(checker, type);
|
|
138
|
+
if (props.length) {
|
|
139
|
+
// Keep the signature line: RBSlider's value/min/max come from Radix and the
|
|
140
|
+
// own-props filter drops them.
|
|
141
|
+
return `# ${name} — own props\n${props.sort().join("\n")}\n\nfull type: ${signature}`;
|
|
142
|
+
}
|
|
143
|
+
// No own props means either a Radix wrapper or unresolved React types. The two
|
|
144
|
+
// look identical, so name the second case explicitly.
|
|
145
|
+
if (/<any>/.test(signature)) {
|
|
146
|
+
return (
|
|
147
|
+
`# ${name} — prop types could not be resolved\n${signature}\n\n` +
|
|
148
|
+
"React's types didn't resolve, so this collapsed to `any`. Install `@types/react` " +
|
|
149
|
+
"in this project (`npm i -D @types/react`) and retry for the real props."
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return (
|
|
153
|
+
`# ${name} — full type (no plugin-ui-specific props; it forwards a third-party ` +
|
|
154
|
+
`component's props)\n${signature}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Rendering every export up front is what makes the result cacheable: once the
|
|
159
|
+
// text exists no TypeChecker is needed.
|
|
160
|
+
function buildUiIndex(dtsPath) {
|
|
161
|
+
const { checker, sourceFile, exports } = loadReactTypes(dtsPath);
|
|
162
|
+
const props = {};
|
|
163
|
+
for (const symbol of exports) {
|
|
164
|
+
props[symbol.getName()] = propsText(checker, sourceFile, symbol.getName(), symbol);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
components: exports
|
|
168
|
+
.map((sym) => `${sym.getName()} (${exportKind(checker, sym)})`)
|
|
169
|
+
.sort()
|
|
170
|
+
.join("\n"),
|
|
171
|
+
props,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ts.createProgram pulls in ~1100 source files (~1.5s) and an MCP server is a fresh
|
|
176
|
+
// process per session. The key covers the .d.ts identity and the kit version.
|
|
177
|
+
const UI_INDEX_CACHE_DIR = join(homedir(), ".cache", "muscat-pk");
|
|
178
|
+
const UI_INDEX_CACHE = join(UI_INDEX_CACHE_DIR, "ui-index.json");
|
|
179
|
+
|
|
180
|
+
let uiIndexCache = null;
|
|
181
|
+
|
|
182
|
+
async function loadUiIndex() {
|
|
183
|
+
if (uiIndexCache) return uiIndexCache;
|
|
184
|
+
const dtsPath = await reactDtsPath();
|
|
185
|
+
const { mtimeMs, size } = statSync(dtsPath);
|
|
186
|
+
const key = `${dtsPath}|${mtimeMs}|${size}|${kitVersion()}`;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const cached = JSON.parse(readFileSync(UI_INDEX_CACHE, "utf8"));
|
|
190
|
+
// A half-written file still parses as JSON, so check the shape too.
|
|
191
|
+
if (
|
|
192
|
+
cached.key === key &&
|
|
193
|
+
typeof cached.components === "string" &&
|
|
194
|
+
cached.props &&
|
|
195
|
+
typeof cached.props === "object"
|
|
196
|
+
) {
|
|
197
|
+
return (uiIndexCache = cached);
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
/* absent, unreadable or corrupt — rebuild below */
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
uiIndexCache = { key, ...buildUiIndex(dtsPath) };
|
|
204
|
+
try {
|
|
205
|
+
mkdirSync(UI_INDEX_CACHE_DIR, { recursive: true });
|
|
206
|
+
// rename is atomic, so a concurrent server never reads a half-written file.
|
|
207
|
+
const tmp = `${UI_INDEX_CACHE}.${process.pid}.tmp`;
|
|
208
|
+
writeFileSync(tmp, JSON.stringify(uiIndexCache));
|
|
209
|
+
renameSync(tmp, UI_INDEX_CACHE);
|
|
210
|
+
} catch {
|
|
211
|
+
/* read-only fs / sandbox: the cache is an optimisation, never a requirement */
|
|
212
|
+
}
|
|
213
|
+
return uiIndexCache;
|
|
214
|
+
}
|
|
215
|
+
|
|
86
216
|
/* -------------------------------------------------------------------------- */
|
|
87
217
|
/* Storybook story source lookup */
|
|
88
218
|
/* -------------------------------------------------------------------------- */
|
|
@@ -262,6 +392,114 @@ ${script}
|
|
|
262
392
|
return { data };
|
|
263
393
|
}
|
|
264
394
|
|
|
395
|
+
/* -------------------------------------------------------------------------- */
|
|
396
|
+
/* rainbow-rb-sdk-dev (C++) headers — copied out of the cpp dev image */
|
|
397
|
+
/* -------------------------------------------------------------------------- */
|
|
398
|
+
|
|
399
|
+
const CPP_INCLUDE_CACHE = join(UI_INDEX_CACHE_DIR, "rb-sdk-include");
|
|
400
|
+
// Headers we don't want to surface: third-party vendored code, pybind glue,
|
|
401
|
+
// flatbuffers-generated schemas, and the industrial comm stacks.
|
|
402
|
+
const CPP_SKIP = /^(vendor|binding|rb_comm_source)\/|_generated\.h$/;
|
|
403
|
+
|
|
404
|
+
// The apt package is only installed inside the muscat-pk-cpp-dev image, so
|
|
405
|
+
// copy /usr/include/rainbow/rb_sdk out once per image build (cache key =
|
|
406
|
+
// image id) with `docker cp` — no host tar needed.
|
|
407
|
+
function cppIncludeDir() {
|
|
408
|
+
const insp = spawnSync("docker", ["image", "inspect", "-f", "{{.Id}}", CPP_DEV_IMAGE], { encoding: "utf8" });
|
|
409
|
+
if (insp.error) return { error: "Docker not found — the C++ SDK headers live in the muscat-pk cpp dev image." };
|
|
410
|
+
if (insp.status !== 0) {
|
|
411
|
+
return {
|
|
412
|
+
error:
|
|
413
|
+
`Docker image ${CPP_DEV_IMAGE} not built yet. Run \`muscat-pk dev:back\` (or \`muscat-pk build\`) once ` +
|
|
414
|
+
"in a cpp / rclcpp plugin to build it, then retry.",
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
const dir = join(CPP_INCLUDE_CACHE, insp.stdout.trim().replace(/^sha256:/, "").slice(0, 12));
|
|
418
|
+
const out = join(dir, "rb_sdk");
|
|
419
|
+
if (existsSync(out)) return { dir: out };
|
|
420
|
+
mkdirSync(dir, { recursive: true });
|
|
421
|
+
const ctr = spawnSync("docker", ["create", CPP_DEV_IMAGE], { encoding: "utf8" });
|
|
422
|
+
if (ctr.status !== 0) return { error: `docker create failed: ${(ctr.stderr || "").trim().slice(0, 300)}` };
|
|
423
|
+
const id = ctr.stdout.trim();
|
|
424
|
+
const cp = spawnSync("docker", ["cp", `${id}:/usr/include/rainbow/rb_sdk`, dir], { encoding: "utf8" });
|
|
425
|
+
spawnSync("docker", ["rm", "-f", id], { encoding: "utf8" });
|
|
426
|
+
if (cp.status !== 0 || !existsSync(out)) {
|
|
427
|
+
return { error: `docker cp failed: ${(cp.stderr || "").trim().slice(0, 300)}` };
|
|
428
|
+
}
|
|
429
|
+
return { dir: out };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function cppHeaders(root) {
|
|
433
|
+
const files = [];
|
|
434
|
+
const walk = (d, rel) => {
|
|
435
|
+
for (const name of readdirSync(d)) {
|
|
436
|
+
const r = rel ? `${rel}/${name}` : name;
|
|
437
|
+
if (CPP_SKIP.test(r)) continue;
|
|
438
|
+
const full = join(d, name);
|
|
439
|
+
if (statSync(full).isDirectory()) walk(full, r);
|
|
440
|
+
else if (/\.hpp$/.test(name)) files.push(r);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
walk(root, "");
|
|
444
|
+
return files.sort();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// "class Foo {" / "class Foo : public Bar {" — not forward declarations.
|
|
448
|
+
const CLASS_RE = /^\s*(?:class|struct)\s+(\w+)\b(?![^\n]*;\s*$)/;
|
|
449
|
+
|
|
450
|
+
function cppClassesIn(text) {
|
|
451
|
+
return [...new Set(text.split("\n").map((l) => l.match(CLASS_RE)?.[1]).filter(Boolean))];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Return the class body (brace-matched) plus the file's leading comment block,
|
|
455
|
+
// which in these headers usually carries a full usage example.
|
|
456
|
+
function cppExtract(text, className, method) {
|
|
457
|
+
const lines = text.split("\n");
|
|
458
|
+
const start = lines.findIndex((l) => CLASS_RE.test(l) && l.match(CLASS_RE)[1] === className);
|
|
459
|
+
if (start < 0) return null;
|
|
460
|
+
let depth = 0, end = start, seen = false;
|
|
461
|
+
for (let i = start; i < lines.length; i++) {
|
|
462
|
+
for (const ch of lines[i]) {
|
|
463
|
+
if (ch === "{") { depth++; seen = true; }
|
|
464
|
+
else if (ch === "}") depth--;
|
|
465
|
+
}
|
|
466
|
+
if (seen && depth === 0) { end = i; break; }
|
|
467
|
+
}
|
|
468
|
+
const body = lines.slice(start, end + 1);
|
|
469
|
+
if (method) {
|
|
470
|
+
// Declarations sit at class-member indent (<= 2 spaces) and the name is
|
|
471
|
+
// not preceded by `.`/`->`/`::` — that skips call sites inside bodies.
|
|
472
|
+
const re = new RegExp(`^ {0,2}[^(]*(?<![\\w.>:])${method.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
|
|
473
|
+
const hits = [];
|
|
474
|
+
for (let i = 0; i < body.length; i++) {
|
|
475
|
+
if (!re.test(body[i]) || /^\s*\/\//.test(body[i])) continue;
|
|
476
|
+
let a = i;
|
|
477
|
+
while (a > 0 && /^\s*\/\//.test(body[a - 1])) a--;
|
|
478
|
+
let b = i;
|
|
479
|
+
while (b < body.length - 1 && !/[{;]/.test(body[b])) b++;
|
|
480
|
+
hits.push(body.slice(a, b + 1).join("\n"));
|
|
481
|
+
}
|
|
482
|
+
return hits.length ? hits.join("\n\n") : null;
|
|
483
|
+
}
|
|
484
|
+
const head = [];
|
|
485
|
+
for (const l of lines) {
|
|
486
|
+
if (/^\s*\/\//.test(l)) head.push(l);
|
|
487
|
+
else if (l.trim() === "" || /^#pragma/.test(l)) continue;
|
|
488
|
+
else break;
|
|
489
|
+
}
|
|
490
|
+
return (head.length > 3 ? head.join("\n") + "\n\n" : "") + body.join("\n");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function cppFind(className) {
|
|
494
|
+
const { dir, error } = cppIncludeDir();
|
|
495
|
+
if (error) return { error };
|
|
496
|
+
for (const rel of cppHeaders(dir)) {
|
|
497
|
+
const text = readFileSync(join(dir, rel), "utf8");
|
|
498
|
+
if (cppClassesIn(text).includes(className)) return { rel, text };
|
|
499
|
+
}
|
|
500
|
+
return { error: `No class "${className}" in the C++ SDK headers. Call list_rb_sdk with lang "cpp" for valid names.` };
|
|
501
|
+
}
|
|
502
|
+
|
|
265
503
|
/* -------------------------------------------------------------------------- */
|
|
266
504
|
/* client registration — `muscat-pk mcp --install <client>` */
|
|
267
505
|
/* -------------------------------------------------------------------------- */
|
|
@@ -313,11 +551,8 @@ export async function runMcp() {
|
|
|
313
551
|
inputSchema: {},
|
|
314
552
|
},
|
|
315
553
|
async () => {
|
|
316
|
-
const {
|
|
317
|
-
|
|
318
|
-
.map((sym) => `${sym.getName()} (${exportKind(checker, sym)})`)
|
|
319
|
-
.sort();
|
|
320
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
554
|
+
const { components } = await loadUiIndex();
|
|
555
|
+
return { content: [{ type: "text", text: components }] };
|
|
321
556
|
},
|
|
322
557
|
);
|
|
323
558
|
|
|
@@ -331,9 +566,8 @@ export async function runMcp() {
|
|
|
331
566
|
inputSchema: { name: z.string().describe('Exported name, e.g. "RBSlider"') },
|
|
332
567
|
},
|
|
333
568
|
async ({ name }) => {
|
|
334
|
-
const {
|
|
335
|
-
|
|
336
|
-
if (!symbol) {
|
|
569
|
+
const { props } = await loadUiIndex();
|
|
570
|
+
if (!Object.hasOwn(props, name)) {
|
|
337
571
|
return {
|
|
338
572
|
isError: true,
|
|
339
573
|
content: [
|
|
@@ -344,15 +578,7 @@ export async function runMcp() {
|
|
|
344
578
|
],
|
|
345
579
|
};
|
|
346
580
|
}
|
|
347
|
-
|
|
348
|
-
const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0] ?? sourceFile;
|
|
349
|
-
const type = checker.getTypeOfSymbolAtLocation(resolved, declaration);
|
|
350
|
-
const text = checker.typeToString(
|
|
351
|
-
type,
|
|
352
|
-
undefined,
|
|
353
|
-
ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.MultilineObjectLiterals,
|
|
354
|
-
);
|
|
355
|
-
return { content: [{ type: "text", text }] };
|
|
581
|
+
return { content: [{ type: "text", text: props[name] }] };
|
|
356
582
|
},
|
|
357
583
|
);
|
|
358
584
|
|
|
@@ -399,6 +625,8 @@ export async function runMcp() {
|
|
|
399
625
|
title: "Search the Muscat plugin-author docs",
|
|
400
626
|
description:
|
|
401
627
|
"Search the Muscat plugin-author README for how to use useRb(), rb.pub/sub/action/serve, " +
|
|
628
|
+
"plugin.router (external endpoints: router.on/subscribe/emit exposed over REST/Socket.IO/TCP/" +
|
|
629
|
+
"MQTT/WebRTC at common/plugin/{plugin_id}/{path}, PluginRouteError, meta.transport/principal), " +
|
|
402
630
|
"rb.toast/alert/confirm/prompt, useRbFormData/useRbSave (save/restore form data through the " +
|
|
403
631
|
"host), manifest.json (including the \"variables\" declaration for the pyfm variable picker — " +
|
|
404
632
|
"names require the RBP_ prefix), program variables & executor state (ctx.get_variable/" +
|
|
@@ -446,7 +674,10 @@ export async function runMcp() {
|
|
|
446
674
|
const other = { shadow: [], radius: [], spacing: [] };
|
|
447
675
|
for (const t of vars) {
|
|
448
676
|
const m = t.group?.match(/^color-(bg|fg|border|fill)$/);
|
|
449
|
-
|
|
677
|
+
// Class name only: the value is just an indirection to another CSS var
|
|
678
|
+
// (var(--color-scale-zinc-200)), and each class is declared twice (light/dark)
|
|
679
|
+
// with different values — dropping the value lets the Set dedup collapse them.
|
|
680
|
+
if (m && t.class) colorGroups[m[1]].push(t.class);
|
|
450
681
|
else if (t.name.startsWith("shadow-")) other.shadow.push(`shadow-${t.name.slice(7)}`);
|
|
451
682
|
else if (t.name.startsWith("radius-")) other.radius.push(`rounded-${t.name.slice(7)}`);
|
|
452
683
|
else if (t.name.startsWith("spacing-")) other.spacing.push(t.name);
|
|
@@ -509,14 +740,28 @@ export async function runMcp() {
|
|
|
509
740
|
server.registerTool(
|
|
510
741
|
"list_rb_sdk",
|
|
511
742
|
{
|
|
512
|
-
title: "List rainbow-rb-sdk
|
|
743
|
+
title: "List rainbow-rb-sdk classes and functions (Python or C++)",
|
|
513
744
|
description:
|
|
514
|
-
"List the public classes and functions of the
|
|
515
|
-
"
|
|
516
|
-
"before writing py/main.py
|
|
517
|
-
|
|
745
|
+
"List the public classes and functions of the plugin BACKEND SDK. lang \"python\" (default): the " +
|
|
746
|
+
"installed rainbow.rb_sdk package (RBPluginCall, RBManipulateSDK, RBAmrSDK, PluginProgramContext, " +
|
|
747
|
+
"...) — call before writing py/main.py; requires rainbow-rb-sdk to be pip-installed. lang \"cpp\": " +
|
|
748
|
+
"the C++ SDK headers (apt rainbow-rb-sdk-dev: RBManipulateSDK, rb_plugin::PluginProgramContext, " +
|
|
749
|
+
"...) grouped by header path — call before writing cpp/main.cpp or an rclcpp node; requires the " +
|
|
750
|
+
"muscat-pk-cpp-dev Docker image (built by `muscat-pk dev:back`). Note: RBPluginCall (UI pub/sub/" +
|
|
751
|
+
"serve) exists in Python only.",
|
|
752
|
+
inputSchema: { lang: z.enum(["python", "cpp"]).optional().describe('Default "python"') },
|
|
518
753
|
},
|
|
519
|
-
async () => {
|
|
754
|
+
async ({ lang }) => {
|
|
755
|
+
if (lang === "cpp") {
|
|
756
|
+
const { dir, error } = cppIncludeDir();
|
|
757
|
+
if (error) return { isError: true, content: [{ type: "text", text: error }] };
|
|
758
|
+
const out = [];
|
|
759
|
+
for (const rel of cppHeaders(dir)) {
|
|
760
|
+
const classes = cppClassesIn(readFileSync(join(dir, rel), "utf8"));
|
|
761
|
+
if (classes.length) out.push(`${rel}: ${classes.join(", ")}`);
|
|
762
|
+
}
|
|
763
|
+
return { content: [{ type: "text", text: `#include <rainbow/rb_sdk/...>\n\n${out.join("\n")}` }] };
|
|
764
|
+
}
|
|
520
765
|
const { data, error } = introspectRbSdk(`
|
|
521
766
|
import inspect, json
|
|
522
767
|
mod = importlib.import_module("rainbow.rb_sdk")
|
|
@@ -536,19 +781,22 @@ print(json.dumps(out))
|
|
|
536
781
|
server.registerTool(
|
|
537
782
|
"get_rb_sdk_api",
|
|
538
783
|
{
|
|
539
|
-
title: "Get a rainbow-rb-sdk class/function signature + docs",
|
|
784
|
+
title: "Get a rainbow-rb-sdk class/function signature + docs (Python or C++)",
|
|
540
785
|
description:
|
|
541
786
|
"Get the signature(s) and docstring for a rainbow.rb_sdk class or function (e.g. " +
|
|
542
787
|
"'PluginProgramContext', 'RBManipulateSDK'). For a class, lists its public methods with " +
|
|
543
788
|
"signatures. To drill into ONE method of a class (e.g. RBManipulateSDK.call_move_j), pass " +
|
|
544
|
-
"`method` alongside `name` — do not put both in `name`.
|
|
545
|
-
"pip-installed."
|
|
789
|
+
"`method` alongside `name` — do not put both in `name`. lang \"python\" (default) introspects the " +
|
|
790
|
+
"pip-installed package. lang \"cpp\" returns the class declaration from the C++ SDK header " +
|
|
791
|
+
"(rainbow-rb-sdk-dev) with its header comment — usually a full usage example — and the " +
|
|
792
|
+
"#include path; with `method`, just that method's declaration and comment.",
|
|
546
793
|
inputSchema: {
|
|
547
794
|
name: z.string().describe('Class or function name, e.g. "RBManipulateSDK"'),
|
|
548
795
|
method: z.string().optional().describe('Optional: a specific method on that class, e.g. "call_move_j"'),
|
|
796
|
+
lang: z.enum(["python", "cpp"]).optional().describe('Default "python"'),
|
|
549
797
|
},
|
|
550
798
|
},
|
|
551
|
-
async ({ name, method }) => {
|
|
799
|
+
async ({ name, method, lang }) => {
|
|
552
800
|
// Tolerate a combined "ClassName method_name" in `name` (a common agent
|
|
553
801
|
// mistake) by splitting it ourselves instead of failing validation.
|
|
554
802
|
let className = name;
|
|
@@ -558,6 +806,42 @@ print(json.dumps(out))
|
|
|
558
806
|
if (parts.length === 2) [className, methodName] = parts;
|
|
559
807
|
}
|
|
560
808
|
|
|
809
|
+
if (lang === "cpp") {
|
|
810
|
+
const cls = className.replace(/^.*::/, "");
|
|
811
|
+
let { rel, text, error } = cppFind(cls);
|
|
812
|
+
if (error) return { isError: true, content: [{ type: "text", text: error }] };
|
|
813
|
+
let body = cppExtract(text, cls, methodName);
|
|
814
|
+
let owner = cls;
|
|
815
|
+
if (!body && methodName) {
|
|
816
|
+
// Like the Python path: most calls live on sub-SDK members
|
|
817
|
+
// (RBManipulateMoveSDK move; -> sdk.move.call_move_j) or on the base
|
|
818
|
+
// class — look there before giving up.
|
|
819
|
+
const decl = cppExtract(text, cls);
|
|
820
|
+
const cands = [
|
|
821
|
+
...[...decl.matchAll(/^\s*(\w+SDK)\s+(\w+);/gm)].map((m) => [m[1], `${cls}.${m[2]}`]),
|
|
822
|
+
...[...decl.matchAll(/:\s*public\s+(\w+)/g)].map((m) => [m[1], `${cls} (base ${m[1]})`]),
|
|
823
|
+
];
|
|
824
|
+
for (const [c, label] of cands) {
|
|
825
|
+
const f = cppFind(c);
|
|
826
|
+
if (f.error) continue;
|
|
827
|
+
const b = cppExtract(f.text, c, methodName);
|
|
828
|
+
if (b) { ({ rel, text } = f); body = b; owner = label; break; }
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (!body) {
|
|
832
|
+
return {
|
|
833
|
+
isError: true,
|
|
834
|
+
content: [{ type: "text", text: `No method "${methodName}" declared in class "${cls}" (${rel}) or its sub-SDK members / base class. Call get_rb_sdk_api with just \`name\` to see the class.` }],
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const ns = text.match(/^namespace\s+(\w+)\s*\{/m)?.[1];
|
|
838
|
+
const header =
|
|
839
|
+
`// #include <rainbow/rb_sdk/${rel}>${ns ? ` (namespace ${ns})` : ""}\n` +
|
|
840
|
+
`// link: find_package(rb_sdk) + rb_sdk::rb_sdk\n` +
|
|
841
|
+
(methodName ? `// ${owner}.${methodName}\n` : "") + "\n";
|
|
842
|
+
return { content: [{ type: "text", text: header + body.slice(0, 60_000) }] };
|
|
843
|
+
}
|
|
844
|
+
|
|
561
845
|
const { data, error } = introspectRbSdk(`
|
|
562
846
|
import inspect, json
|
|
563
847
|
mod = importlib.import_module("rainbow.rb_sdk")
|
package/cli/src/ros2.mjs
CHANGED
|
@@ -37,9 +37,11 @@ export async function ensureImage(image = ROS2_DEV_IMAGE, { platform = null } =
|
|
|
37
37
|
const dockerfile = await resolveDockerfile("Dockerfile.dev");
|
|
38
38
|
log.step(`docker build -t ${image}${platform ? ` (${platform})` : ""} (first run is slow, then cached)…`);
|
|
39
39
|
const platformFlags = platform ? ["--platform", platform] : [];
|
|
40
|
+
const sdkVer = process.env.MUSCAT_PK_RB_SDK_VERSION;
|
|
41
|
+
const buildArgs = sdkVer ? ["--build-arg", `RB_SDK_VERSION=${sdkVer}`] : [];
|
|
40
42
|
const r = spawnSync(
|
|
41
43
|
"docker",
|
|
42
|
-
["build", ...platformFlags, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
44
|
+
["build", ...platformFlags, ...buildArgs, "-t", image, "-f", dockerfile, dirname(dockerfile)],
|
|
43
45
|
{ stdio: "inherit" },
|
|
44
46
|
);
|
|
45
47
|
if (r.status !== 0) throw new Error("docker build failed.");
|
|
@@ -16,13 +16,25 @@ export default {
|
|
|
16
16
|
},
|
|
17
17
|
showPresets: { control: "boolean" },
|
|
18
18
|
showFooter: { control: "boolean" },
|
|
19
|
+
min: { control: "date" },
|
|
20
|
+
max: { control: "date" },
|
|
21
|
+
maxDays: {
|
|
22
|
+
control: { type: "number", min: 0, step: 1 },
|
|
23
|
+
description: "선택 가능한 최대 일수. 0은 제한 없음.",
|
|
24
|
+
},
|
|
19
25
|
},
|
|
26
|
+
args: { maxDays: 0 },
|
|
20
27
|
} as Meta<typeof RBRangeDateInput>;
|
|
21
28
|
|
|
22
29
|
const Template: StoryTemplate = (args) => {
|
|
23
30
|
return (
|
|
24
31
|
<div style={{ height: 700 }}>
|
|
25
|
-
<RBRangeDateInput
|
|
32
|
+
<RBRangeDateInput
|
|
33
|
+
{...args}
|
|
34
|
+
min={args.min != null ? new Date(args.min) : undefined}
|
|
35
|
+
max={args.max != null ? new Date(args.max) : undefined}
|
|
36
|
+
maxDays={(args.maxDays ?? 0) > 0 ? args.maxDays : undefined}
|
|
37
|
+
/>
|
|
26
38
|
</div>
|
|
27
39
|
);
|
|
28
40
|
};
|