@rainbow-robotics/muscat-plugin-kit 1.3.6-dev.6 → 1.3.6-dev.8
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/README.md +3 -8
- package/cli/bin/muscat-pk.mjs +0 -5
- package/cli/bundle-templates.mjs +1 -6
- package/cli/docker/Dockerfile.cpp +6 -28
- package/cli/docker/Dockerfile.dev +0 -32
- package/cli/docker/Dockerfile.python +0 -19
- package/cli/src/bridge.mjs +12 -45
- package/cli/src/build.mjs +79 -48
- package/cli/src/cpp.mjs +5 -11
- package/cli/src/dev.mjs +2 -7
- package/cli/src/devBack.mjs +40 -97
- package/cli/src/host_agent.py +83 -143
- package/cli/src/manifest.mjs +0 -11
- package/cli/src/mcp.mjs +54 -398
- package/cli/src/new.mjs +3 -13
- package/cli/src/ports.mjs +7 -21
- package/cli/src/python.mjs +20 -16
- package/cli/src/ros2.mjs +8 -16
- package/cli/src/sdk.mjs +21 -23
- package/cli/src/templates-embedded.mjs +4 -0
- package/cli/src/type.mjs +10 -44
- package/cli/src/uninstall.mjs +20 -3
- package/cli/src/update.mjs +8 -14
- package/cli/src/util.mjs +9 -70
- package/cli/storybook-dist/_stories-src/RBDatePicker/RBRangeDateInput.stories.tsx +1 -13
- package/cli/storybook-dist/_stories-src/RBInput/RBSelectBox.stories.tsx +1 -98
- package/cli/storybook-dist/assets/{Color-YHDXOIA2-C5ttlJWI.js → Color-YHDXOIA2-Du8dJ-nM.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CCfiEyzU.js → Docs--Fm8069p.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-B3AV-YvP.js → Docs-Cb72AJnj.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BN206lKl.js → Docs-LdjYwuG4.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-BqH2prGO.js → Docs-Sy5RDbtH.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-DhncK7Aw.js → Docs-guGflRzZ.js} +1 -1
- package/cli/storybook-dist/assets/{Docs-CGN1_Ua8.js → Docs-pwGqGTV_.js} +1 -1
- package/cli/storybook-dist/assets/{DocsRenderer-CFRXHY34-D-w8Kq64.js → DocsRenderer-CFRXHY34-YGBLQ4BD.js} +1 -1
- package/cli/storybook-dist/assets/{NxAvatar.stories-oCcYtLwq.js → NxAvatar.stories-C1d4n8cq.js} +1 -1
- package/cli/storybook-dist/assets/{NxButton.stories-C5jUScIj.js → NxButton.stories-gCBoZdMq.js} +1 -1
- package/cli/storybook-dist/assets/{NxIcons.stories-DeqrVzVs.js → NxIcons.stories-Dg42riT5.js} +1 -1
- package/cli/storybook-dist/assets/{NxNumberInput.stories-gjsEulIN.js → NxNumberInput.stories-DB-vLM2J.js} +1 -1
- package/cli/storybook-dist/assets/{NxPagination.stories-BK6uSWIa.js → NxPagination.stories-CS8iMIak.js} +1 -1
- package/cli/storybook-dist/assets/{NxPointerHoldButton.stories-BDD5CvSZ.js → NxPointerHoldButton.stories-D66BopeS.js} +1 -1
- package/cli/storybook-dist/assets/{NxRangeInput.stories-C_nW26_x.js → NxRangeInput.stories-CZxddsJ2.js} +3 -3
- package/cli/storybook-dist/assets/{NxSelectBox.stories-DqLfKU9H.js → NxSelectBox.stories-CmezGGrm.js} +1 -1
- package/cli/storybook-dist/assets/{NxSlideRange.stories-CQjHA8yv.js → NxSlideRange.stories-CGAFMITi.js} +1 -1
- package/cli/storybook-dist/assets/{NxSwitch.stories-BMHCiPoL.js → NxSwitch.stories-B3QInkXX.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextArea.stories-k7r1WVQh.js → NxTextArea.stories-BgJcgZNe.js} +1 -1
- package/cli/storybook-dist/assets/{NxTextInput.stories-Cqfe1I32.js → NxTextInput.stories-CwMQwI9L.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordion.stories-CJ3Ttuja.js → RBAccordion.stories-BuBVNzJT.js} +1 -1
- package/cli/storybook-dist/assets/{RBAccordionGroup.stories-BThv7tM0.js → RBAccordionGroup.stories-Crdb6kFF.js} +1 -1
- package/cli/storybook-dist/assets/{RBAlert.stories-CSMP8Vun.js → RBAlert.stories-CqXgUvA2.js} +1 -1
- package/cli/storybook-dist/assets/{RBBadge.stories-cBMA2R6F.js → RBBadge.stories-BCw_ksML.js} +1 -1
- package/cli/storybook-dist/assets/{RBBattery.stories-BKVOskqs.js → RBBattery.stories-0id4XYoc.js} +1 -1
- package/cli/storybook-dist/assets/{RBCard.stories-BTOfK0HC.js → RBCard.stories-CgpG-7Lk.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckBox.stories-DyE912wG.js → RBCheckBox.stories-BG02BoGX.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckIconButton.stories-0sLFU0kM.js → RBCheckIconButton.stories-vrjD4GQ4.js} +1 -1
- package/cli/storybook-dist/assets/{RBCheckLabelButton.stories-c8VecClE.js → RBCheckLabelButton.stories-POba8lU-.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipTab.stories-CQ9Uav5_.js → RBChipTab.stories-XWJbFrHF.js} +1 -1
- package/cli/storybook-dist/assets/{RBChipsBoxTab.stories-Ap4NlhAZ.js → RBChipsBoxTab.stories-BdU0Dzdp.js} +1 -1
- package/cli/storybook-dist/assets/{RBColorIcons.stories-BPPXjif2.js → RBColorIcons.stories-C_0dCNXG.js} +1 -1
- package/cli/storybook-dist/assets/{RBCommand.stories-PDpYOKlm.js → RBCommand.stories-Dvrko5No.js} +1 -1
- package/cli/storybook-dist/assets/{RBConfirm.stories-BEWvMAYP.js → RBConfirm.stories-CkTn0sHa.js} +1 -1
- package/cli/storybook-dist/assets/{RBDateInput.stories-DLdyNnVF.js → RBDateInput.stories-BH3D08cs.js} +1 -1
- package/cli/storybook-dist/assets/{RBDialog.stories-HsawMNOb.js → RBDialog.stories-C2nNX9fl.js} +1 -1
- package/cli/storybook-dist/assets/{RBDivider.stories-dlWwOxik.js → RBDivider.stories-D-GrJ0QZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBDrawer.stories-C87iqGk0.js → RBDrawer.stories-XLOnLOnh.js} +1 -1
- package/cli/storybook-dist/assets/{RBDropdownMenu.stories-B8EQhA0z.js → RBDropdownMenu.stories-BjBSeoV2.js} +1 -1
- package/cli/storybook-dist/assets/{RBEmptyView.stories-SzpIhNDw.js → RBEmptyView.stories-C6U2zKNE.js} +1 -1
- package/cli/storybook-dist/assets/{RBIconButton.stories-BQ0nM1gb.js → RBIconButton.stories-MCJq5Hta.js} +1 -1
- package/cli/storybook-dist/assets/{RBIcons.stories-C19g5M5z.js → RBIcons.stories-DxOCiCe0.js} +1 -1
- package/cli/storybook-dist/assets/{RBInlineLabelButton.stories-DbgI4-1P.js → RBInlineLabelButton.stories-BVZi1aEs.js} +1 -1
- package/cli/storybook-dist/assets/{RBJoyStick.stories-smF-vpGL.js → RBJoyStick.stories-BDThbEek.js} +1 -1
- package/cli/storybook-dist/assets/{RBLabelButton.stories-s53klwDv.js → RBLabelButton.stories-CaPEKUS6.js} +1 -1
- package/cli/storybook-dist/assets/{RBLight.stories-BnzkoR51.js → RBLight.stories-CihQzyEL.js} +1 -1
- package/cli/storybook-dist/assets/{RBMenubar.stories-CW014klu.js → RBMenubar.stories-J852XVFh.js} +1 -1
- package/cli/storybook-dist/assets/{RBMultiRangeInput.stories-qa4tH_4Y.js → RBMultiRangeInput.stories-DXyX0cOZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBPagination.stories-YoQNUpQH.js → RBPagination.stories-C9_49ymO.js} +1 -1
- package/cli/storybook-dist/assets/{RBPopover.stories-Bhpi0OaH.js → RBPopover.stories-DxLE912-.js} +1 -1
- package/cli/storybook-dist/assets/{RBPrompt.stories-iQM5iAka.js → RBPrompt.stories-DlsfGqWb.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioBox.stories-BGyq4rzx.js → RBRadioBox.stories-hb5cNWW1.js} +1 -1
- package/cli/storybook-dist/assets/{RBRadioButton.stories-DF6BcUcJ.js → RBRadioButton.stories-CSuOgeN-.js} +1 -1
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-KRg2glX8.js +15 -0
- package/cli/storybook-dist/assets/{RBResizable.stories-DSVHvwjp.js → RBResizable.stories-BetjF9vG.js} +1 -1
- package/cli/storybook-dist/assets/RBSelectBox.stories-ILLQQ8M8.js +6 -0
- package/cli/storybook-dist/assets/{RBSheet.stories-CAK9hb-p.js → RBSheet.stories-D3QbcMKq.js} +1 -1
- package/cli/storybook-dist/assets/{RBSideNumberInput.stories-DUymnH-9.js → RBSideNumberInput.stories-DcTh0IjM.js} +1 -1
- package/cli/storybook-dist/assets/{RBSlider.stories-BwlDh9--.js → RBSlider.stories-B59We7ei.js} +1 -1
- package/cli/storybook-dist/assets/{RBSpinner.stories-VOlkuP6t.js → RBSpinner.stories-9u3_ETbi.js} +1 -1
- package/cli/storybook-dist/assets/{RBSwitch.stories-BiW2dQnT.js → RBSwitch.stories-DrVm1WGX.js} +1 -1
- package/cli/storybook-dist/assets/{RBTab.stories-DFKVsy0C.js → RBTab.stories-D8Dm7cRW.js} +1 -1
- package/cli/storybook-dist/assets/{RBTable.stories-BWG0ZSYI.js → RBTable.stories-dRdZ8g1j.js} +1 -1
- package/cli/storybook-dist/assets/{RBTableRow.stories-JVTD83Mh.js → RBTableRow.stories-rZeoGwHZ.js} +1 -1
- package/cli/storybook-dist/assets/{RBTabs.stories-D-aN385u.js → RBTabs.stories-STRV2lgO.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextArea.stories-Dx5xkmZS.js → RBTextArea.stories-CfgUbrbJ.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextField.stories-DmVciExd.js → RBTextField.stories-B5t8-Y97.js} +1 -1
- package/cli/storybook-dist/assets/{RBTextInput.stories-CCf0VGEv.js → RBTextInput.stories-Bf1edN6l.js} +1 -1
- package/cli/storybook-dist/assets/{RBToast.stories-DT0DGn05.js → RBToast.stories-Cnm8JwQ6.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggle.stories-Ce_q3EtP.js → RBToggle.stories-B0OCo6zd.js} +1 -1
- package/cli/storybook-dist/assets/{RBToggleGroup.stories-CN40YHsR.js → RBToggleGroup.stories-GdYO2Fxg.js} +1 -1
- package/cli/storybook-dist/assets/{RBTooltip.stories-L7btDEpA.js → RBTooltip.stories-DXJTCScO.js} +1 -1
- package/cli/storybook-dist/assets/SideNumberInput-CrF-a1Nr.js +1114 -0
- package/cli/storybook-dist/assets/browserUtils-CxJ5gea3.js +1 -0
- package/cli/storybook-dist/assets/{entry-preview-D_nGAXUk.js → entry-preview-DSRwIaVJ.js} +1 -1
- package/cli/storybook-dist/assets/{iframe-23VS_ZeI.js → iframe-Cj53Wn3d.js} +2 -2
- package/cli/storybook-dist/assets/{index-ClAc6FBW.js → index-C1OzuK6-.js} +1 -1
- package/cli/storybook-dist/assets/{index-Ckae_HWe.js → index-CkHtLEe8.js} +3 -3
- package/cli/storybook-dist/assets/preview-CEfMaO1t.css +1 -0
- package/cli/storybook-dist/assets/{preview-BVJwlGUF.js → preview-CILRoSnB.js} +1 -1
- package/cli/storybook-dist/assets/{preview-CD0LINug.js → preview-CgOtgtCD.js} +2 -2
- package/cli/storybook-dist/assets/preview-D_2um8Sd.js +3 -0
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CtORL3tG.js +102 -0
- package/cli/storybook-dist/iframe.html +1 -1
- package/cli/storybook-dist/index.html +1 -1
- package/cli/storybook-dist/index.json +1 -1
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +7 -211
- package/cli/templates/common/py/main.py +2 -29
- package/cli/templates/common/requirements.txt +3 -3
- package/cli/templates/cpp/cpp/CMakeLists.txt +7 -6
- package/cli/templates/cpp/cpp/main.cpp +10 -57
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/CMakeLists.txt +0 -4
- package/cli/templates/ros2-cpp/ros2_ws/src/__PLUGIN_SLUG__/src/node.cpp +4 -5
- package/package.json +3 -3
- package/cli/storybook-dist/assets/RBRangeDateInput.stories-COOQ_HNF.js +0 -15
- package/cli/storybook-dist/assets/RBSelectBox.stories-BookV8Sm.js +0 -74
- package/cli/storybook-dist/assets/SideNumberInput-B5_xLK8z.js +0 -1109
- package/cli/storybook-dist/assets/browserUtils-CQM9Za7e.js +0 -1
- package/cli/storybook-dist/assets/preview-P8yWWsME.js +0 -3
- package/cli/storybook-dist/assets/preview-zuAvMksL.css +0 -1
- package/cli/storybook-dist/assets/react-resizable-panels.browser-CYcsrUgg.js +0 -120
- package/cli/templates/readme-cpp/README.md +0 -993
package/cli/src/mcp.mjs
CHANGED
|
@@ -13,9 +13,7 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Plugin SDK docs + Python backend:
|
|
15
15
|
// - search_rb_sdk search the plugin-author README (useRb(), rb.*, manifest, runtimes).
|
|
16
|
-
// - list_rb_sdk classes/functions of the installed rainbow.rb_sdk
|
|
17
|
-
// (lang: "python" | "cpp" — cpp reads the headers of the
|
|
18
|
-
// rainbow-rb-sdk-dev apt package out of the cpp dev image).
|
|
16
|
+
// - list_rb_sdk classes/functions of the installed rainbow.rb_sdk.
|
|
19
17
|
// - get_rb_sdk_api signature + docstring for an rb_sdk class/function.
|
|
20
18
|
//
|
|
21
19
|
// Add to an MCP client config, e.g. Claude Code's .mcp.json:
|
|
@@ -24,26 +22,15 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
24
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25
23
|
import { z } from "zod";
|
|
26
24
|
import ts from "typescript";
|
|
27
|
-
import {
|
|
28
|
-
appendFileSync,
|
|
29
|
-
existsSync,
|
|
30
|
-
mkdirSync,
|
|
31
|
-
readdirSync,
|
|
32
|
-
readFileSync,
|
|
33
|
-
renameSync,
|
|
34
|
-
statSync,
|
|
35
|
-
writeFileSync,
|
|
36
|
-
} from "node:fs";
|
|
25
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
37
26
|
import { spawnSync } from "node:child_process";
|
|
38
27
|
import { homedir } from "node:os";
|
|
39
28
|
import { join, resolve } from "node:path";
|
|
40
29
|
import { fileURLToPath } from "node:url";
|
|
41
30
|
import { kitRoot, kitVersion, findPluginRoot, venvPython } from "./util.mjs";
|
|
42
|
-
import { PYTHON_DEV_IMAGE } from "./python.mjs";
|
|
43
31
|
import { SHIPPED_STORIES_SRC, SHIPPED_DESIGN_TOKENS } from "./storybook.mjs";
|
|
44
|
-
import { CPP_DEV_IMAGE } from "./cpp.mjs";
|
|
45
32
|
|
|
46
|
-
const
|
|
33
|
+
const TEMPLATE_README = join(kitRoot, "cli/templates/common/README.md");
|
|
47
34
|
// Design tokens: prefer the shipped copy, fall back to the monorepo source.
|
|
48
35
|
const DESIGN_TOKENS_CSS = existsSync(SHIPPED_DESIGN_TOKENS)
|
|
49
36
|
? SHIPPED_DESIGN_TOKENS
|
|
@@ -53,10 +40,14 @@ const DESIGN_TOKENS_CSS = existsSync(SHIPPED_DESIGN_TOKENS)
|
|
|
53
40
|
/* react.d.ts introspection — component/hook list + prop types */
|
|
54
41
|
/* -------------------------------------------------------------------------- */
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
43
|
+
let typesCache = null;
|
|
44
|
+
|
|
45
|
+
// Parse plugin-ui/react's dist/react.d.ts. It's self-contained (rb-components
|
|
46
|
+
// types are inlined at build — see plugin-ui/build-dist.mjs), so this one file
|
|
47
|
+
// is enough even in an external install.
|
|
48
|
+
async function loadReactTypes() {
|
|
49
|
+
if (typesCache) return typesCache;
|
|
50
|
+
|
|
60
51
|
const entryUrl = await import.meta.resolve("@rainbow-robotics/plugin-ui/react");
|
|
61
52
|
const dtsPath = fileURLToPath(entryUrl).replace(/\.js$/, ".d.ts");
|
|
62
53
|
if (!existsSync(dtsPath)) {
|
|
@@ -64,23 +55,19 @@ async function reactDtsPath() {
|
|
|
64
55
|
`plugin-ui type declarations not found at ${dtsPath} — reinstall or rebuild @rainbow-robotics/plugin-ui.`,
|
|
65
56
|
);
|
|
66
57
|
}
|
|
67
|
-
return dtsPath;
|
|
68
|
-
}
|
|
69
58
|
|
|
70
|
-
function loadReactTypes(dtsPath) {
|
|
71
|
-
// Without moduleResolution the `import ... from "react"` inside react.d.ts
|
|
72
|
-
// never resolves and every React-derived type collapses to `any`.
|
|
73
59
|
const program = ts.createProgram([dtsPath], {
|
|
74
60
|
target: ts.ScriptTarget.ES2022,
|
|
75
61
|
module: ts.ModuleKind.ESNext,
|
|
76
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
77
62
|
skipLibCheck: true,
|
|
78
63
|
});
|
|
79
64
|
const sourceFile = program.getSourceFile(dtsPath);
|
|
80
65
|
const checker = program.getTypeChecker();
|
|
81
66
|
const moduleSymbol = sourceFile && checker.getSymbolAtLocation(sourceFile);
|
|
82
67
|
const exports = moduleSymbol ? checker.getExportsOfModule(moduleSymbol) : [];
|
|
83
|
-
|
|
68
|
+
|
|
69
|
+
typesCache = { checker, sourceFile, exports };
|
|
70
|
+
return typesCache;
|
|
84
71
|
}
|
|
85
72
|
|
|
86
73
|
// The barrel's trailing `export { A, B, ... }` produces Alias symbols whose
|
|
@@ -96,124 +83,6 @@ function exportKind(checker, symbol) {
|
|
|
96
83
|
return resolved.flags & ts.SymbolFlags.Value ? "value" : "type";
|
|
97
84
|
}
|
|
98
85
|
|
|
99
|
-
// typeToString alone often prints an opaque alias (`ForwardRefExoticComponent<PropsType$s>`).
|
|
100
|
-
// The first call signature works for plain components and forwardRef alike; its
|
|
101
|
-
// first parameter is the props object.
|
|
102
|
-
function ownProps(checker, type) {
|
|
103
|
-
const [signature] = type.getCallSignatures?.() ?? [];
|
|
104
|
-
const param = signature?.getParameters?.()[0];
|
|
105
|
-
if (!param) return [];
|
|
106
|
-
const declaration = param.valueDeclaration ?? param.declarations?.[0];
|
|
107
|
-
if (!declaration) return [];
|
|
108
|
-
const propsType = checker.getTypeOfSymbolAtLocation(param, declaration);
|
|
109
|
-
|
|
110
|
-
return checker
|
|
111
|
-
.getPropertiesOfType(propsType)
|
|
112
|
-
// Without this, ~220 inherited React DOM attributes bury the component's own API.
|
|
113
|
-
.filter((prop) =>
|
|
114
|
-
(prop.declarations ?? []).some(
|
|
115
|
-
(d) => !d.getSourceFile().fileName.includes("node_modules"),
|
|
116
|
-
),
|
|
117
|
-
)
|
|
118
|
-
.map((prop) => {
|
|
119
|
-
const d = prop.valueDeclaration ?? prop.declarations[0];
|
|
120
|
-
const optional = prop.flags & ts.SymbolFlags.Optional ? "?" : "";
|
|
121
|
-
return `${prop.getName()}${optional}: ${checker.typeToString(
|
|
122
|
-
checker.getTypeOfSymbolAtLocation(prop, d),
|
|
123
|
-
)}`;
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Kept out of the tool handler so every export can be precomputed and cached.
|
|
128
|
-
function propsText(checker, sourceFile, name, symbol) {
|
|
129
|
-
const resolved = resolveExport(checker, symbol);
|
|
130
|
-
const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0] ?? sourceFile;
|
|
131
|
-
const type = checker.getTypeOfSymbolAtLocation(resolved, declaration);
|
|
132
|
-
const signature = checker.typeToString(
|
|
133
|
-
type,
|
|
134
|
-
undefined,
|
|
135
|
-
ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.MultilineObjectLiterals,
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
const props = ownProps(checker, type);
|
|
139
|
-
if (props.length) {
|
|
140
|
-
// Keep the signature line: RBSlider's value/min/max come from Radix and the
|
|
141
|
-
// own-props filter drops them.
|
|
142
|
-
return `# ${name} — own props\n${props.sort().join("\n")}\n\nfull type: ${signature}`;
|
|
143
|
-
}
|
|
144
|
-
// No own props means either a Radix wrapper or unresolved React types. The two
|
|
145
|
-
// look identical, so name the second case explicitly.
|
|
146
|
-
if (/<any>/.test(signature)) {
|
|
147
|
-
return (
|
|
148
|
-
`# ${name} — prop types could not be resolved\n${signature}\n\n` +
|
|
149
|
-
"React's types didn't resolve, so this collapsed to `any`. Install `@types/react` " +
|
|
150
|
-
"in this project (`npm i -D @types/react`) and retry for the real props."
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
return (
|
|
154
|
-
`# ${name} — full type (no plugin-ui-specific props; it forwards a third-party ` +
|
|
155
|
-
`component's props)\n${signature}`
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Rendering every export up front is what makes the result cacheable: once the
|
|
160
|
-
// text exists no TypeChecker is needed.
|
|
161
|
-
function buildUiIndex(dtsPath) {
|
|
162
|
-
const { checker, sourceFile, exports } = loadReactTypes(dtsPath);
|
|
163
|
-
const props = {};
|
|
164
|
-
for (const symbol of exports) {
|
|
165
|
-
props[symbol.getName()] = propsText(checker, sourceFile, symbol.getName(), symbol);
|
|
166
|
-
}
|
|
167
|
-
return {
|
|
168
|
-
components: exports
|
|
169
|
-
.map((sym) => `${sym.getName()} (${exportKind(checker, sym)})`)
|
|
170
|
-
.sort()
|
|
171
|
-
.join("\n"),
|
|
172
|
-
props,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ts.createProgram pulls in ~1100 source files (~1.5s) and an MCP server is a fresh
|
|
177
|
-
// process per session. The key covers the .d.ts identity and the kit version.
|
|
178
|
-
const UI_INDEX_CACHE_DIR = join(homedir(), ".cache", "muscat-pk");
|
|
179
|
-
const UI_INDEX_CACHE = join(UI_INDEX_CACHE_DIR, "ui-index.json");
|
|
180
|
-
|
|
181
|
-
let uiIndexCache = null;
|
|
182
|
-
|
|
183
|
-
async function loadUiIndex() {
|
|
184
|
-
if (uiIndexCache) return uiIndexCache;
|
|
185
|
-
const dtsPath = await reactDtsPath();
|
|
186
|
-
const { mtimeMs, size } = statSync(dtsPath);
|
|
187
|
-
const key = `${dtsPath}|${mtimeMs}|${size}|${kitVersion()}`;
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
const cached = JSON.parse(readFileSync(UI_INDEX_CACHE, "utf8"));
|
|
191
|
-
// A half-written file still parses as JSON, so check the shape too.
|
|
192
|
-
if (
|
|
193
|
-
cached.key === key &&
|
|
194
|
-
typeof cached.components === "string" &&
|
|
195
|
-
cached.props &&
|
|
196
|
-
typeof cached.props === "object"
|
|
197
|
-
) {
|
|
198
|
-
return (uiIndexCache = cached);
|
|
199
|
-
}
|
|
200
|
-
} catch {
|
|
201
|
-
/* absent, unreadable or corrupt — rebuild below */
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
uiIndexCache = { key, ...buildUiIndex(dtsPath) };
|
|
205
|
-
try {
|
|
206
|
-
mkdirSync(UI_INDEX_CACHE_DIR, { recursive: true });
|
|
207
|
-
// rename is atomic, so a concurrent server never reads a half-written file.
|
|
208
|
-
const tmp = `${UI_INDEX_CACHE}.${process.pid}.tmp`;
|
|
209
|
-
writeFileSync(tmp, JSON.stringify(uiIndexCache));
|
|
210
|
-
renameSync(tmp, UI_INDEX_CACHE);
|
|
211
|
-
} catch {
|
|
212
|
-
/* read-only fs / sandbox: the cache is an optimisation, never a requirement */
|
|
213
|
-
}
|
|
214
|
-
return uiIndexCache;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
86
|
/* -------------------------------------------------------------------------- */
|
|
218
87
|
/* Storybook story source lookup */
|
|
219
88
|
/* -------------------------------------------------------------------------- */
|
|
@@ -261,29 +130,10 @@ function splitSections(text) {
|
|
|
261
130
|
return sections;
|
|
262
131
|
}
|
|
263
132
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
const { TEMPLATES } = await import("./templates-embedded.mjs");
|
|
270
|
-
return TEMPLATES[rel] ? Buffer.from(TEMPLATES[rel], "base64").toString("utf8") : "";
|
|
271
|
-
} catch {
|
|
272
|
-
return "";
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
async function searchReadme(query, lang) {
|
|
277
|
-
const langs = lang ? [lang] : ["python", "cpp"];
|
|
278
|
-
const seen = new Set();
|
|
279
|
-
const sections = [];
|
|
280
|
-
for (const l of langs) {
|
|
281
|
-
for (const section of splitSections(await readTemplateReadme(TEMPLATE_READMES[l]))) {
|
|
282
|
-
if (seen.has(section)) continue; // UI/manifest sections are shared by both READMEs
|
|
283
|
-
seen.add(section);
|
|
284
|
-
sections.push(langs.length > 1 && l === "cpp" ? `[C++ backend]\n${section}` : section);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
133
|
+
function searchReadme(query) {
|
|
134
|
+
if (!existsSync(TEMPLATE_README)) return [];
|
|
135
|
+
const text = readFileSync(TEMPLATE_README, "utf8");
|
|
136
|
+
const sections = splitSections(text);
|
|
287
137
|
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
288
138
|
|
|
289
139
|
return sections
|
|
@@ -377,6 +227,9 @@ function pythonCmd() {
|
|
|
377
227
|
// backend dependency). Returns a clear hint if it isn't importable.
|
|
378
228
|
function introspectRbSdk(script) {
|
|
379
229
|
const py = pythonCmd();
|
|
230
|
+
if (!py) {
|
|
231
|
+
return { error: "Python not found on PATH. Install Python 3 to use rb-sdk introspection." };
|
|
232
|
+
}
|
|
380
233
|
const probe = `
|
|
381
234
|
import json, importlib
|
|
382
235
|
try:
|
|
@@ -385,44 +238,10 @@ except Exception as e:
|
|
|
385
238
|
print(json.dumps({"__notinstalled__": str(e)})); raise SystemExit(0)
|
|
386
239
|
${script}
|
|
387
240
|
`;
|
|
388
|
-
if (!py) {
|
|
389
|
-
return introspectInDocker(probe) ?? { error: "Python not found on PATH. Install Python 3 to use rb-sdk introspection." };
|
|
390
|
-
}
|
|
391
241
|
const r = spawnSync(py, ["-c", probe], { encoding: "utf8", maxBuffer: 8 << 20 });
|
|
392
242
|
if (r.status !== 0) {
|
|
393
243
|
return { error: `python introspection failed: ${(r.stderr || "").trim().slice(0, 500)}` };
|
|
394
244
|
}
|
|
395
|
-
const local = parseProbe(r);
|
|
396
|
-
return local.error && local.notInstalled ? introspectInDocker(probe) ?? local : local;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// No macOS wheels: import the plugin's pinned SDK inside the python dev image (cached).
|
|
400
|
-
function introspectInDocker(probe) {
|
|
401
|
-
if (spawnSync("docker", ["image", "inspect", PYTHON_DEV_IMAGE], { encoding: "utf8" }).status !== 0) return null;
|
|
402
|
-
let ver = "";
|
|
403
|
-
try {
|
|
404
|
-
const req = readFileSync(join(findPluginRoot(), "requirements.txt"), "utf8");
|
|
405
|
-
ver = req.match(/^rainbow-rb-sdk==(\S+)/m)?.[1] ?? "";
|
|
406
|
-
} catch {
|
|
407
|
-
/* not a python plugin — latest */
|
|
408
|
-
}
|
|
409
|
-
// ponytail: "latest" cache is never refreshed; delete ~/.cache/... rb-sdk-py/latest to update.
|
|
410
|
-
const cache = join(UI_INDEX_CACHE_DIR, "rb-sdk-py", ver || "latest");
|
|
411
|
-
mkdirSync(cache, { recursive: true });
|
|
412
|
-
const pkg = ver ? `rainbow-rb-sdk==${ver}` : "rainbow-rb-sdk";
|
|
413
|
-
const script =
|
|
414
|
-
`[ -f /c/.ok ] || { pip3 install -q --break-system-packages --target /c '${pkg}' >&2 && touch /c/.ok; } && ` +
|
|
415
|
-
`PYTHONPATH=/c python3 -c "$PROBE"`;
|
|
416
|
-
const r = spawnSync("docker", ["run", "--rm", "-v", `${cache}:/c`, "-e", "PROBE", PYTHON_DEV_IMAGE, "bash", "-c", script], {
|
|
417
|
-
encoding: "utf8",
|
|
418
|
-
maxBuffer: 8 << 20,
|
|
419
|
-
env: { ...process.env, PROBE: probe },
|
|
420
|
-
});
|
|
421
|
-
if (r.status !== 0) return { error: `python introspection (docker) failed: ${(r.stderr || "").trim().slice(-500)}` };
|
|
422
|
-
return parseProbe(r);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function parseProbe(r) {
|
|
426
245
|
// Importing rainbow.rb_sdk has side effects that print to stdout (e.g. the
|
|
427
246
|
// Zenoh client logs its connection endpoints), ahead of our own final
|
|
428
247
|
// print(json.dumps(...)) — so parse the last line, not the whole output.
|
|
@@ -435,7 +254,6 @@ function parseProbe(r) {
|
|
|
435
254
|
}
|
|
436
255
|
if (data && data.__notinstalled__) {
|
|
437
256
|
return {
|
|
438
|
-
notInstalled: true,
|
|
439
257
|
error:
|
|
440
258
|
"rainbow-rb-sdk is not installed in this environment. Install it with `pip install rainbow-rb-sdk` " +
|
|
441
259
|
"(it's already in a plugin's requirements.txt), then retry.",
|
|
@@ -444,114 +262,6 @@ function parseProbe(r) {
|
|
|
444
262
|
return { data };
|
|
445
263
|
}
|
|
446
264
|
|
|
447
|
-
/* -------------------------------------------------------------------------- */
|
|
448
|
-
/* rainbow-rb-sdk-dev (C++) headers — copied out of the cpp dev image */
|
|
449
|
-
/* -------------------------------------------------------------------------- */
|
|
450
|
-
|
|
451
|
-
const CPP_INCLUDE_CACHE = join(UI_INDEX_CACHE_DIR, "rb-sdk-include");
|
|
452
|
-
// Headers we don't want to surface: third-party vendored code, pybind glue,
|
|
453
|
-
// flatbuffers-generated schemas, and the industrial comm stacks.
|
|
454
|
-
const CPP_SKIP = /^(vendor|binding|rb_comm_source)\/|_generated\.h$/;
|
|
455
|
-
|
|
456
|
-
// The apt package is only installed inside the muscat-pk-cpp-dev image, so
|
|
457
|
-
// copy /usr/include/rainbow/rb_sdk out once per image build (cache key =
|
|
458
|
-
// image id) with `docker cp` — no host tar needed.
|
|
459
|
-
function cppIncludeDir() {
|
|
460
|
-
const insp = spawnSync("docker", ["image", "inspect", "-f", "{{.Id}}", CPP_DEV_IMAGE], { encoding: "utf8" });
|
|
461
|
-
if (insp.error) return { error: "Docker not found — the C++ SDK headers live in the muscat-pk cpp dev image." };
|
|
462
|
-
if (insp.status !== 0) {
|
|
463
|
-
return {
|
|
464
|
-
error:
|
|
465
|
-
`Docker image ${CPP_DEV_IMAGE} not built yet. Run \`muscat-pk dev:back\` (or \`muscat-pk build\`) once ` +
|
|
466
|
-
"in a cpp / rclcpp plugin to build it, then retry.",
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
const dir = join(CPP_INCLUDE_CACHE, insp.stdout.trim().replace(/^sha256:/, "").slice(0, 12));
|
|
470
|
-
const out = join(dir, "rb_sdk");
|
|
471
|
-
if (existsSync(out)) return { dir: out };
|
|
472
|
-
mkdirSync(dir, { recursive: true });
|
|
473
|
-
const ctr = spawnSync("docker", ["create", CPP_DEV_IMAGE], { encoding: "utf8" });
|
|
474
|
-
if (ctr.status !== 0) return { error: `docker create failed: ${(ctr.stderr || "").trim().slice(0, 300)}` };
|
|
475
|
-
const id = ctr.stdout.trim();
|
|
476
|
-
const cp = spawnSync("docker", ["cp", `${id}:/usr/include/rainbow/rb_sdk`, dir], { encoding: "utf8" });
|
|
477
|
-
spawnSync("docker", ["rm", "-f", id], { encoding: "utf8" });
|
|
478
|
-
if (cp.status !== 0 || !existsSync(out)) {
|
|
479
|
-
return { error: `docker cp failed: ${(cp.stderr || "").trim().slice(0, 300)}` };
|
|
480
|
-
}
|
|
481
|
-
return { dir: out };
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function cppHeaders(root) {
|
|
485
|
-
const files = [];
|
|
486
|
-
const walk = (d, rel) => {
|
|
487
|
-
for (const name of readdirSync(d)) {
|
|
488
|
-
const r = rel ? `${rel}/${name}` : name;
|
|
489
|
-
if (CPP_SKIP.test(r)) continue;
|
|
490
|
-
const full = join(d, name);
|
|
491
|
-
if (statSync(full).isDirectory()) walk(full, r);
|
|
492
|
-
else if (/\.hpp$/.test(name)) files.push(r);
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
walk(root, "");
|
|
496
|
-
return files.sort();
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// "class Foo {" / "class Foo : public Bar {" — not forward declarations.
|
|
500
|
-
const CLASS_RE = /^\s*(?:class|struct)\s+(?:[A-Z][A-Z0-9_]*_API\s+)?(\w+)\b(?![^\n]*;\s*$)/; // skip export macros (RB_SDK_API)
|
|
501
|
-
|
|
502
|
-
function cppClassesIn(text) {
|
|
503
|
-
return [...new Set(text.split("\n").map((l) => l.match(CLASS_RE)?.[1]).filter(Boolean))];
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Return the class body (brace-matched) plus the file's leading comment block,
|
|
507
|
-
// which in these headers usually carries a full usage example.
|
|
508
|
-
function cppExtract(text, className, method) {
|
|
509
|
-
const lines = text.split("\n");
|
|
510
|
-
const start = lines.findIndex((l) => CLASS_RE.test(l) && l.match(CLASS_RE)[1] === className);
|
|
511
|
-
if (start < 0) return null;
|
|
512
|
-
let depth = 0, end = start, seen = false;
|
|
513
|
-
for (let i = start; i < lines.length; i++) {
|
|
514
|
-
for (const ch of lines[i]) {
|
|
515
|
-
if (ch === "{") { depth++; seen = true; }
|
|
516
|
-
else if (ch === "}") depth--;
|
|
517
|
-
}
|
|
518
|
-
if (seen && depth === 0) { end = i; break; }
|
|
519
|
-
}
|
|
520
|
-
const body = lines.slice(start, end + 1);
|
|
521
|
-
if (method) {
|
|
522
|
-
// Declarations sit at class-member indent (<= 2 spaces) and the name is
|
|
523
|
-
// not preceded by `.`/`->`/`::` — that skips call sites inside bodies.
|
|
524
|
-
const re = new RegExp(`^ {0,2}[^(]*(?<![\\w.>:])${method.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
|
|
525
|
-
const hits = [];
|
|
526
|
-
for (let i = 0; i < body.length; i++) {
|
|
527
|
-
if (!re.test(body[i]) || /^\s*\/\//.test(body[i])) continue;
|
|
528
|
-
let a = i;
|
|
529
|
-
while (a > 0 && /^\s*\/\//.test(body[a - 1])) a--;
|
|
530
|
-
let b = i;
|
|
531
|
-
while (b < body.length - 1 && !/[{;]/.test(body[b])) b++;
|
|
532
|
-
hits.push(body.slice(a, b + 1).join("\n"));
|
|
533
|
-
}
|
|
534
|
-
return hits.length ? hits.join("\n\n") : null;
|
|
535
|
-
}
|
|
536
|
-
const head = [];
|
|
537
|
-
for (const l of lines) {
|
|
538
|
-
if (/^\s*\/\//.test(l)) head.push(l);
|
|
539
|
-
else if (l.trim() === "" || /^#pragma/.test(l)) continue;
|
|
540
|
-
else break;
|
|
541
|
-
}
|
|
542
|
-
return (head.length > 3 ? head.join("\n") + "\n\n" : "") + body.join("\n");
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function cppFind(className) {
|
|
546
|
-
const { dir, error } = cppIncludeDir();
|
|
547
|
-
if (error) return { error };
|
|
548
|
-
for (const rel of cppHeaders(dir)) {
|
|
549
|
-
const text = readFileSync(join(dir, rel), "utf8");
|
|
550
|
-
if (cppClassesIn(text).includes(className)) return { rel, text };
|
|
551
|
-
}
|
|
552
|
-
return { error: `No class "${className}" in the C++ SDK headers. Call list_rb_sdk with lang "cpp" for valid names.` };
|
|
553
|
-
}
|
|
554
|
-
|
|
555
265
|
/* -------------------------------------------------------------------------- */
|
|
556
266
|
/* client registration — `muscat-pk mcp --install <client>` */
|
|
557
267
|
/* -------------------------------------------------------------------------- */
|
|
@@ -603,8 +313,11 @@ export async function runMcp() {
|
|
|
603
313
|
inputSchema: {},
|
|
604
314
|
},
|
|
605
315
|
async () => {
|
|
606
|
-
const {
|
|
607
|
-
|
|
316
|
+
const { checker, exports } = await loadReactTypes();
|
|
317
|
+
const lines = exports
|
|
318
|
+
.map((sym) => `${sym.getName()} (${exportKind(checker, sym)})`)
|
|
319
|
+
.sort();
|
|
320
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
608
321
|
},
|
|
609
322
|
);
|
|
610
323
|
|
|
@@ -618,8 +331,9 @@ export async function runMcp() {
|
|
|
618
331
|
inputSchema: { name: z.string().describe('Exported name, e.g. "RBSlider"') },
|
|
619
332
|
},
|
|
620
333
|
async ({ name }) => {
|
|
621
|
-
const {
|
|
622
|
-
|
|
334
|
+
const { checker, sourceFile, exports } = await loadReactTypes();
|
|
335
|
+
const symbol = exports.find((sym) => sym.getName() === name);
|
|
336
|
+
if (!symbol) {
|
|
623
337
|
return {
|
|
624
338
|
isError: true,
|
|
625
339
|
content: [
|
|
@@ -630,7 +344,15 @@ export async function runMcp() {
|
|
|
630
344
|
],
|
|
631
345
|
};
|
|
632
346
|
}
|
|
633
|
-
|
|
347
|
+
const resolved = resolveExport(checker, symbol);
|
|
348
|
+
const declaration = resolved.valueDeclaration ?? resolved.declarations?.[0] ?? sourceFile;
|
|
349
|
+
const type = checker.getTypeOfSymbolAtLocation(resolved, declaration);
|
|
350
|
+
const text = checker.typeToString(
|
|
351
|
+
type,
|
|
352
|
+
undefined,
|
|
353
|
+
ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.MultilineObjectLiterals,
|
|
354
|
+
);
|
|
355
|
+
return { content: [{ type: "text", text }] };
|
|
634
356
|
},
|
|
635
357
|
);
|
|
636
358
|
|
|
@@ -677,29 +399,20 @@ export async function runMcp() {
|
|
|
677
399
|
title: "Search the Muscat plugin-author docs",
|
|
678
400
|
description:
|
|
679
401
|
"Search the Muscat plugin-author README for how to use useRb(), rb.pub/sub/action/serve, " +
|
|
680
|
-
"plugin.router (external endpoints: router.on/subscribe/emit exposed over REST/Socket.IO/TCP/" +
|
|
681
|
-
"MQTT/WebRTC at common/plugin/{plugin_id}/{path}, PluginRouteError, meta.transport/principal), " +
|
|
682
402
|
"rb.toast/alert/confirm/prompt, useRbFormData/useRbSave (save/restore form data through the " +
|
|
683
403
|
"host), manifest.json (including the \"variables\" declaration for the pyfm variable picker — " +
|
|
684
404
|
"names require the RBP_ prefix), program variables & executor state (ctx.get_variable/" +
|
|
685
|
-
"set_variable/executor_state/check_stop),
|
|
686
|
-
"
|
|
687
|
-
"an external PC), contribution points, and backend runtimes " +
|
|
688
|
-
"(Python/C++/ROS2). Covers BOTH backends: the Python README (py/main.py, rainbow.rb_sdk) and the " +
|
|
689
|
-
"C++ README (cpp/main.cpp or an rclcpp node: rb_plugin::RBPluginCall serve/subscribe/publish/" +
|
|
690
|
-
"router(), rb_plugin::PluginProgramContext, RBManipulateSDK, CMake rb_sdk::rb_sdk). Pass lang " +
|
|
691
|
-
"\"python\" or \"cpp\" to match the plugin's manifest runtime (cpp or ros2+rclcpp → \"cpp\"); " +
|
|
692
|
-
"omit it to search both. IMPORTANT: " +
|
|
405
|
+
"set_variable/executor_state/check_stop), contribution points, and backend runtimes " +
|
|
406
|
+
"(Python/C++/ROS2). IMPORTANT: " +
|
|
693
407
|
"the backend pattern depends on the contribution point — search 'which backend pattern' before " +
|
|
694
|
-
"writing
|
|
408
|
+
"writing py/main.py. Only a program.panel command uses PluginProgramContext; a toolbar/move.panel " +
|
|
695
409
|
"plugin controls the robot with the SDK (RBManipulateSDK/RBAmrSDK) directly.",
|
|
696
410
|
inputSchema: {
|
|
697
411
|
query: z.string().describe('Keywords, e.g. "save form data" or "rb.action"'),
|
|
698
|
-
lang: z.enum(["python", "cpp"]).optional().describe("Backend language; omit to search both"),
|
|
699
412
|
},
|
|
700
413
|
},
|
|
701
|
-
async ({ query
|
|
702
|
-
const sections =
|
|
414
|
+
async ({ query }) => {
|
|
415
|
+
const sections = searchReadme(query);
|
|
703
416
|
if (sections.length === 0) {
|
|
704
417
|
return { content: [{ type: "text", text: `No matching section found for "${query}".` }] };
|
|
705
418
|
}
|
|
@@ -733,10 +446,7 @@ export async function runMcp() {
|
|
|
733
446
|
const other = { shadow: [], radius: [], spacing: [] };
|
|
734
447
|
for (const t of vars) {
|
|
735
448
|
const m = t.group?.match(/^color-(bg|fg|border|fill)$/);
|
|
736
|
-
|
|
737
|
-
// (var(--color-scale-zinc-200)), and each class is declared twice (light/dark)
|
|
738
|
-
// with different values — dropping the value lets the Set dedup collapse them.
|
|
739
|
-
if (m && t.class) colorGroups[m[1]].push(t.class);
|
|
449
|
+
if (m && t.class) colorGroups[m[1]].push(`${t.class} (${t.value})`);
|
|
740
450
|
else if (t.name.startsWith("shadow-")) other.shadow.push(`shadow-${t.name.slice(7)}`);
|
|
741
451
|
else if (t.name.startsWith("radius-")) other.radius.push(`rounded-${t.name.slice(7)}`);
|
|
742
452
|
else if (t.name.startsWith("spacing-")) other.spacing.push(t.name);
|
|
@@ -799,29 +509,14 @@ export async function runMcp() {
|
|
|
799
509
|
server.registerTool(
|
|
800
510
|
"list_rb_sdk",
|
|
801
511
|
{
|
|
802
|
-
title: "List rainbow-rb-sdk classes and functions
|
|
512
|
+
title: "List rainbow-rb-sdk (Python) classes and functions",
|
|
803
513
|
description:
|
|
804
|
-
"List the public classes and functions of the
|
|
805
|
-
"
|
|
806
|
-
"
|
|
807
|
-
|
|
808
|
-
"...) grouped by header path — call before writing cpp/main.cpp or an rclcpp node; requires the " +
|
|
809
|
-
"muscat-pk-cpp-dev Docker image (built by `muscat-pk dev:back`). RBPluginCall (UI pub/sub/serve, " +
|
|
810
|
-
"router) exists in both: Python rainbow.rb_sdk.RBPluginCall, C++ rb_plugin::RBPluginCall " +
|
|
811
|
-
"(plugin/rb_plugin.hpp).",
|
|
812
|
-
inputSchema: { lang: z.enum(["python", "cpp"]).optional().describe('Default "python"') },
|
|
514
|
+
"List the public classes and functions of the installed rainbow.rb_sdk Python package — the " +
|
|
515
|
+
"plugin BACKEND SDK (RBPluginCall, RBManipulateSDK, RBAmrSDK, PluginProgramContext, ...). Call " +
|
|
516
|
+
"before writing py/main.py. Requires rainbow-rb-sdk to be pip-installed.",
|
|
517
|
+
inputSchema: {},
|
|
813
518
|
},
|
|
814
|
-
async (
|
|
815
|
-
if (lang === "cpp") {
|
|
816
|
-
const { dir, error } = cppIncludeDir();
|
|
817
|
-
if (error) return { isError: true, content: [{ type: "text", text: error }] };
|
|
818
|
-
const out = [];
|
|
819
|
-
for (const rel of cppHeaders(dir)) {
|
|
820
|
-
const classes = cppClassesIn(readFileSync(join(dir, rel), "utf8"));
|
|
821
|
-
if (classes.length) out.push(`${rel}: ${classes.join(", ")}`);
|
|
822
|
-
}
|
|
823
|
-
return { content: [{ type: "text", text: `#include <rainbow/rb_sdk/...>\n\n${out.join("\n")}` }] };
|
|
824
|
-
}
|
|
519
|
+
async () => {
|
|
825
520
|
const { data, error } = introspectRbSdk(`
|
|
826
521
|
import inspect, json
|
|
827
522
|
mod = importlib.import_module("rainbow.rb_sdk")
|
|
@@ -841,22 +536,19 @@ print(json.dumps(out))
|
|
|
841
536
|
server.registerTool(
|
|
842
537
|
"get_rb_sdk_api",
|
|
843
538
|
{
|
|
844
|
-
title: "Get a rainbow-rb-sdk class/function signature + docs
|
|
539
|
+
title: "Get a rainbow-rb-sdk class/function signature + docs",
|
|
845
540
|
description:
|
|
846
541
|
"Get the signature(s) and docstring for a rainbow.rb_sdk class or function (e.g. " +
|
|
847
542
|
"'PluginProgramContext', 'RBManipulateSDK'). For a class, lists its public methods with " +
|
|
848
543
|
"signatures. To drill into ONE method of a class (e.g. RBManipulateSDK.call_move_j), pass " +
|
|
849
|
-
"`method` alongside `name` — do not put both in `name`.
|
|
850
|
-
"pip-installed
|
|
851
|
-
"(rainbow-rb-sdk-dev) with its header comment — usually a full usage example — and the " +
|
|
852
|
-
"#include path; with `method`, just that method's declaration and comment.",
|
|
544
|
+
"`method` alongside `name` — do not put both in `name`. Requires rainbow-rb-sdk to be " +
|
|
545
|
+
"pip-installed.",
|
|
853
546
|
inputSchema: {
|
|
854
547
|
name: z.string().describe('Class or function name, e.g. "RBManipulateSDK"'),
|
|
855
548
|
method: z.string().optional().describe('Optional: a specific method on that class, e.g. "call_move_j"'),
|
|
856
|
-
lang: z.enum(["python", "cpp"]).optional().describe('Default "python"'),
|
|
857
549
|
},
|
|
858
550
|
},
|
|
859
|
-
async ({ name, method
|
|
551
|
+
async ({ name, method }) => {
|
|
860
552
|
// Tolerate a combined "ClassName method_name" in `name` (a common agent
|
|
861
553
|
// mistake) by splitting it ourselves instead of failing validation.
|
|
862
554
|
let className = name;
|
|
@@ -866,42 +558,6 @@ print(json.dumps(out))
|
|
|
866
558
|
if (parts.length === 2) [className, methodName] = parts;
|
|
867
559
|
}
|
|
868
560
|
|
|
869
|
-
if (lang === "cpp") {
|
|
870
|
-
const cls = className.replace(/^.*::/, "");
|
|
871
|
-
let { rel, text, error } = cppFind(cls);
|
|
872
|
-
if (error) return { isError: true, content: [{ type: "text", text: error }] };
|
|
873
|
-
let body = cppExtract(text, cls, methodName);
|
|
874
|
-
let owner = cls;
|
|
875
|
-
if (!body && methodName) {
|
|
876
|
-
// Like the Python path: most calls live on sub-SDK members
|
|
877
|
-
// (RBManipulateMoveSDK move; -> sdk.move.call_move_j) or on the base
|
|
878
|
-
// class — look there before giving up.
|
|
879
|
-
const decl = cppExtract(text, cls);
|
|
880
|
-
const cands = [
|
|
881
|
-
...[...decl.matchAll(/^\s*(\w+SDK)\s+(\w+);/gm)].map((m) => [m[1], `${cls}.${m[2]}`]),
|
|
882
|
-
...[...decl.matchAll(/:\s*public\s+(\w+)/g)].map((m) => [m[1], `${cls} (base ${m[1]})`]),
|
|
883
|
-
];
|
|
884
|
-
for (const [c, label] of cands) {
|
|
885
|
-
const f = cppFind(c);
|
|
886
|
-
if (f.error) continue;
|
|
887
|
-
const b = cppExtract(f.text, c, methodName);
|
|
888
|
-
if (b) { ({ rel, text } = f); body = b; owner = label; break; }
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
if (!body) {
|
|
892
|
-
return {
|
|
893
|
-
isError: true,
|
|
894
|
-
content: [{ type: "text", text: `No method "${methodName}" declared in class "${cls}" (${rel}) or its sub-SDK members / base class. Call get_rb_sdk_api with just \`name\` to see the class.` }],
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
const ns = text.match(/^namespace\s+(\w+)\s*\{/m)?.[1];
|
|
898
|
-
const header =
|
|
899
|
-
`// #include <rainbow/rb_sdk/${rel}>${ns ? ` (namespace ${ns})` : ""}\n` +
|
|
900
|
-
`// link: find_package(rb_sdk) + rb_sdk::rb_sdk\n` +
|
|
901
|
-
(methodName ? `// ${owner}.${methodName}\n` : "") + "\n";
|
|
902
|
-
return { content: [{ type: "text", text: header + body.slice(0, 60_000) }] };
|
|
903
|
-
}
|
|
904
|
-
|
|
905
561
|
const { data, error } = introspectRbSdk(`
|
|
906
562
|
import inspect, json
|
|
907
563
|
mod = importlib.import_module("rainbow.rb_sdk")
|
package/cli/src/new.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { color, ensureIdeVenv, hasBin, kitVersion, parseArgs, prompt, templatesD
|
|
|
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).
|
|
19
19
|
// ponytail: import() is module-cached by the runtime — no manual cache needed.
|
|
20
|
-
|
|
20
|
+
async function copyTemplate(srcDir, dst) {
|
|
21
21
|
if (existsSync(templatesDir)) { cpSync(srcDir, dst, { recursive: true }); return; }
|
|
22
22
|
const { TEMPLATES } = await import("./templates-embedded.mjs");
|
|
23
23
|
const prefix = srcDir.slice(templatesDir.length + 1).replace(/\\/g, "/") + "/";
|
|
@@ -87,7 +87,6 @@ export function runtimeForChoice(choice, slug) {
|
|
|
87
87
|
|
|
88
88
|
export function applyTokens(dir, tokens) {
|
|
89
89
|
for (const entry of readdirSync(dir)) {
|
|
90
|
-
if (entry === "node_modules" || entry === ".git" || entry === ".venv") continue;
|
|
91
90
|
const full = resolve(dir, entry);
|
|
92
91
|
if (statSync(full).isDirectory()) {
|
|
93
92
|
applyTokens(full, tokens);
|
|
@@ -107,12 +106,6 @@ export function applyTokens(dir, tokens) {
|
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
// Derive naming tokens from a plugin directory name.
|
|
110
|
-
// One id part (see PLUGIN_ID_RE): lowercase, single "-", starts with a letter.
|
|
111
|
-
function idPart(name) {
|
|
112
|
-
const part = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
113
|
-
return /^[a-z]/.test(part) ? part : part ? `plugin-${part}` : "plugin";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
109
|
export function deriveNames(target) {
|
|
117
110
|
const base = basename(target);
|
|
118
111
|
const slug = base.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
@@ -122,7 +115,7 @@ export function deriveNames(target) {
|
|
|
122
115
|
slug,
|
|
123
116
|
pluginName,
|
|
124
117
|
pluginClass: pluginName.replace(/\s+/g, ""),
|
|
125
|
-
pluginId: `com.example.${
|
|
118
|
+
pluginId: `com.example.${base.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
|
|
126
119
|
};
|
|
127
120
|
}
|
|
128
121
|
|
|
@@ -233,9 +226,6 @@ export async function runNew(args) {
|
|
|
233
226
|
rmSync(resolve(target, "py"), { recursive: true, force: true });
|
|
234
227
|
rmSync(resolve(target, "requirements.txt"), { force: true });
|
|
235
228
|
}
|
|
236
|
-
if (runtimeChoice.type === "cpp" || runtimeChoice.rosClient === "rclcpp") {
|
|
237
|
-
await copyTemplate(resolve(templatesDir, "readme-cpp"), target);
|
|
238
|
-
}
|
|
239
229
|
|
|
240
230
|
applyTokens(target, {
|
|
241
231
|
__PLUGIN_ID__: pluginId,
|
|
@@ -260,7 +250,7 @@ export async function runNew(args) {
|
|
|
260
250
|
// gitignore (templates can't ship a literal .gitignore via npm reliably)
|
|
261
251
|
writeFileSync(
|
|
262
252
|
resolve(target, ".gitignore"),
|
|
263
|
-
["node_modules", "dist", ".muscat-dev", ".venv", "*.zip", "ros2_ws/install", "
|
|
253
|
+
["node_modules", "dist", ".muscat-dev", ".venv", "*.zip", "ros2_ws/install", "cpp/build", ""].join("\n"),
|
|
264
254
|
);
|
|
265
255
|
|
|
266
256
|
if (!opts["no-install"]) {
|