@rainbow-robotics/muscat-plugin-kit 1.3.3 → 1.3.6-dev.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/docker/Dockerfile.cpp +1 -1
- package/cli/docker/Dockerfile.dev +1 -1
- package/cli/src/build.mjs +17 -0
- package/cli/src/devBack.mjs +52 -6
- package/cli/src/manifest.mjs +11 -0
- package/cli/src/mcp.mjs +2 -1
- package/cli/src/new.mjs +7 -1
- package/cli/src/templates-embedded.mjs +2 -1
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +98 -1
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-DPI4Is-F.js → Color-YHDXOIA2-C5ttlJWI.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-Dow4_Rnl.js → Docs-B3AV-YvP.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CbCty_Qg.js → Docs-BN206lKl.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CQnf2HJu.js → Docs-BqH2prGO.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-TMe-1-I2.js → Docs-CCfiEyzU.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DDYksosp.js → Docs-CGN1_Ua8.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CKTHz6Db.js → Docs-DhncK7Aw.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-DSTzrCAK.js → DocsRenderer-CFRXHY34-D-w8Kq64.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-B3OxXBgc.js → NxAvatar.stories-oCcYtLwq.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-UTqX8ZkA.js → NxButton.stories-C5jUScIj.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-GoLEe-uJ.js → NxNumberInput.stories-gjsEulIN.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-Jj6dt6eG.js → NxPagination.stories-BK6uSWIa.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-tLN9u4nC.js → NxPointerHoldButton.stories-BDD5CvSZ.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-C-MK73xb.js → NxRangeInput.stories-C_nW26_x.js} +1 -1
- package/cli/storybook-dist/assets/{NxSelectBox.stories-CBcFcOIX.js → NxSelectBox.stories-DqLfKU9H.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-oNQkoW66.js → NxSlideRange.stories-CQjHA8yv.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-CqBkO5GC.js → NxSwitch.stories-BMHCiPoL.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-DtysSyl4.js → NxTextArea.stories-k7r1WVQh.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-BOLDbSgF.js → NxTextInput.stories-Cqfe1I32.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-D9-wu9LY.js → RBAccordion.stories-CJ3Ttuja.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BPfnIKm6.js → RBAccordionGroup.stories-BThv7tM0.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-CGn-c2ac.js → RBAlert.stories-CSMP8Vun.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-CBLmeoq1.js → RBBadge.stories-cBMA2R6F.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-oo47rbDh.js → RBBattery.stories-BKVOskqs.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-SiD2VfQ4.js → RBCard.stories-BTOfK0HC.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-sEEVrjSg.js → RBCheckBox.stories-DyE912wG.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-C5GQUjZf.js → RBCheckIconButton.stories-0sLFU0kM.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-DTaTqBvJ.js → RBCheckLabelButton.stories-c8VecClE.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-3AZr4kOz.js → RBChipTab.stories-CQ9Uav5_.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-hCJnBYQl.js → RBChipsBoxTab.stories-Ap4NlhAZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-BX4RJygu.js → RBColorIcons.stories-BPPXjif2.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-BidZ4H8f.js → RBCommand.stories-PDpYOKlm.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-D3Txw1kT.js → RBConfirm.stories-BEWvMAYP.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-CLxRaN6T.js → RBDateInput.stories-DLdyNnVF.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-CEhmUGp-.js → RBDialog.stories-HsawMNOb.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-peXP2KG9.js → RBDivider.stories-dlWwOxik.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-38iDf6uC.js → RBDrawer.stories-C87iqGk0.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-Dsi8tqSy.js → RBDropdownMenu.stories-B8EQhA0z.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-VMxWhzLP.js → RBEmptyView.stories-SzpIhNDw.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-BPLhceGO.js → RBIconButton.stories-BQ0nM1gb.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-DuYdO951.js → RBIcons.stories-C19g5M5z.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-CGNYhQyl.js → RBInlineLabelButton.stories-DbgI4-1P.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-CCmn-LMy.js → RBJoyStick.stories-smF-vpGL.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-IkKjthGk.js → RBLabelButton.stories-s53klwDv.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-eJFQOjPH.js → RBLight.stories-BnzkoR51.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-kg-xXGrW.js → RBMenubar.stories-CW014klu.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-Czr1r3gd.js → RBMultiRangeInput.stories-qa4tH_4Y.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-Dyu6Khnl.js → RBPagination.stories-YoQNUpQH.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-CAM6hpf2.js → RBPopover.stories-Bhpi0OaH.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-B4M-veaf.js → RBPrompt.stories-iQM5iAka.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-DeAOKvil.js → RBRadioBox.stories-BGyq4rzx.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-DjXwYXFy.js → RBRadioButton.stories-DF6BcUcJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBRangeDateInput.stories-9pzKhXGf.js → RBRangeDateInput.stories-COOQ_HNF.js} +1 -1
- package/cli/storybook-dist/assets/{RBResizable.stories-B4Hi4DUD.js → RBResizable.stories-DSVHvwjp.js} +1 -1
- package/cli/storybook-dist/assets/RBSelectBox.stories-BookV8Sm.js +74 -0
- package/cli/storybook-dist/assets/{RBSheet.stories-BwOCmaNN.js → RBSheet.stories-CAK9hb-p.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-BbGOIitU.js → RBSideNumberInput.stories-DUymnH-9.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-yIDdawYQ.js → RBSlider.stories-BwlDh9--.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-CIJvpbgK.js → RBSpinner.stories-VOlkuP6t.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-Bo8hrSiq.js → RBSwitch.stories-BiW2dQnT.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-Cq3N4Dgb.js → RBTab.stories-DFKVsy0C.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-DDeZf-Wb.js → RBTable.stories-BWG0ZSYI.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-BeKVMSo3.js → RBTableRow.stories-JVTD83Mh.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-DovB7PPB.js → RBTabs.stories-D-aN385u.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-D7H4RvP8.js → RBTextArea.stories-Dx5xkmZS.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-CmkNXeEk.js → RBTextField.stories-DmVciExd.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-hnxbQJsb.js → RBTextInput.stories-CCf0VGEv.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-BklGGkNh.js → RBToast.stories-DT0DGn05.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-C56AXyxq.js → RBToggle.stories-Ce_q3EtP.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-CpLPdAF0.js → RBToggleGroup.stories-CN40YHsR.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories-BdOnCwGk.js → RBTooltip.stories-L7btDEpA.js} +1 -1
- package/cli/storybook-dist/assets/{SideNumberInput-DLmM2_Dy.js → SideNumberInput-B5_xLK8z.js} +119 -119
- package/cli/storybook-dist/assets/{entry-preview-tBizwvut.js → entry-preview-D_nGAXUk.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-BVXraKDc.js → iframe-23VS_ZeI.js} +2 -2
- package/cli/storybook-dist/assets/{index-CI3FxSvF.js → index-Ckae_HWe.js} +3 -3
- package/cli/storybook-dist/assets/{index-C1OzuK6-.js → index-ClAc6FBW.js} +1 -1
- package/cli/storybook-dist/assets/{preview-CILRoSnB.js → preview-BVJwlGUF.js} +1 -1
- package/cli/storybook-dist/assets/{preview-BUOha-H6.js → preview-CD0LINug.js} +2 -2
- package/cli/storybook-dist/assets/preview-P8yWWsME.js +3 -0
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/index.json +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +27 -2
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/readme-cpp/README.md +26 -2
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +3 -4
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBSelectBox.stories-tVVv4c6z.js +0 -6
- package/cli/storybook-dist/assets/preview-DGIRF99V.js +0 -3
|
@@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
12
12
|
|
|
13
13
|
# Rainbow Robotics C++ SDK for find_package(rb_sdk).
|
|
14
14
|
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
15
|
-
ARG RB_SDK_VERSION=0.1.
|
|
15
|
+
ARG RB_SDK_VERSION=0.1.6
|
|
16
16
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
17
17
|
&& install -d /etc/apt/keyrings \
|
|
18
18
|
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
|
@@ -11,7 +11,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
11
11
|
|
|
12
12
|
# Rainbow Robotics C++ SDK for find_package(rb_sdk).
|
|
13
13
|
# Override with MUSCAT_PK_RB_SDK_VERSION=<ver> (+ MUSCAT_PK_REBUILD_IMAGE=1).
|
|
14
|
-
ARG RB_SDK_VERSION=0.1.
|
|
14
|
+
ARG RB_SDK_VERSION=0.1.6
|
|
15
15
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \
|
|
16
16
|
&& install -d /etc/apt/keyrings \
|
|
17
17
|
&& curl -fsSL https://rainbowrobotics.github.io/rb-sdk/rb-sdk-apt-repo.gpg.key -o /etc/apt/keyrings/rb-sdk.asc \
|
package/cli/src/build.mjs
CHANGED
|
@@ -74,6 +74,21 @@ function runViteBuild(pluginRoot, { mode = "production" } = {}) {
|
|
|
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)
|
|
76
76
|
// rclcpp: arch-specific C++ binaries → dist/ros2_ws/install-{arch}/ per arch
|
|
77
|
+
// Cross-arch C++ builds run inside an emulated container. Docker Desktop ships the
|
|
78
|
+
// emulation; native Linux needs binfmt/QEMU — check up front so the error says so.
|
|
79
|
+
function assertCanBuildArch(arch, dockerPlatform) {
|
|
80
|
+
const info = spawnSync("docker", ["info", "--format", "{{.Architecture}}"], { encoding: "utf8" });
|
|
81
|
+
const host = { x86_64: "amd64", aarch64: "arm64", arm64: "arm64" }[(info.stdout || "").trim()] ?? "";
|
|
82
|
+
if (arch === host) return;
|
|
83
|
+
const r = spawnSync("docker", ["run", "--rm", "--platform", dockerPlatform, "ubuntu:24.04", "true"], { stdio: "ignore" });
|
|
84
|
+
if (r.status === 0) return;
|
|
85
|
+
throw new Error(
|
|
86
|
+
`can't build ${arch} on this ${host || "machine"}: Docker has no ${arch} emulation.\n` +
|
|
87
|
+
` enable it once: docker run --privileged --rm tonistiigi/binfmt --install ${arch}\n` +
|
|
88
|
+
` or build only this machine's arch: muscat-pk build --arch ${host || "<arch>"}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
78
93
|
if (manifestRuntime(root) !== "ros2") return;
|
|
79
94
|
const { assertDockerReady, ensureImage, colconBuild, ROS2_DEV_IMAGE, ARCH_DOCKER_PLATFORM } = await import("./ros2.mjs");
|
|
@@ -85,6 +100,7 @@ export async function exportRos2Build(root, archs = ["amd64", "arm64"]) {
|
|
|
85
100
|
for (const arch of archs) {
|
|
86
101
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
87
102
|
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
103
|
+
assertCanBuildArch(arch, dockerPlatform);
|
|
88
104
|
await ensureImage(`${ROS2_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
89
105
|
colconBuild(root, { symlink: false, arch });
|
|
90
106
|
log.ok(`dist/ros2_ws/install-${arch}/ ready`);
|
|
@@ -111,6 +127,7 @@ export async function exportCppBuild(root, distDir, archs = ["amd64", "arm64"])
|
|
|
111
127
|
for (const arch of archs) {
|
|
112
128
|
const dockerPlatform = ARCH_DOCKER_PLATFORM[arch];
|
|
113
129
|
if (!dockerPlatform) throw new Error(`unknown arch '${arch}'`);
|
|
130
|
+
assertCanBuildArch(arch, dockerPlatform);
|
|
114
131
|
await ensureImage(`${CPP_DEV_IMAGE}-${arch}`, { platform: dockerPlatform });
|
|
115
132
|
cmakeBuild(root, { buildType: "Release", arch });
|
|
116
133
|
const built = resolve(root, `cpp/build-${arch}`, entry);
|
package/cli/src/devBack.mjs
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
// ros2 : Jazzy + Python 3.12 container, colcon build, run the node, restart
|
|
9
9
|
// on source changes (rclcpp rebuilds on change).
|
|
10
10
|
import { spawn, spawnSync } from "node:child_process";
|
|
11
|
-
import { existsSync, watch } from "node:fs";
|
|
12
|
-
import {
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs";
|
|
12
|
+
import { isIP } from "node:net";
|
|
13
|
+
import { dirname, resolve } from "node:path";
|
|
13
14
|
import { ensureDocker, ensureIdeVenv, findPluginRoot, log, promptRobotConnection, realtimeFlags } from "./util.mjs";
|
|
14
15
|
import { ensureRemoteZenohEnv, usesHostNetwork } from "./zenohDev.mjs";
|
|
15
|
-
import { assertManifestPorts, assertManifestVariables, manifestRuntime, readManifest, validateManifest } from "./manifest.mjs";
|
|
16
|
+
import { PLUGIN_ID_RE, assertManifestPorts, assertManifestVariables, manifestRuntime, readManifest, rosNamespace, validateManifest } from "./manifest.mjs";
|
|
16
17
|
import {
|
|
17
18
|
formatPortRanges,
|
|
18
19
|
portEnvFlags,
|
|
@@ -69,6 +70,43 @@ function backendNetwork(portRanges) {
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// rb.rosPeers in dev: answer muscat/plugin/<id>/ros_peers/{get,set} on the gateway the way
|
|
74
|
+
// Muscat (common) does on the robot — keep the list in .muscat-dev/ and restart the node
|
|
75
|
+
// with ROS_STATIC_PEERS.
|
|
76
|
+
const ROS_PEERS_FILE = ".muscat-dev/ros-peers.json";
|
|
77
|
+
|
|
78
|
+
function readRosPeers(root) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(readFileSync(resolve(root, ROS_PEERS_FILE), "utf8"));
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function serveRosPeers(root, pluginId, restart) {
|
|
87
|
+
if (!pluginId) return;
|
|
88
|
+
const { RbWs } = await import("@rainbow-robotics/plugin-ui/ws");
|
|
89
|
+
const ws = await Promise.race([
|
|
90
|
+
RbWs.connect("http://127.0.0.1:10000", pluginId).catch(() => null),
|
|
91
|
+
new Promise((r) => setTimeout(() => r(null), 5000)),
|
|
92
|
+
]);
|
|
93
|
+
if (!ws) return log.warn("rb.rosPeers unavailable in dev: plugin gateway not reachable on :10000");
|
|
94
|
+
const base = `/muscat/plugin/${pluginId}/ros_peers`;
|
|
95
|
+
await ws.serve(`${base}/get`, () => ({ peers: readRosPeers(root) }));
|
|
96
|
+
await ws.serve(`${base}/set`, (payload) => {
|
|
97
|
+
const peers = payload?.peers;
|
|
98
|
+
if (!Array.isArray(peers) || !peers.every((p) => typeof p === "string" && isIP(p))) {
|
|
99
|
+
return { ok: false, error: "peers must be a list of IP addresses" };
|
|
100
|
+
}
|
|
101
|
+
const file = resolve(root, ROS_PEERS_FILE);
|
|
102
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
103
|
+
writeFileSync(file, JSON.stringify(peers) + "\n");
|
|
104
|
+
log.step(`ROS_STATIC_PEERS → ${peers.join(";") || "(none)"} — restarting node`);
|
|
105
|
+
restart();
|
|
106
|
+
return { ok: true, peers };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
72
110
|
// Our own MediaMTX, only when `dev` found its ports free (a local Muscat brings its own).
|
|
73
111
|
function startMediaServer(container) {
|
|
74
112
|
if (process.env.MUSCAT_PK_MEDIA !== "1") return;
|
|
@@ -435,6 +473,8 @@ async function runRos2(root, portRanges) {
|
|
|
435
473
|
// Same rationale as runPython(): no -t on docker exec below means
|
|
436
474
|
// rclpy's Python process stdout is block-buffered, not line-buffered.
|
|
437
475
|
"-e", "PYTHONUNBUFFERED=1",
|
|
476
|
+
// Same as the robot: ROS discovery stays on this host (allow a PC with ROS_STATIC_PEERS).
|
|
477
|
+
"-e", "ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST",
|
|
438
478
|
...net.env,
|
|
439
479
|
...(pluginId ? ["-e", `RB_PLUGIN_ID=${pluginId}`] : []),
|
|
440
480
|
...(robotIp ? ["-e", `MUSCAT_ROBOT_HOST=${robotIp}`] : []),
|
|
@@ -463,18 +503,24 @@ async function runRos2(root, portRanges) {
|
|
|
463
503
|
let child = null;
|
|
464
504
|
const startNode = () => {
|
|
465
505
|
if (child) child.kill();
|
|
466
|
-
//
|
|
506
|
+
// killing the `docker exec` client doesn't stop the node inside the container
|
|
507
|
+
spawnSync("docker", ["exec", container, "pkill", "-f", `${pkg}_node|${manifest.runtime?.launch || pkg}`], { stdio: "ignore" });
|
|
508
|
+
const peers = readRosPeers(root);
|
|
509
|
+
// Same command the robot runs: rclpy execs the console script, rclcpp uses `ros2 run`;
|
|
510
|
+
// relative topics land under the plugin's namespace (rosNamespace).
|
|
467
511
|
const launch = manifest.runtime?.launch || `${pkg}_node`;
|
|
468
|
-
const
|
|
512
|
+
const nsArgs = PLUGIN_ID_RE.test(pluginId) ? ` --ros-args -r __ns:=${rosNamespace(pluginId)}` : "";
|
|
513
|
+
const run = (isRclcpp ? `ros2 run ${pkg} ${launch}` : `exec ${launch}`) + nsArgs;
|
|
469
514
|
log.step(run);
|
|
470
515
|
child = spawn(
|
|
471
516
|
"docker",
|
|
472
|
-
["exec", ...envFlags, container, "bash", "-lc",
|
|
517
|
+
["exec", ...envFlags, ...(peers.length ? ["-e", `ROS_STATIC_PEERS=${peers.join(";")}`] : []), container, "bash", "-lc",
|
|
473
518
|
`cd ros2_ws && source install/setup.bash && ${run}`],
|
|
474
519
|
{ cwd: root, stdio: "inherit" },
|
|
475
520
|
);
|
|
476
521
|
};
|
|
477
522
|
startNode();
|
|
523
|
+
serveRosPeers(root, pluginId, startNode);
|
|
478
524
|
|
|
479
525
|
// restart node on source change. rclpy reflects .py edits live (symlink-install);
|
|
480
526
|
// rclcpp (C++) and package-metadata changes require a colcon rebuild.
|
package/cli/src/manifest.mjs
CHANGED
|
@@ -11,6 +11,15 @@ export const CONTRIBUTION_POINTS = [
|
|
|
11
11
|
"program.panel",
|
|
12
12
|
];
|
|
13
13
|
const ICON_SIZES = ["16", "48", "128"];
|
|
14
|
+
// Reverse-domain id: "."-separated parts of lowercase letters/digits joined by single "-",
|
|
15
|
+
// each part starting with a letter. Keeps the ROS namespace derived from it (rosNamespace)
|
|
16
|
+
// valid and collision-free (no "_" in ids, so "." and "-" map to distinct characters).
|
|
17
|
+
export const PLUGIN_ID_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*(\.[a-z][a-z0-9]*(-[a-z0-9]+)*)*$/;
|
|
18
|
+
|
|
19
|
+
/** ROS namespace for a plugin id: "." → "/", "-" → "_" (com.example.my-plugin → /com/example/my_plugin). */
|
|
20
|
+
export function rosNamespace(id) {
|
|
21
|
+
return "/" + id.replace(/\./g, "/").replace(/-/g, "_");
|
|
22
|
+
}
|
|
14
23
|
const RUNTIME_TYPES = ["none", "python", "cpp", "ros2"];
|
|
15
24
|
const ROS_CLIENTS = ["rclpy", "rclcpp"];
|
|
16
25
|
const I18N_LANGS = ["ko", "en", "ja", "zh", "vi", "th", "es"];
|
|
@@ -192,6 +201,8 @@ export function validateManifest(manifest) {
|
|
|
192
201
|
if (typeof manifest[key] !== "string" || manifest[key].length === 0)
|
|
193
202
|
errors.push(`"${key}" is required`);
|
|
194
203
|
}
|
|
204
|
+
if (typeof manifest.id === "string" && manifest.id && !PLUGIN_ID_RE.test(manifest.id))
|
|
205
|
+
errors.push(`"id" must be "."-separated parts of lowercase letters/digits and single "-", each starting with a letter (e.g. com.example.my-plugin)`);
|
|
195
206
|
if (typeof manifest.author !== "string" || manifest.author.length === 0)
|
|
196
207
|
errors.push(`"author" is required`);
|
|
197
208
|
if (typeof manifest.email !== "string" || manifest.email.length === 0)
|
package/cli/src/mcp.mjs
CHANGED
|
@@ -683,7 +683,8 @@ export async function runMcp() {
|
|
|
683
683
|
"host), manifest.json (including the \"variables\" declaration for the pyfm variable picker — " +
|
|
684
684
|
"names require the RBP_ prefix), program variables & executor state (ctx.get_variable/" +
|
|
685
685
|
"set_variable/executor_state/check_stop), video streaming (MediaMTX: backend publishes RTSP to " +
|
|
686
|
-
"127.0.0.1:8554, UI plays WebRTC from :8889),
|
|
686
|
+
"127.0.0.1:8554, UI plays WebRTC from :8889), ROS 2 topics (plugin namespace, rb.rosPeers to allow " +
|
|
687
|
+
"an external PC), contribution points, and backend runtimes " +
|
|
687
688
|
"(Python/C++/ROS2). Covers BOTH backends: the Python README (py/main.py, rainbow.rb_sdk) and the " +
|
|
688
689
|
"C++ README (cpp/main.cpp or an rclcpp node: rb_plugin::RBPluginCall serve/subscribe/publish/" +
|
|
689
690
|
"router(), rb_plugin::PluginProgramContext, RBManipulateSDK, CMake rb_sdk::rb_sdk). Pass lang " +
|
package/cli/src/new.mjs
CHANGED
|
@@ -106,6 +106,12 @@ export function applyTokens(dir, tokens) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// Derive naming tokens from a plugin directory name.
|
|
109
|
+
// One id part (see PLUGIN_ID_RE): lowercase, single "-", starts with a letter.
|
|
110
|
+
function idPart(name) {
|
|
111
|
+
const part = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
112
|
+
return /^[a-z]/.test(part) ? part : part ? `plugin-${part}` : "plugin";
|
|
113
|
+
}
|
|
114
|
+
|
|
109
115
|
export function deriveNames(target) {
|
|
110
116
|
const base = basename(target);
|
|
111
117
|
const slug = base.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
@@ -115,7 +121,7 @@ export function deriveNames(target) {
|
|
|
115
121
|
slug,
|
|
116
122
|
pluginName,
|
|
117
123
|
pluginClass: pluginName.replace(/\s+/g, ""),
|
|
118
|
-
pluginId: `com.example.${base
|
|
124
|
+
pluginId: `com.example.${idPart(base)}`,
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
127
|
|