@rainbow-robotics/muscat-plugin-kit 1.10.3-dev.3 → 1.10.3
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 +187 -60
- package/README.md +187 -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
|
|
|
@@ -485,6 +577,7 @@ if (await rb.confirm({ title: "Run", message: "Start the backend action?" })) {
|
|
|
485
577
|
| `rb.program.getSteps(scope?)` | main step tree of the program open on the Program page |
|
|
486
578
|
| `rb.program.setSteps(steps, scope?)` | replace it — an unsaved edit the user then saves |
|
|
487
579
|
| `rb.program.getCatalog(scope?)` | the loaded robot's command catalog (names, default args, nesting rules) |
|
|
580
|
+
| `rb.program.getCommandDefaults?(command, scope?)` | default step of a command whose defaults read the live robot (catalog `rules.hasFactoryDataFn`); optional, host-dependent |
|
|
488
581
|
|
|
489
582
|
```ts
|
|
490
583
|
type PluginProgramStep = {
|
|
@@ -561,7 +654,7 @@ await rb.storage.local.set("theme", "dark");
|
|
|
561
654
|
#### `useRbStorage` — one stored value as React state
|
|
562
655
|
|
|
563
656
|
```tsx
|
|
564
|
-
import { useRbStorage } from "@rainbow-robotics/plugin-ui/react";
|
|
657
|
+
import { RBTextInput, useRbStorage } from "@rainbow-robotics/plugin-ui/react";
|
|
565
658
|
|
|
566
659
|
type Draft = { name: string };
|
|
567
660
|
|
|
@@ -581,7 +674,7 @@ function Editor() {
|
|
|
581
674
|
Define once at module level, use from any screen of the plugin:
|
|
582
675
|
|
|
583
676
|
```tsx
|
|
584
|
-
import { createRbStore } from "@rainbow-robotics/plugin-ui/react";
|
|
677
|
+
import { createRbStore, RBSlider } from "@rainbow-robotics/plugin-ui/react";
|
|
585
678
|
|
|
586
679
|
type Settings = {
|
|
587
680
|
speed: number;
|
|
@@ -635,7 +728,8 @@ persist(fn, { name: "settings", storage: createJSONStorage(() => rb.storage.loca
|
|
|
635
728
|
|
|
636
729
|
| API | Meaning |
|
|
637
730
|
|---|---|
|
|
638
|
-
| `rb.data` | host-provided mount data (`program.monitor` scope, saved form values) |
|
|
731
|
+
| `rb.data` | host-provided mount data (`program.monitor` scope, saved form values); `undefined` for a fresh mount |
|
|
732
|
+
| `rb.onSave(getPayload)` | low-level form of `useRbSave`; returns the unregister function |
|
|
639
733
|
| `rb.rosPeers.get()` | PCs allowed to reach this plugin's ROS 2 nodes |
|
|
640
734
|
| `rb.rosPeers.set(ips)` | save the list and restart the backend; rejects an invalid IP |
|
|
641
735
|
|
|
@@ -646,11 +740,12 @@ persist(fn, { name: "settings", storage: createJSONStorage(() => rb.storage.loca
|
|
|
646
740
|
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
741
|
|
|
648
742
|
```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
|
|
743
|
+
from rainbow.rb_sdk import RBPluginCall, PluginProgramContext, RBManipulateSDK, RBAmrSDK, RBRby1SDK
|
|
744
|
+
from rainbow.rb_sdk.rb_sdk_cpp import ConnectOptions, PluginRouteError, RBPluginGatewayError
|
|
652
745
|
```
|
|
653
746
|
|
|
747
|
+
`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`.
|
|
748
|
+
|
|
654
749
|
Which API depends on the contribution point:
|
|
655
750
|
|
|
656
751
|
| Contribution point | Backend |
|
|
@@ -660,7 +755,7 @@ Which API depends on the contribution point:
|
|
|
660
755
|
|
|
661
756
|
### 7.1 `RBPluginCall` — UI ↔ backend
|
|
662
757
|
|
|
663
|
-
Internal only (UI ↔ backend, plugin ↔ plugin); not reachable from outside Muscat.
|
|
758
|
+
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
759
|
|
|
665
760
|
```python
|
|
666
761
|
plugin = RBPluginCall()
|
|
@@ -681,18 +776,18 @@ res = plugin.call("tool/ping", payload={"hi": 1}, timeout=3.0) # UI: rb.serve(
|
|
|
681
776
|
|
|
682
777
|
| Member | Meaning |
|
|
683
778
|
|---|---|
|
|
684
|
-
| `serve(keyexpr, handler=None)` | request/response handler; decorator or direct call. Return value = reply |
|
|
779
|
+
| `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
780
|
| `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 |
|
|
781
|
+
| `publish(topic, payload=None)` | push to subscribers; dropped silently while disconnected |
|
|
782
|
+
| `call(keyexpr, *, payload, timeout=3.0)` | request the other side and wait; raises `RBPluginGatewayError` when disconnected or on timeout |
|
|
783
|
+
| `unserve(keyexpr=None)` / `undeclare_queryable(keyexpr=None)` | remove one / all serve handlers |
|
|
784
|
+
| `unsubscribe(topic=None, callback=None)` / `unsubscribe_topic(topic=None, callback=None)` | remove one callback, a topic's subscriptions, or all |
|
|
690
785
|
| `plugin_id` | the plugin id in use (dev id under `muscat-pk dev`) |
|
|
691
786
|
| `connected` | transport connected |
|
|
692
|
-
| `data_dir` | persistent data directory, created on access
|
|
787
|
+
| `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
788
|
| `router` | external endpoints (7.2) |
|
|
694
789
|
|
|
695
|
-
Payloads must be JSON-serialisable. Handlers may be sync or async.
|
|
790
|
+
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
791
|
|
|
697
792
|
### 7.2 `plugin.router` — external endpoints
|
|
698
793
|
|
|
@@ -818,6 +913,8 @@ asyncio.run(main())
|
|
|
818
913
|
| `executor_state(robot_model=None)` | state string for a model, or `{model: state}` for all |
|
|
819
914
|
| `is_paused(robot_model=None)` / `is_stopped(robot_model=None)` | state checks |
|
|
820
915
|
| `on_executor_state_change(fn)` | decorator; `(robot_model, state)` once per change |
|
|
916
|
+
| `set_execute_handler(fn)` / `set_state_change_handler(fn)` | the same two registrations without a decorator |
|
|
917
|
+
| `begin_execution(robot_model=None)` / `end_execution()` | execute lifecycle hooks the SDK calls around `on_execute`; not needed in normal plugins |
|
|
821
918
|
| `start()` / `stop()` / `pause()` | lifecycle |
|
|
822
919
|
| `in_execute`, `exec_robot_model`, `plugin_id`, `started`, `control_topic` | properties |
|
|
823
920
|
|
|
@@ -928,16 +1025,21 @@ sudo apt update && sudo apt install rainbow-rb-sdk-dev
|
|
|
928
1025
|
|
|
929
1026
|
### 9.1 Local Muscat runtime
|
|
930
1027
|
|
|
931
|
-
Without a robot IP, `dev` / `dev:ui` need a Muscat on `localhost:3000`:
|
|
1028
|
+
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
1029
|
|
|
933
|
-
- reuses any container already publishing `:10000` (`muscat`, `muscat-dev`, …);
|
|
934
|
-
-
|
|
1030
|
+
- reuses any container already publishing `:10000` (`muscat`, `muscat-dev`, …). A container not started by the kit does not publish the plugin's ports;
|
|
1031
|
+
- uses a Muscat already listening on those ports without a container (robot PC, host networking);
|
|
1032
|
+
- 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.
|
|
1033
|
+
- `dev` removes its own `muscat-pk-muscat` on exit; started by `dev:ui` alone it keeps running.
|
|
1034
|
+
- The backend joins the local Muscat's network namespace, so `execute` and the SDK reach it like on the robot.
|
|
935
1035
|
|
|
936
1036
|
```bash
|
|
937
1037
|
docker rm -f muscat-pk-muscat # stop it
|
|
938
1038
|
MUSCAT_PK_NO_LOCAL_MUSCAT=1 muscat-pk dev # don't start it
|
|
939
1039
|
```
|
|
940
1040
|
|
|
1041
|
+
Its API docs are at `http://localhost:3000/common/docs`.
|
|
1042
|
+
|
|
941
1043
|
### 9.2 Developing on the robot PC
|
|
942
1044
|
|
|
943
1045
|
On the robot PC Muscat already serves the gateway on `10000`; the CLI uses it and starts no local Muscat.
|
|
@@ -952,13 +1054,15 @@ If the project has its own `muscat-plugin-kit` in `node_modules`, any `muscat-pk
|
|
|
952
1054
|
|
|
953
1055
|
### 9.5 Offline / air-gapped
|
|
954
1056
|
|
|
955
|
-
Docker images are built only when missing (a rebuild contacts the registry even when cached).
|
|
1057
|
+
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
1058
|
|
|
957
1059
|
```bash
|
|
958
1060
|
docker save muscat-pk-python-dev muscat-pk-runtime | gzip > muscat-pk-images.tar.gz # online machine
|
|
959
1061
|
gunzip -c muscat-pk-images.tar.gz | docker load # offline machine
|
|
960
1062
|
```
|
|
961
1063
|
|
|
1064
|
+
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.
|
|
1065
|
+
|
|
962
1066
|
Force a rebuild with `MUSCAT_PK_REBUILD_IMAGE=1`.
|
|
963
1067
|
|
|
964
1068
|
### 9.6 IDE support (Python)
|
|
@@ -1006,13 +1110,20 @@ and `"jsxImportSource": "@emotion/react"` in `tsconfig.json`. Emotion `Global` i
|
|
|
1006
1110
|
|
|
1007
1111
|
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
1112
|
|
|
1009
|
-
|
|
1113
|
+
| Tool | Returns |
|
|
1114
|
+
|---|---|
|
|
1115
|
+
| `list_components`, `get_component_props`, `get_component_example` | plugin-ui components, their prop types, real Storybook usage |
|
|
1116
|
+
| `list_design_tokens`, `search_design_tokens` | design-token Tailwind classes (`bg-bg-*`, `text-fg-*`, `typo-*`) |
|
|
1117
|
+
| `list_rb_sdk`, `search_rb_sdk`, `get_rb_sdk_api` | Python `rainbow-rb-sdk` and C++ SDK classes / signatures / docstrings |
|
|
1118
|
+
| `check_project` | what an older project must change after `muscat-pk update` |
|
|
1119
|
+
|
|
1120
|
+
Claude Code — `.mcp.json` at the plugin root (written by `new`, or by `update` when missing):
|
|
1010
1121
|
|
|
1011
1122
|
```json
|
|
1012
1123
|
{ "mcpServers": { "muscat": { "command": "muscat-pk", "args": ["mcp"] } } }
|
|
1013
1124
|
```
|
|
1014
1125
|
|
|
1015
|
-
Codex — `muscat-pk mcp --install codex
|
|
1126
|
+
Codex — `muscat-pk mcp --install codex` (on Windows it writes `muscat-pk.cmd`), or `~/.codex/config.toml`:
|
|
1016
1127
|
|
|
1017
1128
|
```toml
|
|
1018
1129
|
[mcp_servers.muscat]
|
|
@@ -1033,12 +1144,20 @@ The agent starts the server itself.
|
|
|
1033
1144
|
| `MUSCAT_PK_NO_LOCAL_MUSCAT=1` | don't start the local Muscat runtime |
|
|
1034
1145
|
| `MUSCAT_PK_REBUILD_IMAGE=1` | rebuild Docker images |
|
|
1035
1146
|
| `MUSCAT_PK_RB_SDK_VERSION` | C++ SDK version baked into the C++ images (with `MUSCAT_PK_REBUILD_IMAGE=1`) |
|
|
1147
|
+
| `MUSCAT_PK_NO_BUILD_CACHE=1` | build Docker images with `--no-cache` (CI) |
|
|
1148
|
+
| `MUSCAT_PK_HOST_NETWORK=0` | on native Linux, use the Docker Desktop (bridge) path instead of `--network host` (CI) |
|
|
1149
|
+
| `MUSCAT_PK_RBY1_CPUS` | CPU cap of the rby1 simulator (default `1.5`) |
|
|
1150
|
+
| `MUSCAT_PK_MUSCAT_S3` | where the local Muscat runtime is downloaded from (default `https://rainbow-deploy.s3.ap-northeast-2.amazonaws.com/muscat/main`) |
|
|
1151
|
+
| `MUSCAT_HOST_AGENT_DISABLE=1` | don't start the host Zenoh byte-proxy (macOS / Windows / WSL against a remote robot) |
|
|
1152
|
+
| `MUSCAT_PROXY_PORT` | port of that proxy (default `47100`) |
|
|
1153
|
+
| `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
1154
|
| `MUSCAT_PK_NO_UPDATE_CHECK=1` | skip the update check on `dev` / `dev:ui` |
|
|
1037
1155
|
| `RB_REGISTRY` | npm registry for the update check (default `https://registry.npmjs.org`) |
|
|
1038
1156
|
| `MUSCAT_PK_TAG` | install script channel / version |
|
|
1039
|
-
| `
|
|
1157
|
+
| `CI` | set: `dev:ui` doesn't open the browser |
|
|
1158
|
+
| `NO_COLOR=1` / `FORCE_COLOR` | output colors |
|
|
1040
1159
|
|
|
1041
|
-
Inside the backend container: `RB_PLUGIN_ID` (the plugin / dev id).
|
|
1160
|
+
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
1161
|
|
|
1043
1162
|
---
|
|
1044
1163
|
|
|
@@ -1047,10 +1166,18 @@ Inside the backend container: `RB_PLUGIN_ID` (the plugin / dev id).
|
|
|
1047
1166
|
| Symptom | Fix |
|
|
1048
1167
|
|---|---|
|
|
1049
1168
|
| `manifest.json errors: …` | fix each listed field (section 5) |
|
|
1169
|
+
| `manifest.json variable errors: …` / `manifest.json port errors: …` on `dev` | fix `variables` (5.5) / `ports` (5.6) |
|
|
1170
|
+
| `A Muscat plugin dev environment is already running: …` | answer `y` to stop it, or stop the other `dev` first |
|
|
1171
|
+
| `Docker is installed but the daemon is not running` | start Docker Desktop (or `sudo systemctl start docker`) |
|
|
1172
|
+
| `contributes declares ui "<entry>" but ui/<entry>.tsx … does not exist.` | add the file or fix `contributes[].ui` |
|
|
1173
|
+
| `wheel download failed for <arch>` | a dependency has no cp312 binary wheel for that arch — pick another version or package |
|
|
1174
|
+
| `can't build <arch> on this <host>: Docker has no <arch> emulation.` | `docker run --privileged --rm tonistiigi/binfmt --install <arch>`, or `--arch <host arch>` |
|
|
1175
|
+
| `Permission denied when deleting dist/` | an old build left root-owned files: `sudo rm -rf dist` once |
|
|
1050
1176
|
| backend stops with an undeclared port | add the port (28600-28999) to `ports` |
|
|
1051
1177
|
| 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
1178
|
| `vite not found — UI will not load` | run `npm install` in the project |
|
|
1053
1179
|
| `AttributeError: router` | robot SDK older than 0.0.9.dev5 |
|
|
1180
|
+
| `RBPluginGatewayError` from `plugin.call` | the gateway is disconnected, or no reply within `timeout` (is a UI `rb.serve` registered?) |
|
|
1054
1181
|
| step never finishes | `on_execute` must `await ctx.done()` |
|
|
1055
1182
|
| Stop doesn't stop the plugin | call `await ctx.check_stop()` in every loop |
|
|
1056
1183
|
| `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();
|