@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.45 → 0.1.2-dev.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/src/devBack.mjs +17 -37
- package/cli/src/mcp.mjs +15 -4
- package/cli/src/new.mjs +16 -1
- package/cli/src/util.mjs +29 -0
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/package.json +2 -0
- package/package.json +3 -3
package/cli/src/devBack.mjs
CHANGED
|
@@ -10,15 +10,7 @@
|
|
|
10
10
|
import { spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import { existsSync, watch } from "node:fs";
|
|
12
12
|
import { resolve } from "node:path";
|
|
13
|
-
import {
|
|
14
|
-
detectPythonCmd,
|
|
15
|
-
ensureDocker,
|
|
16
|
-
findPluginRoot,
|
|
17
|
-
hasBin,
|
|
18
|
-
log,
|
|
19
|
-
promptRobotConnection,
|
|
20
|
-
venvPython,
|
|
21
|
-
} from "./util.mjs";
|
|
13
|
+
import { ensureDocker, ensureIdeVenv, findPluginRoot, log, promptRobotConnection } from "./util.mjs";
|
|
22
14
|
import { assertManifestPorts, manifestRuntime, readManifest, validateManifest } from "./manifest.mjs";
|
|
23
15
|
import {
|
|
24
16
|
formatPortRanges,
|
|
@@ -43,33 +35,16 @@ import {
|
|
|
43
35
|
} from "./cpp.mjs";
|
|
44
36
|
|
|
45
37
|
// ── IDE tooling venv (best-effort, non-blocking) ────────────────────────────
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
// Docker works fine with zero local Python.
|
|
38
|
+
// `muscat-pk new` already creates this up front (see new.mjs); this just
|
|
39
|
+
// refreshes it on every `dev`/`dev:back` in case requirements.txt changed.
|
|
40
|
+
// Docker remains the actual execution environment — this never affects it.
|
|
41
|
+
// Runs in the background so it never delays `dev:back` starting, and any
|
|
42
|
+
// failure here (no local Python toolchain, offline, ...) is silent.
|
|
52
43
|
function syncIdeVenv(root) {
|
|
53
|
-
const venvDir = resolve(root, ".venv");
|
|
54
|
-
const py = venvPython(venvDir);
|
|
55
|
-
|
|
56
44
|
setImmediate(() => {
|
|
57
45
|
try {
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
if (hasBin("uv")) {
|
|
61
|
-
created = spawnSync("uv", ["venv", venvDir, "--python", "3.12", "--seed"], { cwd: root, stdio: "ignore" });
|
|
62
|
-
} else {
|
|
63
|
-
const { cmd } = detectPythonCmd();
|
|
64
|
-
created = spawnSync(cmd[0], [...cmd.slice(1), "-m", "venv", venvDir], { cwd: root, stdio: "ignore" });
|
|
65
|
-
}
|
|
66
|
-
if (created.status !== 0 || !existsSync(py)) return;
|
|
67
|
-
}
|
|
68
|
-
const reqs = resolve(root, "requirements.txt");
|
|
69
|
-
const args = ["-m", "pip", "install", "-q"];
|
|
70
|
-
if (existsSync(reqs)) args.push("-r", reqs);
|
|
71
|
-
if (spawnSync(py, args, { cwd: root, stdio: "ignore" }).status === 0) {
|
|
72
|
-
log.info(`IDE venv ready → .venv (point your editor's Python interpreter there for pylint/pyright)`);
|
|
46
|
+
if (ensureIdeVenv(root)) {
|
|
47
|
+
log.info("IDE venv ready → .venv (point your editor's Python interpreter there for pylint/pyright)");
|
|
73
48
|
}
|
|
74
49
|
} catch {
|
|
75
50
|
/* best-effort only */
|
|
@@ -82,9 +57,11 @@ function syncIdeVenv(root) {
|
|
|
82
57
|
// Under `muscat-pk dev`, it shares the bridge container's netns, so the SDK
|
|
83
58
|
// reaches the Socket.IO bridge at the default localhost:10000.
|
|
84
59
|
async function runPython(root, portRanges) {
|
|
60
|
+
// Independent of Docker readiness — must still happen if Docker isn't
|
|
61
|
+
// installed/running, since that's exactly when local IDE tooling matters most.
|
|
62
|
+
syncIdeVenv(root);
|
|
85
63
|
pyAssertDockerReady();
|
|
86
64
|
await pyEnsureImage();
|
|
87
|
-
syncIdeVenv(root);
|
|
88
65
|
|
|
89
66
|
const manifest = readManifest(root);
|
|
90
67
|
const pluginId = manifest.id ?? "";
|
|
@@ -154,16 +131,19 @@ async function runPython(root, portRanges) {
|
|
|
154
131
|
|
|
155
132
|
// ── ros2 ───────────────────────────────────────────────────────────────────────
|
|
156
133
|
async function runRos2(root, portRanges) {
|
|
134
|
+
const manifest = readManifest(root);
|
|
135
|
+
const isRclcpp = manifest.runtime?.rosClient === "rclcpp";
|
|
136
|
+
// Independent of Docker readiness — must still happen if Docker isn't
|
|
137
|
+
// installed/running, since that's exactly when local IDE tooling matters most.
|
|
138
|
+
if (!isRclcpp) syncIdeVenv(root); // rclpy is plain Python — rclcpp is C++, no venv needed
|
|
139
|
+
|
|
157
140
|
assertDockerReady();
|
|
158
141
|
const pkg = ros2PackageName(root);
|
|
159
142
|
if (!pkg) throw new Error("no ROS2 package found under ros2_ws/src.");
|
|
160
143
|
|
|
161
144
|
await ensureImage();
|
|
162
145
|
|
|
163
|
-
const manifest = readManifest(root);
|
|
164
146
|
const pluginId = manifest.id ?? "";
|
|
165
|
-
const isRclcpp = manifest.runtime?.rosClient === "rclcpp";
|
|
166
|
-
if (!isRclcpp) syncIdeVenv(root); // rclpy is plain Python — rclcpp is C++, no venv needed
|
|
167
147
|
const container = `${ROS2_DEV_IMAGE}-run`;
|
|
168
148
|
const robotIp = process.env.MUSCAT_ROBOT_HOST ?? "";
|
|
169
149
|
const robotToken = process.env.MUSCAT_ROBOT_TOKEN ?? "";
|
package/cli/src/mcp.mjs
CHANGED
|
@@ -305,16 +305,27 @@ export async function runMcp() {
|
|
|
305
305
|
"Get the real Storybook story source for a component (e.g. RBSlider) — actual working " +
|
|
306
306
|
"props, imports, and layout, not a guessed shape. Prefer this over get_component_props when " +
|
|
307
307
|
"you just need to know how to use a component, not its full type.",
|
|
308
|
-
|
|
308
|
+
// `component` accepted as an alias — agents often guess it over `name`.
|
|
309
|
+
inputSchema: {
|
|
310
|
+
name: z.string().optional().describe('Component name, e.g. "RBSlider"'),
|
|
311
|
+
component: z.string().optional().describe("Alias for `name`."),
|
|
312
|
+
},
|
|
309
313
|
},
|
|
310
|
-
async ({ name }) => {
|
|
311
|
-
const
|
|
314
|
+
async ({ name, component }) => {
|
|
315
|
+
const target = name ?? component;
|
|
316
|
+
if (!target) {
|
|
317
|
+
return {
|
|
318
|
+
isError: true,
|
|
319
|
+
content: [{ type: "text", text: 'Missing required argument: "name" (component name, e.g. "RBSlider").' }],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const source = findStorySource(target);
|
|
312
323
|
if (!source) {
|
|
313
324
|
return {
|
|
314
325
|
content: [
|
|
315
326
|
{
|
|
316
327
|
type: "text",
|
|
317
|
-
text: `No Storybook example found for "${
|
|
328
|
+
text: `No Storybook example found for "${target}". Run "muscat-pk storybook" to browse all components live, or use get_component_props for its type.`,
|
|
318
329
|
},
|
|
319
330
|
],
|
|
320
331
|
};
|
package/cli/src/new.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
writeFileSync,
|
|
13
13
|
} from "node:fs";
|
|
14
14
|
import { basename, dirname, resolve } from "node:path";
|
|
15
|
-
import { hasBin, kitVersion, parseArgs, prompt, templatesDir, log } from "./util.mjs";
|
|
15
|
+
import { ensureIdeVenv, hasBin, kitVersion, parseArgs, prompt, templatesDir, log } from "./util.mjs";
|
|
16
16
|
|
|
17
17
|
// Like cpSync(src, dst, {recursive:true}) but reads from the embedded snapshot
|
|
18
18
|
// when the templates dir doesn't exist on disk (compiled binary mode).
|
|
@@ -227,6 +227,21 @@ export async function runNew(args) {
|
|
|
227
227
|
} catch {
|
|
228
228
|
log.warn(`'${pm} install' failed; run it manually in ${targetName}`);
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
// Local .venv is for your editor/linter (pylint, pyright, ...) only — the
|
|
232
|
+
// backend actually RUNS in Docker (see README), this never affects that.
|
|
233
|
+
// rclcpp is C++, so no venv applies there.
|
|
234
|
+
const needsIdeVenv =
|
|
235
|
+
runtimeChoice.type === "python" ||
|
|
236
|
+
(runtimeChoice.type === "ros2" && runtimeChoice.rosClient === "rclpy");
|
|
237
|
+
if (needsIdeVenv) {
|
|
238
|
+
log.step("creating .venv for your editor (pylint/pyright)…");
|
|
239
|
+
if (ensureIdeVenv(target)) {
|
|
240
|
+
log.ok(".venv ready — point your editor's Python interpreter there.");
|
|
241
|
+
} else {
|
|
242
|
+
log.warn("Could not create .venv (no local Python/uv?) — skipping, Docker dev still works.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
230
245
|
}
|
|
231
246
|
|
|
232
247
|
// The back end (python / cpp / ros2) runs in a Docker container that matches
|
package/cli/src/util.mjs
CHANGED
|
@@ -39,6 +39,35 @@ export function venvPython(venvDir) {
|
|
|
39
39
|
: resolve(venvDir, "bin", "python");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Create/refresh a LOCAL `.venv` with the plugin's requirements.txt installed —
|
|
44
|
+
* purely so an editor/linter (pylint, pyright, mypy, ...) can resolve imports.
|
|
45
|
+
* Docker remains the actual execution environment for `dev`/`dev:back`; this
|
|
46
|
+
* never affects it. Uses `uv` if available (fast, pins Python 3.12 — matches
|
|
47
|
+
* the robot), else falls back to the detected system Python.
|
|
48
|
+
* Returns true if the venv is ready with deps installed.
|
|
49
|
+
*/
|
|
50
|
+
export function ensureIdeVenv(root) {
|
|
51
|
+
const venvDir = resolve(root, ".venv");
|
|
52
|
+
const py = venvPython(venvDir);
|
|
53
|
+
|
|
54
|
+
if (!existsSync(py)) {
|
|
55
|
+
let created;
|
|
56
|
+
if (hasBin("uv")) {
|
|
57
|
+
created = spawnSync("uv", ["venv", venvDir, "--python", "3.12", "--seed"], { cwd: root, stdio: "ignore" });
|
|
58
|
+
} else {
|
|
59
|
+
const { cmd } = detectPythonCmd();
|
|
60
|
+
created = spawnSync(cmd[0], [...cmd.slice(1), "-m", "venv", venvDir], { cwd: root, stdio: "ignore" });
|
|
61
|
+
}
|
|
62
|
+
if (created.status !== 0 || !existsSync(py)) return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const reqs = resolve(root, "requirements.txt");
|
|
66
|
+
const args = ["-m", "pip", "install", "-q"];
|
|
67
|
+
if (existsSync(reqs)) args.push("-r", reqs);
|
|
68
|
+
return spawnSync(py, args, { cwd: root, stdio: "ignore" }).status === 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
42
71
|
/** Published install/CLI package name. Author imports use @rainbow-robotics/plugin-ui separately. */
|
|
43
72
|
export const KIT_PUBLISHED_NAME = "@rainbow-robotics/muscat-plugin-kit";
|
|
44
73
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"generatedAt":
|
|
1
|
+
{"generatedAt":1783623695483,"userSince":1750932757926,"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":false,"hasStorybookEslint":true,"refCount":0,"testPackages":{"@vitest/browser":"3.2.4","@vitest/coverage-v8":"3.2.4","playwright":"1.59.1","vitest":"3.2.4"},"hasRouterPackage":false,"monorepo":"Turborepo","packageManager":{"type":"yarn","version":"berry","agent":"yarn@berry"},"preview":{"usesGlobals":false},"framework":{"name":"@storybook/react-vite","options":{}},"builder":"@storybook/builder-vite","renderer":"@storybook/react","portableStoriesFileCount":0,"applicationFileCount":0,"storybookVersion":"8.6.18","storybookVersionSpecifier":"^8.6.4","language":"typescript","storybookPackages":{"@chromatic-com/storybook":{"version":"3.2.7"},"@storybook/addon-essentials":{"version":"8.6.18"},"@storybook/addon-links":{"version":"8.6.18"},"@storybook/addon-onboarding":{"version":"8.6.18"},"@storybook/addon-themes":{"version":"8.6.18"},"@storybook/blocks":{"version":"8.6.18"},"@storybook/experimental-addon-test":{"version":"8.6.18"},"@storybook/react":{"version":"8.6.18"},"@storybook/react-vite":{"version":"8.6.18"},"@storybook/test":{"version":"8.6.18"},"@storybook/theming":{"version":"8.6.18"},"eslint-plugin-storybook":{"version":"0.11.6"},"storybook":{"version":"8.6.18"},"storybook-addon-react-docgen":{"version":"1.2.44"}},"addons":{"$SNIP/node_modules/@storybook/addon-links":{"version":null},"$SNIP/node_modules/@storybook/addon-essentials":{"version":null},"$SNIP/node_modules/@chromatic-com/storybook":{"version":null},"$SNIP/node_modules/@storybook/addon-themes":{"version":null},"$SNIP/node_modules/@storybook/experimental-addon-test":{"version":null}}}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"@tailwindcss/vite": "^4.1.0",
|
|
11
11
|
"@rainbow-robotics/muscat-plugin-kit": "__RB_VERSION__",
|
|
12
12
|
"@rainbow-robotics/preview-host": "__RB_VERSION__",
|
|
13
|
+
"@types/react": "^19.0.10",
|
|
14
|
+
"@types/react-dom": "^19.0.4",
|
|
13
15
|
"@vitejs/plugin-react": "^4.0.0",
|
|
14
16
|
"tailwindcss": "^4.1.0",
|
|
15
17
|
"typescript": "^5.8.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainbow-robotics/muscat-plugin-kit",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.46",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
56
|
-
"@rainbow-robotics/plugin-ui": "0.1.2-dev.
|
|
57
|
-
"@rainbow-robotics/preview-host": "0.1.2-dev.
|
|
56
|
+
"@rainbow-robotics/plugin-ui": "0.1.2-dev.46",
|
|
57
|
+
"@rainbow-robotics/preview-host": "0.1.2-dev.46",
|
|
58
58
|
"postcss": "^8.5.8",
|
|
59
59
|
"socket.io": "^4.8.1",
|
|
60
60
|
"typescript": "^5.8.0",
|