@rainbow-robotics/muscat-plugin-kit 1.10.3-dev.3 → 1.10.3-dev.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +186 -60
- package/README.md +186 -60
- package/cli/README.md +3 -1
- package/cli/bin/muscat-pk.mjs +5 -1
- package/cli/src/completion.mjs +2 -0
- package/cli/src/dev.mjs +19 -2
- package/cli/src/devBack.mjs +1 -1
- package/cli/src/devUi.mjs +2 -1
- package/cli/src/muscatLocal.mjs +55 -19
- package/cli/src/sim.mjs +163 -0
- package/cli/src/util.mjs +2 -0
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-Cp8LHsF-.js → Color-YHDXOIA2-DSB_yvex.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-Kg1Im4XT.js → Docs-C_90bDRy.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-UJwiVwO2.js → Docs-CbW9HZd4.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-LeW_BIsw.js → Docs-CoskoRMT.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-B2ox3o_G.js → Docs-Cy9XFeLJ.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-By11aG_M.js → Docs-De1oc64k.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-ik2AhpV0.js → Docs-klD3F_Ai.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-DPFneFUi.js → DocsRenderer-CFRXHY34-Bkm4uV_D.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-CwnK8AUF.js → NxAvatar.stories-Ctf5G2FQ.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-ueYdosfQ.js → NxButton.stories-DeLObVSj.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-M5ZCLbrK.js → NxNumberInput.stories-LqoY0ed1.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-bxxUnHEL.js → NxPagination.stories-DI6bXuJM.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BHxB2kr5.js → NxPointerHoldButton.stories-3HSV1XmN.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-BKfr-fp4.js → NxRangeInput.stories-1pjgQVaG.js} +1 -1
- package/cli/storybook-dist/assets/{NxSelectBox.stories-CG8FuTBq.js → NxSelectBox.stories-bcQPZEuF.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-BRTFtMTL.js → NxSlideRange.stories-BosX4axk.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-Bwuyzn-R.js → NxSwitch.stories-DxV6dS33.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-COq2sGqu.js → NxTextArea.stories-CO545-LP.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-CjsBLz05.js → NxTextInput.stories-BP0npai1.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-BARwuZ0Q.js → RBAccordion.stories-fVR4Va2a.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-COrPxETW.js → RBAccordionGroup.stories-Du5bsckD.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-BQrkjZPt.js → RBAlert.stories-DNkMXUu6.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-BTsLL-lK.js → RBBadge.stories-C44pXt8D.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-DAeQdJbc.js → RBBattery.stories-DE2JHvfL.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-HyhOE80R.js → RBCard.stories-CuIda7NI.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-KFk0WqX_.js → RBCheckBox.stories-D1qM1ayr.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-CXbbktF5.js → RBCheckIconButton.stories-e4gkliBO.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-DWfYz_9Z.js → RBCheckLabelButton.stories-CDzCtsMI.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-JPEgVFAS.js → RBChipTab.stories-DBrc_3Mp.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-CTPuWXrQ.js → RBChipsBoxTab.stories-hlrsHjKY.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-DiFRZ5jk.js → RBColorIcons.stories-BHUvpkdu.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-Pf1HAVIR.js → RBCommand.stories-azAF41NP.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-D1cEPUXk.js → RBConfirm.stories-BMNjwbmT.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-RPYpCjig.js → RBDateInput.stories-D0VezDzv.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-DJH-0XSR.js → RBDialog.stories-AOJ975Fy.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-uEj1vOgK.js → RBDivider.stories-NSvnj43Z.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-BiMzElb2.js → RBDrawer.stories-Du0oTvD1.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-Da_TBkla.js → RBDropdownMenu.stories-Hsxve4PW.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-BHeKGfPS.js → RBEmptyView.stories-BlwHx3zp.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-NEsRFoYp.js → RBIconButton.stories-DAPDY63x.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-CcZ-TTLS.js → RBIcons.stories-W5ZUV2L1.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-B0wpdQyR.js → RBInlineLabelButton.stories-C99j0Y3g.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-DEgBou3K.js → RBJoyStick.stories-Du0H1iAS.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-DT8drIsF.js → RBLabelButton.stories-BlvHaU_Z.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-Dh-aoeyG.js → RBLight.stories-BY6DKrre.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-D-H7UqVh.js → RBMenubar.stories-D2PEGpM9.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-hlclv6jI.js → RBMultiRangeInput.stories-Bm2qSmjq.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-DWCeoZ_S.js → RBPagination.stories-DDleJa5N.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-C514M09_.js → RBPopover.stories-27YxPSFG.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-BSItvfiT.js → RBPrompt.stories-BFaIOctr.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-BBb4YbFU.js → RBRadioBox.stories-BroDar0t.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-CXyOGJCv.js → RBRadioButton.stories-M3O-wX7V.js} +1 -1
- package/cli/storybook-dist/assets/{RBRangeDateInput.stories-qvhubu0e.js → RBRangeDateInput.stories-CWlM_D5X.js} +1 -1
- package/cli/storybook-dist/assets/{RBResizable.stories-CZKeyGjH.js → RBResizable.stories-ui8X1qnc.js} +1 -1
- package/cli/storybook-dist/assets/{RBSelectBox.stories-CRKP3ybf.js → RBSelectBox.stories-C8T6cfA0.js} +1 -1
- package/cli/storybook-dist/assets/{RBSheet.stories-sCM3ZJUa.js → RBSheet.stories-BCOo_Zcc.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-Bb7gw8RF.js → RBSideNumberInput.stories-CU2_MVlJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-23OAQU5z.js → RBSlider.stories-DgOp1TFc.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-2xcQdcmt.js → RBSpinner.stories-qLScY3zZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-BACUaJm_.js → RBSwitch.stories-SXSY1QMb.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-CDHE3sVs.js → RBTab.stories-BzXXTsXP.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-C0Cfhfl7.js → RBTable.stories-DHTeRNVk.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-BF8kIkpp.js → RBTableRow.stories-DRNRsd15.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-BdJWRq1o.js → RBTabs.stories-DP0noBLp.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-CGy2xeYH.js → RBTextArea.stories-TkaejfxJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-IfUOzxCM.js → RBTextField.stories-B6VpCGyt.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-VjXNFo4R.js → RBTextInput.stories-D3hZ08e-.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-BzcobblV.js → RBToast.stories-BQ2wS2U0.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-DuLE5mKv.js → RBToggle.stories-GVOjGqo3.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-CuR0N0GJ.js → RBToggleGroup.stories-pzx0btOD.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories--aAgNC8_.js → RBTooltip.stories-jufB6fa1.js} +1 -1
- package/cli/storybook-dist/assets/{SideNumberInput-DyWQxYUj.js → SideNumberInput-BYlmNHEw.js} +1 -1
- package/cli/storybook-dist/assets/{entry-preview-BTw3Jh8E.js → entry-preview-DY0ZrYNE.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-BqPQIkPQ.js → iframe-LwKb1w63.js} +2 -2
- package/cli/storybook-dist/assets/{index-k_VkBflK.js → index-BsAhjDKO.js} +3 -3
- package/cli/storybook-dist/assets/{preview-Cog7BnbD.js → preview-BJ0FvQZh.js} +2 -2
- package/cli/storybook-dist/assets/{preview-C3ctYQQj.js → preview-DiY2_30w.js} +2 -2
- package/cli/storybook-dist/assets/{preview-CigNHo-I.css → preview-kTZPgP6D.css} +1 -1
- package/cli/storybook-dist/assets/{react-resizable-panels-Dp9zi-o3.css → react-resizable-panels-HsFbxPbn.css} +1 -1
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/package.json +3 -3
- /package/cli/storybook-dist/assets/{react-resizable-panels.browser-8hC8dr-6.js → react-resizable-panels.browser-DuxpZLkF.js} +0 -0
package/README.md
CHANGED
|
@@ -111,6 +111,7 @@ Install the ZIP from the robot's Muscat plugin page.
|
|
|
111
111
|
| `muscat-pk build` | Build, lock Python deps, vendor wheels, ZIP |
|
|
112
112
|
| `muscat-pk update` | Update the kit, `plugin-ui` and `preview-host` |
|
|
113
113
|
| `muscat-pk sdk <list\|update [version]>` | Manage the `rainbow-rb-sdk` version |
|
|
114
|
+
| `muscat-pk sim list` | List the robot simulators for `dev --sim` |
|
|
114
115
|
| `muscat-pk storybook` | Browse the bundled rb-components Storybook |
|
|
115
116
|
| `muscat-pk mcp` | MCP server for AI coding agents |
|
|
116
117
|
| `muscat-pk uninstall` | Remove the CLI |
|
|
@@ -134,9 +135,15 @@ muscat-pk new [dir] [--type python|cpp|ros2|ros2-cpp] [--pm npm|yarn|pnpm|bun]
|
|
|
134
135
|
| `--pm` | front-end package manager; defaults to `yarn` inside a Yarn workspace, else `npm` |
|
|
135
136
|
| `--author` / `--email` / `--description` | manifest fields (email is required by the manifest) |
|
|
136
137
|
| `--yes` | no prompts; unspecified values use defaults |
|
|
137
|
-
| `--no-install` | skip
|
|
138
|
+
| `--no-install` | skip `<pm> install`, the IDE `.venv` and the C++ dev image |
|
|
139
|
+
| `--help` | print the usage |
|
|
138
140
|
|
|
139
|
-
|
|
141
|
+
The plugin id is `com.example.<dir>` (lowercased; edit it in `manifest.json`). `new` also writes `.gitignore`, `AGENTS.md`, `CLAUDE.md` and `.mcp.json` (see [11](#11-ai-coding-agent-setup-muscat-pk-mcp)), and runs `git init` with an initial commit (delete `.git` if you don't want it).
|
|
142
|
+
|
|
143
|
+
Unless `--no-install`:
|
|
144
|
+
|
|
145
|
+
- python / ros2 (rclpy): an IDE-only `.venv` (see [9.6](#96-ide-support-python)).
|
|
146
|
+
- cpp / ros2-cpp: builds the C++ dev image so the editor finds the SDK headers (skipped with a warning when Docker is missing).
|
|
140
147
|
|
|
141
148
|
### 3.2 `type change`
|
|
142
149
|
|
|
@@ -144,25 +151,67 @@ Python projects also get an IDE-only `.venv` (see [9.6](#96-ide-support-python))
|
|
|
144
151
|
muscat-pk type change [--to python|cpp|ros2|ros2-cpp] [--readme]
|
|
145
152
|
```
|
|
146
153
|
|
|
147
|
-
|
|
154
|
+
| Option | Meaning |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `--to` | new type; a numbered prompt when omitted |
|
|
157
|
+
| `--readme` | replace `README.md` with the new type's without asking (old file → `README.md.bak`) |
|
|
158
|
+
|
|
159
|
+
Rewrites `manifest.json` → `runtime` and adds the new backend's files (`cpp/` or `ros2_ws/`) unless that directory already exists. Files of the old type (`cpp/`, `ros2_ws/`, …) are left in place. When switching between a Python and a C++ backend it offers to replace the README (`--readme` answers yes). Switching to a C++ type prepares the C++ dev image like `new`.
|
|
148
160
|
|
|
149
161
|
### 3.3 `dev`, `dev:ui`, `dev:back`
|
|
150
162
|
|
|
151
163
|
```bash
|
|
152
|
-
muscat-pk dev [--robot-ip <ip>] [--keep-id]
|
|
153
|
-
|
|
154
|
-
muscat-pk dev:
|
|
164
|
+
muscat-pk dev [--robot-ip <ip>] [--robot-token <token>] [--keep-id] [--sim <model>]
|
|
165
|
+
[--port <n>] [--no-open]
|
|
166
|
+
muscat-pk dev:ui [--robot-ip <ip>] [--port <n>] [--keep-id] [--no-open]
|
|
167
|
+
muscat-pk dev:back [--robot-ip <ip>] [--robot-token <token>] [--keep-id]
|
|
155
168
|
```
|
|
156
169
|
|
|
157
170
|
| Command | What runs |
|
|
158
171
|
|---|---|
|
|
159
|
-
| `dev` | a local Muscat runtime when no robot is given, then `dev:ui` and `dev:back` |
|
|
160
|
-
| `dev:ui` | the plugin's Vite dev server + preview-host at `http://localhost:4180
|
|
172
|
+
| `dev` | a local Muscat runtime when no robot is given (or the robot runs no Muscat), then `dev:ui` and `dev:back` |
|
|
173
|
+
| `dev:ui` | the plugin's Vite dev server + preview-host at `http://localhost:4180` (next free port), opened in the browser |
|
|
161
174
|
| `dev:back` | the backend in a Docker container matching the robot. Python restarts on `py/**/*.py` changes (watchfiles) |
|
|
162
175
|
|
|
163
|
-
|
|
176
|
+
| Option | Meaning |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `--robot-ip` | robot to develop against; else `MUSCAT_ROBOT_HOST`, else a prompt (blank = no robot) |
|
|
179
|
+
| `--robot-token` | service token for a robot with the login policy on; else `MUSCAT_ROBOT_TOKEN`, else a prompt (blank = none) |
|
|
180
|
+
| `--keep-id` | run under the manifest id instead of a dev id (see [9.3](#93-several-developers-on-one-robot)) |
|
|
181
|
+
| `--sim` | `dev` only, no robot: start a robot simulator (below). Skips the robot IP prompt |
|
|
182
|
+
| `--port` | preview-host port (default `4180`; the next free one when taken) |
|
|
183
|
+
| `--no-open` | don't open the browser (it is not opened in CI or without a TTY either) |
|
|
184
|
+
|
|
185
|
+
- One dev environment per PC: they share the backend container, the local Muscat and its ports. A second `dev` / `dev:ui` / `dev:back` asks to stop the running one (`Stop it and start this one? [y/N]`).
|
|
164
186
|
- Against a remote robot the plugin runs under a dev id (see [9.3](#93-several-developers-on-one-robot)); `--keep-id` uses the manifest id.
|
|
165
|
-
-
|
|
187
|
+
- Before start, `dev` and `dev:back` check `manifest.json` → `variables` and `ports` (errors stop it); `dev:back` also prints other manifest errors as warnings. The full check runs on `build`. Undeclared listening ports stop the backend (see [5.6](#56-ports)).
|
|
188
|
+
- Against a robot running Muscat, the UI is that robot's Muscat UI and the backend uses its gateway. With a local `muscat-dev` container (or `--sim`) it is the local real Muscat UI; otherwise the mock preview.
|
|
189
|
+
- `dev` / `dev:ui` print a hint when a newer kit is published (`MUSCAT_PK_NO_UPDATE_CHECK=1` skips it).
|
|
190
|
+
- Stopping `dev` (Ctrl+C) removes the backend container, and its own local Muscat and simulators.
|
|
191
|
+
|
|
192
|
+
#### Developing without a robot (`--sim`)
|
|
193
|
+
|
|
194
|
+
With no robot IP, `dev --sim <model>` also starts a robot simulator next to the local Muscat. The plugin SDK's robot calls then reach a real coreSW.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
muscat-pk sim list # "<name>\t<what runs>" per line
|
|
198
|
+
muscat-pk dev --sim 500920
|
|
199
|
+
muscat-pk dev --sim rby1-m_v1.2
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
| `--sim` | Muscat services | Simulator |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `500880`, `500920` | common, log, manipulate | one `rainbowroboticsofficial/cobot-sim` |
|
|
205
|
+
| `RBY2` | common, log, manipulate | three cobot-sim (C500880, C501880, C502880) |
|
|
206
|
+
| `rby1-a`, `rby1-m`, `rby1-ub` (or a version, e.g. `rby1-m_v1.2`) | common, log, rby1 | `rainbowroboticsofficial/rby1-sim` + Xvfb, built locally |
|
|
207
|
+
|
|
208
|
+
- The simulated model is registered as the local Muscat's robot model (`500880` → C500880, `500920` → C500920, `rby1-m` → m). The UI is then the real Muscat UI (dashboard, components, Move, Log, Settings) instead of the mock preview.
|
|
209
|
+
- A simulator runs only with `--sim`. Any other value fails with `--sim takes one of: 500880, 500920, RBY2, rby1-a, rby1-m, rby1-ub (rby1 also takes rby1-m_v1.2)`. A model missing from the manifest's `targetModels` only gets a warning.
|
|
210
|
+
- Simulator images use the latest Docker Hub tag (an rby1 version picks the matching tag). Offline, an already pulled image is used.
|
|
211
|
+
- rby1-sim keeps rendering, so it uses about one CPU core. The cap is `MUSCAT_PK_RBY1_CPUS` (default 1.5).
|
|
212
|
+
- No simulator is attached while another Muscat such as `muscat-dev` is running.
|
|
213
|
+
- There is no amr simulator yet.
|
|
214
|
+
- The local Muscat, the simulators and the backend all run with `--ipc host` like on the robot, so zenoh SHM and `shm_open` reach each other.
|
|
166
215
|
|
|
167
216
|
### 3.4 `build`
|
|
168
217
|
|
|
@@ -172,18 +221,23 @@ muscat-pk build [--arch amd64|arm64|both]
|
|
|
172
221
|
|
|
173
222
|
Steps:
|
|
174
223
|
|
|
175
|
-
1. Asks for a new `manifest.json` version (blank keeps the current one).
|
|
176
|
-
2.
|
|
177
|
-
3.
|
|
178
|
-
4.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
- ros2:
|
|
182
|
-
|
|
224
|
+
1. Asks for a new `manifest.json` version (blank keeps the current one; TTY only).
|
|
225
|
+
2. Asks for the target arch (unless `--arch`) and deletes an existing ZIP of that version, so a failed build leaves no stale ZIP.
|
|
226
|
+
3. Validates the manifest (all of section 5) and checks that each `contributes[].ui` has `ui/<entry>.tsx` (or `.ts` / `.jsx` / `.js`).
|
|
227
|
+
4. `vite build` of every `contributes[].ui` entry → `dist/<entry>.js`. A plugin without `contributes` builds no UI. Without Node.js 20+ (binary CLI) it asks `Build the plugin without its UI? [y/N]`; yes drops `contributes` from `dist/manifest.json`.
|
|
228
|
+
5. Copies `manifest.json`, `assets/`, `README.md`, `README.<lang>.md`, `CHANGELOG.md`, `py/` and `requirements.txt` into `dist/`.
|
|
229
|
+
6. Backend:
|
|
230
|
+
- python / ros2 (rclpy): resolves `requirements.txt` into `dist/requirements.lock` (`uv pip compile`, Python 3.12; pre-releases allowed when a pinned line is one) and downloads binary wheels (cp312, `--only-binary=:all:`) into `dist/wheels/` per arch. A package with no matching wheel stops the build.
|
|
231
|
+
- cpp: CMake Release build in Docker → `dist/<arch>/<entry>` plus `dist/<arch>/lib/` (`librb_sdk.so`). The CMake target name must equal `runtime.entry`.
|
|
232
|
+
- ros2: colcon build in Docker → `dist/ros2_ws/install/` (rclpy, built once) or `dist/ros2_ws/install-<arch>/` (rclcpp, per arch).
|
|
233
|
+
7. Writes `installedSize` (bytes of `dist/` without `wheels/`) into `manifest.json` and `dist/manifest.json`.
|
|
234
|
+
8. Packs `dist/` into `<project dir>-<version>.zip` in the project root.
|
|
183
235
|
|
|
184
236
|
| Option | Meaning |
|
|
185
237
|
|---|---|
|
|
186
|
-
| `--arch` |
|
|
238
|
+
| `--arch` | `amd64` (`x86_64`), `arm64` (`aarch64`) or `both`; asked in a TTY (default both), `both` otherwise |
|
|
239
|
+
|
|
240
|
+
Every runtime except `none` needs Docker. Building the other arch needs Docker emulation; on native Linux enable it once with `docker run --privileged --rm tonistiigi/binfmt --install <arch>`.
|
|
187
241
|
|
|
188
242
|
### 3.5 `update`
|
|
189
243
|
|
|
@@ -191,25 +245,41 @@ Steps:
|
|
|
191
245
|
muscat-pk update # this project only (when it has a local install)
|
|
192
246
|
muscat-pk update --global # this project + the global CLI
|
|
193
247
|
muscat-pk update dev # follow the dev channel
|
|
248
|
+
muscat-pk update 1.2.3 # a specific version (also --tag <tag> / --version <v>)
|
|
194
249
|
```
|
|
195
250
|
|
|
251
|
+
| Option | Meaning |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `[tag]` | npm dist-tag or version (`latest`, `dev`, `1.2.3`). Omitted: `latest`; on a dev build it asks to stay on dev (default) or switch to stable |
|
|
254
|
+
| `--global` | also update the global CLI when run from a project's local install |
|
|
255
|
+
|
|
196
256
|
| Where you run it | What it updates |
|
|
197
257
|
|---|---|
|
|
198
|
-
| Inside a project with a local install | that project's `muscat-plugin-kit` + `plugin-ui` |
|
|
258
|
+
| Inside a project with a local install | that project's `muscat-plugin-kit` + `plugin-ui` (+ `preview-host` when listed in `package.json`) |
|
|
199
259
|
| Same, with `--global` | the above + the global CLI |
|
|
200
|
-
| Anywhere else | the global CLI (and local deps if inside a project
|
|
260
|
+
| Anywhere else | the global CLI (npm `-g` or the S3 binary) and local deps if inside a project |
|
|
261
|
+
|
|
262
|
+
- When the project's local install is older than the CLI you run, that CLI updates the project instead (project only).
|
|
263
|
+
- Packages stay in their current `dependencies` / `devDependencies` section; the package manager is taken from `packageManager` or the lock file.
|
|
264
|
+
- An old project layout is migrated after a `[y/N]` prompt: `src/` → `ui/` (+ `vite.config.ts`, `tsconfig.json`), `main.py` → `py/main.py`, `backend/` → `py/`.
|
|
265
|
+
- Missing `.mcp.json`, `AGENTS.md`, `CLAUDE.md` are added; existing ones are kept.
|
|
266
|
+
- On Windows the new binary replaces the old one on the next run.
|
|
201
267
|
|
|
202
|
-
After updating it lists
|
|
268
|
+
After updating it lists code that needs changes for the new `plugin-ui` (``This project needs changes for the updated plugin-ui. Fix these before `muscat-pk dev` / `build`:``). Fix them (or run `check_project` through `muscat-pk mcp`).
|
|
203
269
|
|
|
204
270
|
### 3.6 `sdk`
|
|
205
271
|
|
|
206
272
|
```bash
|
|
207
|
-
muscat-pk sdk list # published
|
|
208
|
-
muscat-pk sdk update [version] # latest when omitted
|
|
273
|
+
muscat-pk sdk list # 12 newest published versions
|
|
274
|
+
muscat-pk sdk update [version] # latest stable when omitted
|
|
209
275
|
```
|
|
210
276
|
|
|
211
|
-
|
|
212
|
-
|
|
277
|
+
| Backend | `list` source | `update` rewrites |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| python, ros2 (rclpy) | PyPI `rainbow-rb-sdk` | every `rainbow-rb-*==` pin in `requirements.txt` together (they pin each other exactly); adds `rainbow-rb-sdk==<ver>` when missing. An existing `.venv` is refreshed; the dev container installs it on the next `dev` / `dev:back` |
|
|
280
|
+
| C++ (`cpp`, `ros2-cpp`) | apt `rainbow-rb-sdk-dev` (stable, numeric versions only) | `find_package(rb_sdk <ver> EXACT REQUIRED)` in `cpp/CMakeLists.txt` / `ros2_ws/src/*/CMakeLists.txt`; the dev image is rebuilt with it on the next `build` / `dev:back` |
|
|
281
|
+
|
|
282
|
+
A C++ version must be digits and dots (`"<v>" is not a CMake version (digits and dots only)`).
|
|
213
283
|
|
|
214
284
|
### 3.7 `storybook`, `mcp`, `completion`, `version`
|
|
215
285
|
|
|
@@ -218,9 +288,19 @@ muscat-pk storybook [--port 6007] [--rebuild]
|
|
|
218
288
|
muscat-pk mcp [--install codex]
|
|
219
289
|
muscat-pk completion [bash|zsh|fish|powershell]
|
|
220
290
|
muscat-pk version
|
|
291
|
+
muscat-pk uninstall
|
|
221
292
|
```
|
|
222
293
|
|
|
223
|
-
|
|
294
|
+
| Command / option | Meaning |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `storybook --port` | port to serve on (default `6007`); a taken port fails with `port <n> is in use — pass a free one with --port <n>.` |
|
|
297
|
+
| `storybook --rebuild` | rebuild the Storybook first (only from a repo checkout; the published kit ships it prebuilt) |
|
|
298
|
+
| `mcp` | stdio MCP server (section 11). It relays to the real server (`mcp --serve`) and restarts it when the kit is updated, so a running agent session picks up the new tools |
|
|
299
|
+
| `mcp --install codex` | register the server in `~/.codex/config.toml` (no-op when already there) |
|
|
300
|
+
| `version` | the version of the copy that actually ran (also `-v`, `--version`) |
|
|
301
|
+
| `uninstall` | npm global: `npm uninstall -g`; binary: deletes the executable. A project-local install is removed with the project's package manager instead |
|
|
302
|
+
|
|
303
|
+
`completion` prints a Tab-completion script for your shell — once registered, `muscat-pk d<Tab>` offers `dev`, `dev:ui`, `dev:back`. It only prints the script; append it to your shell's rc file as below. `[shell]` is `bash`, `zsh`, `fish` or `powershell`, defaulting to `$SHELL` (else `powershell` on Windows, `bash` elsewhere). Windows `cmd.exe` has no completion hook — use PowerShell, Git Bash or WSL. Optional convenience; nothing depends on it.
|
|
224
304
|
|
|
225
305
|
| OS / shell | Completion setup |
|
|
226
306
|
|---|---|
|
|
@@ -243,12 +323,18 @@ my-plugin/
|
|
|
243
323
|
├─ ros2_ws/ # colcon workspace — ros2 / ros2-cpp type
|
|
244
324
|
├─ assets/icon.svg
|
|
245
325
|
├─ manifest.json
|
|
246
|
-
├─ requirements.txt # python deps (
|
|
326
|
+
├─ requirements.txt # python deps (locked into dist/requirements.lock on build)
|
|
247
327
|
├─ package.json # UI deps (not shipped)
|
|
248
328
|
├─ vite.config.ts # must use the muscat() preset
|
|
249
|
-
|
|
329
|
+
├─ tsconfig.json
|
|
330
|
+
├─ AGENTS.md, CLAUDE.md # rules for AI coding agents (CLAUDE.md → @AGENTS.md)
|
|
331
|
+
├─ .mcp.json # registers `muscat-pk mcp` for Claude Code
|
|
332
|
+
├─ .gitignore
|
|
333
|
+
└─ README.md # copied into dist/ (with README.<lang>.md, CHANGELOG.md)
|
|
250
334
|
```
|
|
251
335
|
|
|
336
|
+
The scaffolded `requirements.txt` pins `rainbow-rb-sdk`, `rainbow-rb-flat-buffers` and `rainbow-rb-zenoh` to the same version (change it with `muscat-pk sdk update`). Generated, not committed: `node_modules/`, `dist/`, `.muscat-dev/`, `.venv/`, `*.zip`, `cpp/build*`, `ros2_ws/{install,build*,log}`.
|
|
337
|
+
|
|
252
338
|
`vite.config.ts`:
|
|
253
339
|
|
|
254
340
|
```ts
|
|
@@ -265,7 +351,7 @@ The ZIP contains only the plugin's own code and assets. `@rainbow-robotics/plugi
|
|
|
265
351
|
|
|
266
352
|
## 5. manifest.json
|
|
267
353
|
|
|
268
|
-
|
|
354
|
+
Fully validated by `muscat-pk build` (errors: `manifest.json errors: …`). `dev` / `dev:back` check `variables` and `ports` before start, and `dev:back` prints the rest as warnings.
|
|
269
355
|
|
|
270
356
|
### 5.1 Full example
|
|
271
357
|
|
|
@@ -303,11 +389,12 @@ Validated by `muscat-pk dev`, `dev:back` and `build`.
|
|
|
303
389
|
| `company` | no | string |
|
|
304
390
|
| `description` | yes | string or i18n object |
|
|
305
391
|
| `icons` | yes | SVG path string, or `{ "16", "48", "128" }` for PNG/JPG (all three) |
|
|
306
|
-
| `targetModels` / `targetCategorys` | at least one | `"all"` or a non-empty array. Categories: `manipulate`, `amr`, `rby1`, `vision` |
|
|
307
|
-
| `contributes` |
|
|
392
|
+
| `targetModels` / `targetCategorys` | at least one | `"all"` or a non-empty array of strings. Categories: `manipulate`, `amr`, `rby1`, `vision` |
|
|
393
|
+
| `contributes` | no | see 5.3. Missing or `[]` = a backend-only plugin (no screens, no UI build) |
|
|
308
394
|
| `variables` | no | see 5.5 |
|
|
309
395
|
| `ports` | no | see 5.6 |
|
|
310
396
|
| `runtime` | yes | see 5.7 |
|
|
397
|
+
| `installedSize` | — | written by `build` (bytes) |
|
|
311
398
|
|
|
312
399
|
i18n object: `en` is required; allowed keys `ko`, `en`, `ja`, `zh`, `vi`, `th`, `es`.
|
|
313
400
|
|
|
@@ -322,7 +409,7 @@ i18n object: `en` is required; allowed keys `ko`, `en`, `ja`, `zh`, `vi`, `th`,
|
|
|
322
409
|
| `opens` | toolbar only: `popover`, `panel`, `page` |
|
|
323
410
|
| `commandInfo` | required for `program.panel` (5.4) |
|
|
324
411
|
|
|
325
|
-
|
|
412
|
+
A non-empty `contributes` needs at least one `toolbar` entry (that is where the host lists the plugin). Each `ui` must have a matching `ui/<entry>.tsx` (or `.ts` / `.jsx` / `.js`), checked on `build`.
|
|
326
413
|
|
|
327
414
|
| Point | Where it shows |
|
|
328
415
|
|---|---|
|
|
@@ -384,12 +471,14 @@ A backend that opens a TCP listener must declare every port:
|
|
|
384
471
|
|
|
385
472
|
| Rule | Enforced by |
|
|
386
473
|
|---|---|
|
|
387
|
-
| Only `28600-28999`, `number[]
|
|
474
|
+
| Only `28600-28999`, `number[]`, non-empty when present, no duplicates | manifest validation |
|
|
388
475
|
| Reserved ports rejected: `502`, `2222`, `4840`, `5170-5179`, `8000-8999`, `10000`, `30004`, `34964`, `44818`, `50000-50005`, `50051` | manifest validation |
|
|
389
|
-
| Every listening port must be declared | `dev` / `dev:back` runtime monitor stops the backend on the first undeclared port |
|
|
476
|
+
| Every listening port must be declared | `dev` / `dev:back` runtime monitor stops the backend on the first undeclared port (`backend opened undeclared TCP port(s): …`) |
|
|
390
477
|
| No two enabled plugins share a port | the robot, at install (HTTP 409) |
|
|
391
478
|
|
|
392
|
-
The kit's own dev ports (gateway `10000`, the Zenoh dev peer port)
|
|
479
|
+
- The kit's own dev ports (gateway `10000`, MediaMTX `8554` / `8889` / `8189`, the Zenoh dev peer port) and OS-assigned loopback ports are excluded.
|
|
480
|
+
- The monitor can't tell plugin ports apart when the backend shares a network namespace with other services, so it is off on native Linux (`--network host`) and when the backend joins a local Muscat container (the usual `dev` without a robot). Declare them anyway — the robot checks them at install.
|
|
481
|
+
- Declared ports are published on `127.0.0.1` (from the local Muscat under `dev`); a busy host port is skipped with a warning. The backend gets them as `RB_PLUGIN_PORTS`.
|
|
393
482
|
|
|
394
483
|
### 5.7 `runtime`
|
|
395
484
|
|
|
@@ -409,8 +498,11 @@ Package `@rainbow-robotics/plugin-ui`:
|
|
|
409
498
|
|
|
410
499
|
| Import | Contents |
|
|
411
500
|
|---|---|
|
|
412
|
-
| `@rainbow-robotics/plugin-ui/react` | Muscat components (`RB*`), `useRb`, `useRbFormData`, `useRbSave`, `useRbStorage`, `createRbStore` |
|
|
413
|
-
| `@rainbow-robotics/plugin-ui/sdk` | the same hooks without the components, `
|
|
501
|
+
| `@rainbow-robotics/plugin-ui/react` | Muscat components (`RB*`), `useRb`, `useRbFormData`, `useRbSave`, `useRbStorage`, `createRbStore`, `RbSdk` / `RbStore` / `RbSetState` types |
|
|
502
|
+
| `@rainbow-robotics/plugin-ui/sdk` | the same hooks without the components, plus `RbContext` and the `RbNotifyType` type |
|
|
503
|
+
| `@rainbow-robotics/plugin-ui/manifest` | manifest types (`PluginManifest`, `PluginContribution`, `PluginVariable`, `PluginRuntime`), `CONTRIBUTION_POINTS`, `VARIABLE_TYPES`, `validateManifest` |
|
|
504
|
+
|
|
505
|
+
Outside a Muscat host (a unit test, Storybook) `useRb()` returns a stub: calls log `[plugin-ui] rb.<name> called outside of a host context` and resolves to an empty value.
|
|
414
506
|
|
|
415
507
|
### 6.1 Entry component
|
|
416
508
|
|
|
@@ -561,7 +653,7 @@ await rb.storage.local.set("theme", "dark");
|
|
|
561
653
|
#### `useRbStorage` — one stored value as React state
|
|
562
654
|
|
|
563
655
|
```tsx
|
|
564
|
-
import { useRbStorage } from "@rainbow-robotics/plugin-ui/react";
|
|
656
|
+
import { RBTextInput, useRbStorage } from "@rainbow-robotics/plugin-ui/react";
|
|
565
657
|
|
|
566
658
|
type Draft = { name: string };
|
|
567
659
|
|
|
@@ -581,7 +673,7 @@ function Editor() {
|
|
|
581
673
|
Define once at module level, use from any screen of the plugin:
|
|
582
674
|
|
|
583
675
|
```tsx
|
|
584
|
-
import { createRbStore } from "@rainbow-robotics/plugin-ui/react";
|
|
676
|
+
import { createRbStore, RBSlider } from "@rainbow-robotics/plugin-ui/react";
|
|
585
677
|
|
|
586
678
|
type Settings = {
|
|
587
679
|
speed: number;
|
|
@@ -635,7 +727,8 @@ persist(fn, { name: "settings", storage: createJSONStorage(() => rb.storage.loca
|
|
|
635
727
|
|
|
636
728
|
| API | Meaning |
|
|
637
729
|
|---|---|
|
|
638
|
-
| `rb.data` | host-provided mount data (`program.monitor` scope, saved form values) |
|
|
730
|
+
| `rb.data` | host-provided mount data (`program.monitor` scope, saved form values); `undefined` for a fresh mount |
|
|
731
|
+
| `rb.onSave(getPayload)` | low-level form of `useRbSave`; returns the unregister function |
|
|
639
732
|
| `rb.rosPeers.get()` | PCs allowed to reach this plugin's ROS 2 nodes |
|
|
640
733
|
| `rb.rosPeers.set(ips)` | save the list and restart the backend; rejects an invalid IP |
|
|
641
734
|
|
|
@@ -646,11 +739,12 @@ persist(fn, { name: "settings", storage: createJSONStorage(() => rb.storage.loca
|
|
|
646
739
|
Python backends (`runtime.type: "python"`, entry `py/main.py`) use `rainbow-rb-sdk`, pinned in `requirements.txt` and changed with `muscat-pk sdk update`. The robot's plugin container provides the SDK at runtime.
|
|
647
740
|
|
|
648
741
|
```python
|
|
649
|
-
from rainbow.rb_sdk import RBPluginCall, PluginProgramContext, RBManipulateSDK, RBAmrSDK
|
|
650
|
-
from rainbow.rb_sdk.
|
|
651
|
-
from rainbow.rb_sdk.rb_sdk_cpp import ConnectOptions
|
|
742
|
+
from rainbow.rb_sdk import RBPluginCall, PluginProgramContext, RBManipulateSDK, RBAmrSDK, RBRby1SDK
|
|
743
|
+
from rainbow.rb_sdk.rb_sdk_cpp import ConnectOptions, PluginRouteError, RBPluginGatewayError
|
|
652
744
|
```
|
|
653
745
|
|
|
746
|
+
`rainbow.rb_sdk` exports `RBPluginCall`, `PluginProgramContext`, `RBManipulateSDK`, `RBAmrSDK`, `RBRby1SDK`, `RBProgramSDK`, `RBBaseSDK` and `rby1` (the vendored rby1-sdk types). Everything else (`ConnectOptions`, `PluginRouter`, `PluginRouteError`, `RBPluginGatewayError`, …) is in `rainbow.rb_sdk.rb_sdk_cpp`.
|
|
747
|
+
|
|
654
748
|
Which API depends on the contribution point:
|
|
655
749
|
|
|
656
750
|
| Contribution point | Backend |
|
|
@@ -660,7 +754,7 @@ Which API depends on the contribution point:
|
|
|
660
754
|
|
|
661
755
|
### 7.1 `RBPluginCall` — UI ↔ backend
|
|
662
756
|
|
|
663
|
-
Internal only (UI ↔ backend, plugin ↔ plugin); not reachable from outside Muscat.
|
|
757
|
+
Internal only (UI ↔ backend, plugin ↔ plugin); not reachable from outside Muscat. `RBPluginCall(plugin_id=None)`: the id is the argument, else env `RB_PLUGIN_ID`, else `PLUGIN_ID` (slashes trimmed). There is one instance per process and plugin id — calling `RBPluginCall()` again returns the same object.
|
|
664
758
|
|
|
665
759
|
```python
|
|
666
760
|
plugin = RBPluginCall()
|
|
@@ -681,18 +775,18 @@ res = plugin.call("tool/ping", payload={"hi": 1}, timeout=3.0) # UI: rb.serve(
|
|
|
681
775
|
|
|
682
776
|
| Member | Meaning |
|
|
683
777
|
|---|---|
|
|
684
|
-
| `serve(keyexpr, handler=None)` | request/response handler; decorator or direct call. Return value = reply |
|
|
778
|
+
| `serve(keyexpr, handler=None)` | request/response handler; decorator or direct call. Return value = reply. One handler per key — serving it again replaces the previous one |
|
|
685
779
|
| `subscribe(topic, callback=None)` | receive publishes |
|
|
686
|
-
| `publish(topic, payload=None)` | push to subscribers |
|
|
687
|
-
| `call(keyexpr, *, payload, timeout=3.0)` | request the other side and wait |
|
|
688
|
-
| `unserve(keyexpr=None)` | remove one / all serve handlers |
|
|
689
|
-
| `unsubscribe(topic=None, callback=None)` | remove subscriptions |
|
|
780
|
+
| `publish(topic, payload=None)` | push to subscribers; dropped silently while disconnected |
|
|
781
|
+
| `call(keyexpr, *, payload, timeout=3.0)` | request the other side and wait; raises `RBPluginGatewayError` when disconnected or on timeout |
|
|
782
|
+
| `unserve(keyexpr=None)` / `undeclare_queryable(keyexpr=None)` | remove one / all serve handlers |
|
|
783
|
+
| `unsubscribe(topic=None, callback=None)` / `unsubscribe_topic(topic=None, callback=None)` | remove one callback, a topic's subscriptions, or all |
|
|
690
784
|
| `plugin_id` | the plugin id in use (dev id under `muscat-pk dev`) |
|
|
691
785
|
| `connected` | transport connected |
|
|
692
|
-
| `data_dir` | persistent data directory, created on access
|
|
786
|
+
| `data_dir` | persistent data directory, created on access: `RB_PLUGIN_DATA_DIR` (set by Muscat), else `/app/data/runtime/plugins/data/{plugin_id}`. Survives updates and restarts; removed with the plugin. The install directory (cwd) is replaced on every update — keep state, caches and settings here |
|
|
693
787
|
| `router` | external endpoints (7.2) |
|
|
694
788
|
|
|
695
|
-
Payloads must be JSON-serialisable. Handlers may be sync or async.
|
|
789
|
+
Payloads must be JSON-serialisable. Handlers may be sync or async. Topics are scoped like the UI's: `"state"` → `{plugin_id}/state`, a leading `/` is absolute.
|
|
696
790
|
|
|
697
791
|
### 7.2 `plugin.router` — external endpoints
|
|
698
792
|
|
|
@@ -818,6 +912,8 @@ asyncio.run(main())
|
|
|
818
912
|
| `executor_state(robot_model=None)` | state string for a model, or `{model: state}` for all |
|
|
819
913
|
| `is_paused(robot_model=None)` / `is_stopped(robot_model=None)` | state checks |
|
|
820
914
|
| `on_executor_state_change(fn)` | decorator; `(robot_model, state)` once per change |
|
|
915
|
+
| `set_execute_handler(fn)` / `set_state_change_handler(fn)` | the same two registrations without a decorator |
|
|
916
|
+
| `begin_execution(robot_model=None)` / `end_execution()` | execute lifecycle hooks the SDK calls around `on_execute`; not needed in normal plugins |
|
|
821
917
|
| `start()` / `stop()` / `pause()` | lifecycle |
|
|
822
918
|
| `in_execute`, `exec_robot_model`, `plugin_id`, `started`, `control_topic` | properties |
|
|
823
919
|
|
|
@@ -928,16 +1024,21 @@ sudo apt update && sudo apt install rainbow-rb-sdk-dev
|
|
|
928
1024
|
|
|
929
1025
|
### 9.1 Local Muscat runtime
|
|
930
1026
|
|
|
931
|
-
Without a robot IP, `dev` / `dev:ui` need a Muscat on `localhost:3000`:
|
|
1027
|
+
Without a robot IP (or against a robot that runs no Muscat), `dev` / `dev:ui` need a Muscat on `localhost:3000` (API) and `:10000` (plugin gateway):
|
|
932
1028
|
|
|
933
|
-
- reuses any container already publishing `:10000` (`muscat`, `muscat-dev`, …);
|
|
934
|
-
-
|
|
1029
|
+
- reuses any container already publishing `:10000` (`muscat`, `muscat-dev`, …). A container not started by the kit does not publish the plugin's ports;
|
|
1030
|
+
- uses a Muscat already listening on those ports without a container (robot PC, host networking);
|
|
1031
|
+
- otherwise starts `muscat-pk-muscat` — the S3 runtime image (`muscat-pk-runtime:<version>`, from `version.json`) running `common` (plus the services `--sim` needs) and MediaMTX (`8554`, `8889`, `8189`), cached in `~/.muscat-pk/muscat/`. It is recreated when the published plugin ports or services change, and a new Muscat version replaces the old image.
|
|
1032
|
+
- `dev` removes its own `muscat-pk-muscat` on exit; started by `dev:ui` alone it keeps running.
|
|
1033
|
+
- The backend joins the local Muscat's network namespace, so `execute` and the SDK reach it like on the robot.
|
|
935
1034
|
|
|
936
1035
|
```bash
|
|
937
1036
|
docker rm -f muscat-pk-muscat # stop it
|
|
938
1037
|
MUSCAT_PK_NO_LOCAL_MUSCAT=1 muscat-pk dev # don't start it
|
|
939
1038
|
```
|
|
940
1039
|
|
|
1040
|
+
Its API docs are at `http://localhost:3000/common/docs`.
|
|
1041
|
+
|
|
941
1042
|
### 9.2 Developing on the robot PC
|
|
942
1043
|
|
|
943
1044
|
On the robot PC Muscat already serves the gateway on `10000`; the CLI uses it and starts no local Muscat.
|
|
@@ -952,13 +1053,15 @@ If the project has its own `muscat-plugin-kit` in `node_modules`, any `muscat-pk
|
|
|
952
1053
|
|
|
953
1054
|
### 9.5 Offline / air-gapped
|
|
954
1055
|
|
|
955
|
-
Docker images are built only when missing (a rebuild contacts the registry even when cached).
|
|
1056
|
+
Docker images are built only when missing or when the kit's Dockerfile / build args changed (a rebuild contacts the registry even when cached).
|
|
956
1057
|
|
|
957
1058
|
```bash
|
|
958
1059
|
docker save muscat-pk-python-dev muscat-pk-runtime | gzip > muscat-pk-images.tar.gz # online machine
|
|
959
1060
|
gunzip -c muscat-pk-images.tar.gz | docker load # offline machine
|
|
960
1061
|
```
|
|
961
1062
|
|
|
1063
|
+
C++ / ROS 2 projects also need `muscat-pk-cpp-dev` / `muscat-pk-ros2-dev` (and the per-arch `-amd64` / `-arm64` images used by `build`). Offline, the local Muscat uses the cached `version.json` and the simulators an already pulled image.
|
|
1064
|
+
|
|
962
1065
|
Force a rebuild with `MUSCAT_PK_REBUILD_IMAGE=1`.
|
|
963
1066
|
|
|
964
1067
|
### 9.6 IDE support (Python)
|
|
@@ -1006,13 +1109,20 @@ and `"jsxImportSource": "@emotion/react"` in `tsconfig.json`. Emotion `Global` i
|
|
|
1006
1109
|
|
|
1007
1110
|
An MCP server (stdio) with grounded info on rb-components (props, Storybook examples), design tokens, and the Python `rainbow-rb-sdk` (introspected signatures/docstrings), plus `check_project` for upgrading old projects.
|
|
1008
1111
|
|
|
1009
|
-
|
|
1112
|
+
| Tool | Returns |
|
|
1113
|
+
|---|---|
|
|
1114
|
+
| `list_components`, `get_component_props`, `get_component_example` | plugin-ui components, their prop types, real Storybook usage |
|
|
1115
|
+
| `list_design_tokens`, `search_design_tokens` | design-token Tailwind classes (`bg-bg-*`, `text-fg-*`, `typo-*`) |
|
|
1116
|
+
| `list_rb_sdk`, `search_rb_sdk`, `get_rb_sdk_api` | Python `rainbow-rb-sdk` and C++ SDK classes / signatures / docstrings |
|
|
1117
|
+
| `check_project` | what an older project must change after `muscat-pk update` |
|
|
1118
|
+
|
|
1119
|
+
Claude Code — `.mcp.json` at the plugin root (written by `new`, or by `update` when missing):
|
|
1010
1120
|
|
|
1011
1121
|
```json
|
|
1012
1122
|
{ "mcpServers": { "muscat": { "command": "muscat-pk", "args": ["mcp"] } } }
|
|
1013
1123
|
```
|
|
1014
1124
|
|
|
1015
|
-
Codex — `muscat-pk mcp --install codex
|
|
1125
|
+
Codex — `muscat-pk mcp --install codex` (on Windows it writes `muscat-pk.cmd`), or `~/.codex/config.toml`:
|
|
1016
1126
|
|
|
1017
1127
|
```toml
|
|
1018
1128
|
[mcp_servers.muscat]
|
|
@@ -1033,12 +1143,20 @@ The agent starts the server itself.
|
|
|
1033
1143
|
| `MUSCAT_PK_NO_LOCAL_MUSCAT=1` | don't start the local Muscat runtime |
|
|
1034
1144
|
| `MUSCAT_PK_REBUILD_IMAGE=1` | rebuild Docker images |
|
|
1035
1145
|
| `MUSCAT_PK_RB_SDK_VERSION` | C++ SDK version baked into the C++ images (with `MUSCAT_PK_REBUILD_IMAGE=1`) |
|
|
1146
|
+
| `MUSCAT_PK_NO_BUILD_CACHE=1` | build Docker images with `--no-cache` (CI) |
|
|
1147
|
+
| `MUSCAT_PK_HOST_NETWORK=0` | on native Linux, use the Docker Desktop (bridge) path instead of `--network host` (CI) |
|
|
1148
|
+
| `MUSCAT_PK_RBY1_CPUS` | CPU cap of the rby1 simulator (default `1.5`) |
|
|
1149
|
+
| `MUSCAT_PK_MUSCAT_S3` | where the local Muscat runtime is downloaded from (default `https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat/main`) |
|
|
1150
|
+
| `MUSCAT_HOST_AGENT_DISABLE=1` | don't start the host Zenoh byte-proxy (macOS / Windows / WSL against a remote robot) |
|
|
1151
|
+
| `MUSCAT_PROXY_PORT` | port of that proxy (default `47100`) |
|
|
1152
|
+
| `MUSCAT_ZENOH_ADVERTISE_HOST` / `MUSCAT_ZENOH_PORT` | LAN IP / port the remote robot dials back to (default: the route to the robot / `53917` or the next free port) |
|
|
1036
1153
|
| `MUSCAT_PK_NO_UPDATE_CHECK=1` | skip the update check on `dev` / `dev:ui` |
|
|
1037
1154
|
| `RB_REGISTRY` | npm registry for the update check (default `https://registry.npmjs.org`) |
|
|
1038
1155
|
| `MUSCAT_PK_TAG` | install script channel / version |
|
|
1039
|
-
| `
|
|
1156
|
+
| `CI` | set: `dev:ui` doesn't open the browser |
|
|
1157
|
+
| `NO_COLOR=1` / `FORCE_COLOR` | output colors |
|
|
1040
1158
|
|
|
1041
|
-
Inside the backend container: `RB_PLUGIN_ID` (the plugin / dev id).
|
|
1159
|
+
Inside the backend container: `RB_PLUGIN_ID` (the plugin / dev id), `RB_PLUGIN_PORTS` (declared `ports`, comma-separated), `MUSCAT_ROBOT_HOST` / `MUSCAT_ROBOT_TOKEN` (remote dev). On the robot Muscat also sets `RB_PLUGIN_DATA_DIR` (see `data_dir`, 7.1).
|
|
1042
1160
|
|
|
1043
1161
|
---
|
|
1044
1162
|
|
|
@@ -1047,10 +1165,18 @@ Inside the backend container: `RB_PLUGIN_ID` (the plugin / dev id).
|
|
|
1047
1165
|
| Symptom | Fix |
|
|
1048
1166
|
|---|---|
|
|
1049
1167
|
| `manifest.json errors: …` | fix each listed field (section 5) |
|
|
1168
|
+
| `manifest.json variable errors: …` / `manifest.json port errors: …` on `dev` | fix `variables` (5.5) / `ports` (5.6) |
|
|
1169
|
+
| `A Muscat plugin dev environment is already running: …` | answer `y` to stop it, or stop the other `dev` first |
|
|
1170
|
+
| `Docker is installed but the daemon is not running` | start Docker Desktop (or `sudo systemctl start docker`) |
|
|
1171
|
+
| `contributes declares ui "<entry>" but ui/<entry>.tsx … does not exist.` | add the file or fix `contributes[].ui` |
|
|
1172
|
+
| `wheel download failed for <arch>` | a dependency has no cp312 binary wheel for that arch — pick another version or package |
|
|
1173
|
+
| `can't build <arch> on this <host>: Docker has no <arch> emulation.` | `docker run --privileged --rm tonistiigi/binfmt --install <arch>`, or `--arch <host arch>` |
|
|
1174
|
+
| `Permission denied when deleting dist/` | an old build left root-owned files: `sudo rm -rf dist` once |
|
|
1050
1175
|
| backend stops with an undeclared port | add the port (28600-28999) to `ports` |
|
|
1051
1176
|
| UI preview says "no default export" | entry must `export default App`; `vite.config.ts` must use `muscat()`; run `check_project` via `muscat-pk mcp` |
|
|
1052
1177
|
| `vite not found — UI will not load` | run `npm install` in the project |
|
|
1053
1178
|
| `AttributeError: router` | robot SDK older than 0.0.9.dev5 |
|
|
1179
|
+
| `RBPluginGatewayError` from `plugin.call` | the gateway is disconnected, or no reply within `timeout` (is a UI `rb.serve` registered?) |
|
|
1054
1180
|
| step never finishes | `on_execute` must `await ctx.done()` |
|
|
1055
1181
|
| Stop doesn't stop the plugin | call `await ctx.check_stop()` in every loop |
|
|
1056
1182
|
| `RuntimeError` on `set_variable` | new variable names need `RBP_` |
|
package/cli/README.md
CHANGED
|
@@ -229,8 +229,10 @@ inside plugins.
|
|
|
229
229
|
at `/plugin-dev` — a real mini-Muscat shell. Without a robot IP it needs a Muscat
|
|
230
230
|
on `localhost:3000` for program preview: `dev` / `dev:ui` reuse any container that
|
|
231
231
|
already publishes `:10000` (`muscat`, `muscat-dev`, …) and otherwise start
|
|
232
|
-
`muscat-pk-muscat` — the S3 runtime image (`muscat/main/muscat.zip`) running
|
|
232
|
+
`muscat-pk-muscat` — the S3 runtime image (`muscat/main/muscat.zip`) running the
|
|
233
233
|
`common` service from `muscat/main/version.json`, cached under `~/.muscat-pk/muscat/`.
|
|
234
|
+
`dev --sim <model>` adds the services the model needs (log + manipulate / rby1), opens
|
|
235
|
+
common's Zenoh on the `muscat-pk` network, and starts simulators there (`cli/src/sim.mjs`).
|
|
234
236
|
It keeps running between runs (`docker rm -f muscat-pk-muscat` to stop);
|
|
235
237
|
`MUSCAT_PK_NO_LOCAL_MUSCAT=1` disables it.
|
|
236
238
|
|
package/cli/bin/muscat-pk.mjs
CHANGED
|
@@ -88,7 +88,7 @@ function formatHelp() {
|
|
|
88
88
|
commandRow("new [dir]", `Scaffold a new plugin project ${color.dim("(prompts: author, email, description, type, front-package-manager)")}`),
|
|
89
89
|
commandRow("type change", `Switch backend type ${color.dim("(python | cpp | ros2-python | ros2-cpp)")}`),
|
|
90
90
|
commandRow("build", `Build + lock deps + vendor wheels + zip ${color.dim(`(${option("--arch")} amd64|arm64|both)`)}`),
|
|
91
|
-
commandRow("dev",
|
|
91
|
+
commandRow("dev", `Run a local Muscat runtime, dev:ui, and dev:back together ${color.dim(`(${option("--sim")} 500880|500920|RBY2|rby1-a|rby1-m|rby1-ub)`)}`),
|
|
92
92
|
commandRow("dev:ui", `Preview the plugin in a mini-Muscat ${color.dim("(preview-host)")}`),
|
|
93
93
|
commandRow("dev:back", `Run the plugin back end ${color.dim("(python: watchfiles reload / ros2: Docker)")}`),
|
|
94
94
|
commandRow("storybook", `Browse the bundled rb-components Storybook ${color.dim(`(${option("--port")} <n>, ${option("--rebuild")})`)}`),
|
|
@@ -96,6 +96,7 @@ function formatHelp() {
|
|
|
96
96
|
commandRow("update", `Update ${color.magenta("@rainbow-robotics/muscat-plugin-kit")}, ${color.magenta("@rainbow-robotics/plugin-ui")} and ${color.magenta("@rainbow-robotics/preview-host")} ${color.dim(`(local-only if run from a project's local install; ${option("--global")} also updates the global CLI)`)}`),
|
|
97
97
|
commandRow("uninstall", `Remove the installed ${color.cyan("muscat-pk")} CLI ${color.dim("(npm global or S3 binary)")}`),
|
|
98
98
|
commandRow("sdk <list|update [version]>", "Manage the python rainbow-rb-sdk version"),
|
|
99
|
+
commandRow("sim list", `List the robot simulators for ${option("dev --sim")}`),
|
|
99
100
|
commandRow("version", `Print the installed ${color.cyan("muscat-pk")} version`),
|
|
100
101
|
commandRow("completion [shell]", `Print a shell completion script ${color.dim("(bash | zsh | fish | powershell)")}`),
|
|
101
102
|
"",
|
|
@@ -137,6 +138,9 @@ async function main() {
|
|
|
137
138
|
return await runUninstall(args);
|
|
138
139
|
case "sdk":
|
|
139
140
|
return await runSdk(args);
|
|
141
|
+
case "sim":
|
|
142
|
+
if (args[0] !== "list") throw new Error("usage: muscat-pk sim list");
|
|
143
|
+
return (await import("../src/sim.mjs")).runSimList();
|
|
140
144
|
case "completion":
|
|
141
145
|
return await runCompletion(args);
|
|
142
146
|
case "version":
|
package/cli/src/completion.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const COMMANDS = [
|
|
|
20
20
|
["update", "Update muscat-pk and plugin-ui"],
|
|
21
21
|
["uninstall", "Remove the installed muscat-pk CLI"],
|
|
22
22
|
["sdk", "Manage the rainbow-rb-sdk version (python: requirements.txt, C++: CMakeLists.txt)"],
|
|
23
|
+
["sim", "List the robot simulators for dev --sim"],
|
|
23
24
|
["version", "Print the installed muscat-pk version"],
|
|
24
25
|
["completion", "Print a shell completion script"],
|
|
25
26
|
["help", "Show help"],
|
|
@@ -28,6 +29,7 @@ const COMMANDS = [
|
|
|
28
29
|
const SUBCOMMANDS = {
|
|
29
30
|
type: ["change"],
|
|
30
31
|
sdk: ["list", "update"],
|
|
32
|
+
sim: ["list"],
|
|
31
33
|
completion: ["bash", "zsh", "fish", "powershell"],
|
|
32
34
|
};
|
|
33
35
|
|
package/cli/src/dev.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// muscat-pk dev — run the local Muscat runtime (gateway, MediaMTX, common),
|
|
1
|
+
// muscat-pk dev — run the local Muscat runtime (gateway, MediaMTX, common), robot simulators (--sim),
|
|
2
|
+
// dev:ui, and dev:back together.
|
|
2
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
4
|
import { existsSync } from "node:fs";
|
|
4
5
|
import { connect as netConnect } from "node:net";
|
|
@@ -7,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
8
|
import { runDevUi } from "./devUi.mjs";
|
|
8
9
|
import { claimDevSession, stopDevBackContainers } from "./devSession.mjs";
|
|
9
10
|
import { ensureLocalMuscat, GATEWAY_PORT, isPortInUse, MUSCAT_CONTAINER, robotHasMuscat } from "./muscatLocal.mjs";
|
|
11
|
+
import { resolveSim, startSim, stopSims } from "./sim.mjs";
|
|
10
12
|
import { assertManifestPorts, assertManifestVariables, readManifest } from "./manifest.mjs";
|
|
11
13
|
import { devPluginId } from "./devId.mjs";
|
|
12
14
|
import {
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
ensureIdeVenv,
|
|
15
17
|
findPluginRoot,
|
|
16
18
|
log,
|
|
19
|
+
parseArgs,
|
|
17
20
|
prompt,
|
|
18
21
|
promptRobotConnection,
|
|
19
22
|
venvPython,
|
|
@@ -129,13 +132,24 @@ export async function runDev(args = []) {
|
|
|
129
132
|
// the robot itself — the local Muscat then publishes the zenoh port for it.
|
|
130
133
|
if (!proxyEndpoint) await ensureRemoteZenohEnv(robotIp, { log });
|
|
131
134
|
|
|
135
|
+
// No robot: a simulator stands in for it (--sim, else the manifest's target).
|
|
136
|
+
const sim = remoteRobot ? null : resolveSim(parseArgs(args).sim, readManifest(root));
|
|
137
|
+
|
|
132
138
|
// A robot running Muscat is the gateway (:10000) and common for both the UI and the backend.
|
|
133
139
|
// Otherwise the local Muscat runtime is; the backend joins its netns, so the plugin's declared
|
|
134
140
|
// ports are published from it too.
|
|
135
141
|
const muscat = (await robotHasMuscat(robotIp)) ? null : await ensureLocalMuscat({
|
|
136
142
|
pluginPorts: portRanges,
|
|
137
143
|
zenohPort: remoteRobot && !proxyEndpoint ? Number(process.env.MUSCAT_ZENOH_PORT) || null : null,
|
|
144
|
+
services: sim?.services,
|
|
145
|
+
env: sim?.env,
|
|
138
146
|
}).catch((e) => { log.warn(`local Muscat: ${e.message}`); return null; });
|
|
147
|
+
if (sim && muscat !== MUSCAT_CONTAINER) {
|
|
148
|
+
log.warn(`--sim needs muscat-pk's own Muscat${muscat ? ` ('${muscat}' is running — stop it to use the simulator)` : ""}; no simulator.`);
|
|
149
|
+
} else if (sim) {
|
|
150
|
+
// dev:ui then serves the real Muscat UI against the local Muscat, like with muscat-dev.
|
|
151
|
+
if (await startSim(sim).catch((e) => log.warn(`simulator: ${e.message}`))) process.env.MUSCAT_PK_SIM = sim.name;
|
|
152
|
+
}
|
|
139
153
|
if (muscat) {
|
|
140
154
|
process.env.MUSCAT_PK_MUSCAT_CONTAINER = muscat;
|
|
141
155
|
} else if (remoteRobot || (await isPortInUse(GATEWAY_PORT))) {
|
|
@@ -161,7 +175,10 @@ export async function runDev(args = []) {
|
|
|
161
175
|
if (backend.exitCode === null) backend.kill();
|
|
162
176
|
stopDevBackContainers();
|
|
163
177
|
// Only our own runtime — a muscat-dev the user started stays up.
|
|
164
|
-
if (muscat === MUSCAT_CONTAINER)
|
|
178
|
+
if (muscat === MUSCAT_CONTAINER) {
|
|
179
|
+
stopSims();
|
|
180
|
+
spawnSync("docker", ["rm", "-f", MUSCAT_CONTAINER], { stdio: "ignore" });
|
|
181
|
+
}
|
|
165
182
|
};
|
|
166
183
|
process.once("SIGINT", cleanup);
|
|
167
184
|
process.once("SIGTERM", cleanup);
|
package/cli/src/devBack.mjs
CHANGED
|
@@ -61,7 +61,7 @@ function muscatZenohEnv(container) {
|
|
|
61
61
|
|
|
62
62
|
function backendNetwork(portRanges, robotGateway) {
|
|
63
63
|
const muscat = process.env.MUSCAT_PK_MUSCAT_CONTAINER;
|
|
64
|
-
if (muscat) return { run: ["--network", `container:${muscat}
|
|
64
|
+
if (muscat) return { run: ["--network", `container:${muscat}`, "--ipc", "host"], env: muscatZenohEnv(muscat), shared: true };
|
|
65
65
|
// The robot runs Muscat: its gateway, own netns (host network on Linux), declared ports published.
|
|
66
66
|
if (robotGateway) {
|
|
67
67
|
const run = usesHostNetwork() ? ["--network", "host"] : publishPortFlags(portRanges);
|
package/cli/src/devUi.mjs
CHANGED
|
@@ -214,7 +214,8 @@ export async function runDevUi(args = []) {
|
|
|
214
214
|
});
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
// muscat-dev, or our own Muscat with a simulator (`dev --sim`): a full local Muscat to show.
|
|
218
|
+
const localDevMuscat = !!process.env.MUSCAT_PK_SIM || (await localFullMuscat(robotIp));
|
|
218
219
|
const uiHost = isRemoteRobot(robotIp) ? robotIp : localDevMuscat ? "localhost" : "";
|
|
219
220
|
|
|
220
221
|
const previewRoot = findPreviewHostRoot();
|