@rainbow-robotics/muscat-plugin-kit 0.1.2-dev.46 → 0.1.2-dev.47

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/mcp.mjs CHANGED
@@ -1,7 +1,6 @@
1
- // muscat-pk mcp — an MCP (Model Context Protocol) server that gives an AI
2
- // coding agent (Claude Code, Codex, ...) grounded knowledge of the Muscat
3
- // plugin UI surface, instead of it guessing prop names or grepping
4
- // node_modules for @rainbow-robotics/plugin-ui and @repo/rb-components:
1
+ // muscat-pk mcp — an MCP (Model Context Protocol) server exposing the Muscat
2
+ // plugin UI/SDK surface to an AI coding agent (Claude Code, Codex, ...) so it
3
+ // looks things up instead of guessing. Tools:
5
4
  //
6
5
  // Frontend UI (@rainbow-robotics/plugin-ui/react, via dist/react.d.ts):
7
6
  // - list_components every exported component/hook/type.
@@ -42,11 +41,9 @@ const DESIGN_TOKENS_CSS = existsSync(SHIPPED_DESIGN_TOKENS)
42
41
 
43
42
  let typesCache = null;
44
43
 
45
- // Resolves @rainbow-robotics/plugin-ui/react's bundled, self-contained
46
- // dist/react.d.ts (see plugin-ui/build-dist.mjs — rb-components' own types are
47
- // inlined into it, so this single file is enough; no need to resolve
48
- // @repo/rb-components separately, which wouldn't exist in an external install
49
- // anyway).
44
+ // Parse plugin-ui/react's dist/react.d.ts. It's self-contained (rb-components
45
+ // types are inlined at build — see plugin-ui/build-dist.mjs), so this one file
46
+ // is enough even in an external install.
50
47
  async function loadReactTypes() {
51
48
  if (typesCache) return typesCache;
52
49
 
@@ -113,9 +110,7 @@ function findStorySource(name) {
113
110
  /* README search */
114
111
  /* -------------------------------------------------------------------------- */
115
112
 
116
- // Splits the plugin-author README on headings and keyword-scores each
117
- // section — good enough for short-query lookups ("save form data", "toast",
118
- // "manifest ui") without pulling in a real search dependency.
113
+ // Split the README on headings and keyword-score each section.
119
114
  function searchReadme(query) {
120
115
  if (!existsSync(TEMPLATE_README)) return [];
121
116
  const text = readFileSync(TEMPLATE_README, "utf8");
@@ -200,9 +195,8 @@ function pythonCmd() {
200
195
  return null;
201
196
  }
202
197
 
203
- // Runs a short introspection script against the INSTALLED rainbow.rb_sdk. It's
204
- // the plugin's own backend dependency (requirements.txt / `muscat-pk sdk`), so
205
- // in a real plugin env it's importable; otherwise we return a clear hint.
198
+ // Run an introspection script against the installed rainbow.rb_sdk (a plugin's
199
+ // backend dependency). Returns a clear hint if it isn't importable.
206
200
  function introspectRbSdk(script) {
207
201
  const py = pythonCmd();
208
202
  if (!py) {
@@ -478,20 +472,66 @@ print(json.dumps(out))
478
472
  description:
479
473
  "Get the signature(s) and docstring for a rainbow.rb_sdk class or function (e.g. " +
480
474
  "'PluginProgramContext', 'RBManipulateSDK'). For a class, lists its public methods with " +
481
- "signatures. Requires rainbow-rb-sdk to be pip-installed.",
482
- inputSchema: { name: z.string().describe('e.g. "PluginProgramContext"') },
475
+ "signatures. To drill into ONE method of a class (e.g. RBManipulateSDK.call_move_j), pass " +
476
+ "`method` alongside `name` — do not put both in `name`. Requires rainbow-rb-sdk to be " +
477
+ "pip-installed.",
478
+ inputSchema: {
479
+ name: z.string().describe('Class or function name, e.g. "RBManipulateSDK"'),
480
+ method: z.string().optional().describe('Optional: a specific method on that class, e.g. "call_move_j"'),
481
+ },
483
482
  },
484
- async ({ name }) => {
483
+ async ({ name, method }) => {
484
+ // Tolerate a combined "ClassName method_name" in `name` (a common agent
485
+ // mistake) by splitting it ourselves instead of failing validation.
486
+ let className = name;
487
+ let methodName = method;
488
+ if (!methodName && /\s/.test(name.trim())) {
489
+ const parts = name.trim().split(/\s+/);
490
+ if (parts.length === 2) [className, methodName] = parts;
491
+ }
492
+
485
493
  const { data, error } = introspectRbSdk(`
486
494
  import inspect, json
487
495
  mod = importlib.import_module("rainbow.rb_sdk")
488
- name = ${JSON.stringify(name)}
496
+ name = ${JSON.stringify(className)}
497
+ method = ${JSON.stringify(methodName ?? null)}
489
498
  obj = getattr(mod, name, None)
490
499
  if obj is None:
491
500
  print(json.dumps({"__missing__": True})); raise SystemExit(0)
492
501
  def sig(o):
493
502
  try: return str(inspect.signature(o))
494
503
  except Exception: return "(...)"
504
+
505
+ if method:
506
+ # Direct method on the class itself.
507
+ fn = getattr(obj, method, None) if inspect.isclass(obj) else None
508
+ # rb_sdk classes often group calls under a nested namespace set in
509
+ # __init__ (e.g. RBManipulateSDK().move.call_move_j) — that only exists on
510
+ # an INSTANCE, which we won't construct (side effects, needs a robot
511
+ # connection). Best-effort: search the class's own __init__ source text
512
+ # for self.<attr> = <NestedClass>(...) and check each nested class too.
513
+ owner = name
514
+ if fn is None and inspect.isclass(obj):
515
+ try:
516
+ src = inspect.getsource(obj.__init__)
517
+ except Exception:
518
+ src = ""
519
+ import re
520
+ for attr, nested_name in re.findall(r"self\\.(\\w+)\\s*=\\s*(\\w+)\\(", src):
521
+ nested = mod.__dict__.get(nested_name) or globals().get(nested_name)
522
+ cand = getattr(nested, method, None) if nested else None
523
+ if cand:
524
+ fn = cand
525
+ owner = f"{name}.{attr} ({nested_name})"
526
+ break
527
+ if fn is None:
528
+ print(json.dumps({"__missing__": True, "__method__": True})); raise SystemExit(0)
529
+ print(json.dumps({
530
+ "name": name, "kind": "method", "owner": owner,
531
+ "signature": method + sig(fn), "doc": inspect.getdoc(fn) or "",
532
+ }))
533
+ raise SystemExit(0)
534
+
495
535
  res = {"name": name, "doc": inspect.getdoc(obj) or ""}
496
536
  if inspect.isclass(obj):
497
537
  res["kind"] = "class"
@@ -508,12 +548,17 @@ print(json.dumps(res))
508
548
  `);
509
549
  if (error) return { isError: true, content: [{ type: "text", text: error }] };
510
550
  if (data.__missing__) {
511
- return {
512
- isError: true,
513
- content: [
514
- { type: "text", text: `No "${name}" in rainbow.rb_sdk. Call list_rb_sdk for valid names.` },
515
- ],
516
- };
551
+ const text = data.__method__
552
+ ? `No method "${methodName}" found directly on "${className}" (nor in an obvious nested ` +
553
+ `namespace set in __init__). Call get_rb_sdk_api with just \`name: "${className}"\` to see ` +
554
+ `its full method list and constructor first.`
555
+ : `No "${className}" in rainbow.rb_sdk. Call list_rb_sdk for valid names.`;
556
+ return { isError: true, content: [{ type: "text", text }] };
557
+ }
558
+ if (data.kind === "method") {
559
+ const lines = [`# ${data.owner}.${data.signature.split("(")[0]}`, data.signature];
560
+ if (data.doc) lines.push("", data.doc);
561
+ return { content: [{ type: "text", text: lines.join("\n") }] };
517
562
  }
518
563
  const lines = [`# ${data.name} (${data.kind})`];
519
564
  if (data.doc) lines.push(data.doc, "");
@@ -46,12 +46,11 @@ import { setHostServices } from "@rainbow-robotics/plugin-ui/host-ui";
46
46
  import { RbWs, createLazyRbSdk } from "@rainbow-robotics/plugin-ui/ws";
47
47
  import App from ${entry};
48
48
 
49
- // HMR only hands the new module to the accept callback — it does NOT rebind the
50
- // static \`App\` import above. So we track the latest component at MODULE scope.
51
- // Without this, navigating away and back (a fresh mount() call on this cached
52
- // bootstrap module) would render the STALE component captured at first import;
53
- // only a hard refresh fixed it. \`activeRender\` points at the currently-mounted
54
- // root so live edits re-render it in place.
49
+ // HMR passes the new module to the accept callback but doesn't rebind the
50
+ // static \`App\` import, so we track the latest at module scope. Without this,
51
+ // navigating away and back (a fresh mount() on this cached module) renders the
52
+ // stale App from first import. \`activeRender\` points at the mounted root so
53
+ // live edits re-render in place.
55
54
  let latestApp = App;
56
55
  let activeRender = null;
57
56
  if (import.meta.hot) {
package/cli/src/util.mjs CHANGED
@@ -40,12 +40,9 @@ export function venvPython(venvDir) {
40
40
  }
41
41
 
42
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.
43
+ * Create/refresh a local `.venv` with requirements.txt installed, for the
44
+ * editor/linter only (the backend actually runs in Docker). Uses `uv` if
45
+ * present, else the system Python. Returns true if the venv is ready.
49
46
  */
50
47
  export function ensureIdeVenv(root) {
51
48
  const venvDir = resolve(root, ".venv");
@@ -1 +1 @@
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}}}
1
+ {"generatedAt":1783624567446,"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}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainbow-robotics/muscat-plugin-kit",
3
- "version": "0.1.2-dev.46",
3
+ "version": "0.1.2-dev.47",
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.46",
57
- "@rainbow-robotics/preview-host": "0.1.2-dev.46",
56
+ "@rainbow-robotics/plugin-ui": "0.1.2-dev.47",
57
+ "@rainbow-robotics/preview-host": "0.1.2-dev.47",
58
58
  "postcss": "^8.5.8",
59
59
  "socket.io": "^4.8.1",
60
60
  "typescript": "^5.8.0",