@rainbow-robotics/muscat-plugin-kit 1.10.2-dev.1 → 1.10.2-dev.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/cli/bin/muscat-pk.mjs +11 -2
- package/cli/src/ai-rules.mjs +37 -0
- package/cli/src/mcp.mjs +30 -0
- package/cli/src/new.mjs +2 -1
- package/cli/src/projectCheck.mjs +102 -0
- package/cli/src/update.mjs +65 -85
- package/cli/src/util.mjs +12 -0
- package/cli/storybook-dist/project.json +1 -1
- package/cli/templates/common/README.md +4 -0
- package/cli/templates/readme-cpp/README.md +4 -0
- package/package.json +3 -3
- package/cli/templates/common/.mcp.json +0 -8
package/cli/bin/muscat-pk.mjs
CHANGED
|
@@ -5,13 +5,13 @@ import { readFileSync, realpathSync } from "node:fs";
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
|
-
import { color, kitVersion, log, resolveLocalCliBin } from "../src/util.mjs";
|
|
8
|
+
import { color, compareKitVersions, kitVersion, log, resolveLocalCliBin } from "../src/util.mjs";
|
|
9
9
|
import { runNew } from "../src/new.mjs";
|
|
10
10
|
import { runBuild } from "../src/build.mjs";
|
|
11
11
|
import { runDevUi } from "../src/devUi.mjs";
|
|
12
12
|
import { runDevBack } from "../src/devBack.mjs";
|
|
13
13
|
import { runDev } from "../src/dev.mjs";
|
|
14
|
-
import { runUpdate } from "../src/update.mjs";
|
|
14
|
+
import { runUpdate, runUpdateCheck } from "../src/update.mjs";
|
|
15
15
|
import { runUninstall } from "../src/uninstall.mjs";
|
|
16
16
|
import { runSdk } from "../src/sdk.mjs";
|
|
17
17
|
import { runType } from "../src/type.mjs";
|
|
@@ -50,6 +50,13 @@ if (isBinaryMode && !process.env.MUSCAT_PK_VERSION) {
|
|
|
50
50
|
// Say so — otherwise an outdated local copy silently answers instead, and
|
|
51
51
|
// its missing/renamed subcommands look like the CLI is broken.
|
|
52
52
|
const localVersion = readVersion(localBin);
|
|
53
|
+
// An older local copy can't update a project correctly — it doesn't know what changed since
|
|
54
|
+
// it was published. Run this CLI's update instead, still touching this project only.
|
|
55
|
+
if (process.argv[2] === "update" && localVersion && compareKitVersions(localVersion, kitVersion()) < 0) {
|
|
56
|
+
console.error(color.dim(`[muscat-pk] project's local install (v${localVersion}) is older — updating it with this CLI (v${kitVersion()})`));
|
|
57
|
+
process.env.MUSCAT_PK_UPDATE_PROJECT_ONLY = "1";
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
53
60
|
console.error(
|
|
54
61
|
color.dim(`[muscat-pk] using this project's local install${localVersion ? ` (v${localVersion})` : ""}: ${localBin}`),
|
|
55
62
|
);
|
|
@@ -121,6 +128,8 @@ async function main() {
|
|
|
121
128
|
}
|
|
122
129
|
case "update":
|
|
123
130
|
return await runUpdate(args);
|
|
131
|
+
case "update:check": // internal: update runs it with the freshly installed local kit
|
|
132
|
+
return await runUpdateCheck(args);
|
|
124
133
|
case "uninstall":
|
|
125
134
|
return await runUninstall(args);
|
|
126
135
|
case "sdk":
|
package/cli/src/ai-rules.mjs
CHANGED
|
@@ -29,6 +29,33 @@ const UI_RULES = [
|
|
|
29
29
|
"Colors and type come from tokens (bg-bg-*, text-fg-*, typo-*), not hex values or arbitrary Tailwind colors.",
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
+
// plugin-ui 1.x replaced the remote-dom components and the connect() bootstrap. The project check
|
|
33
|
+
// (update, MCP check_project) flags these; the migration rules below tell an agent how to fix them.
|
|
34
|
+
export const LEGACY_SUBPATHS = {
|
|
35
|
+
connect: "removed — drop `connect(App)` and `export default App` instead (the host renders the entry's default export)",
|
|
36
|
+
elements: "removed — use the RB* components from @rainbow-robotics/plugin-ui/react",
|
|
37
|
+
"dist-browser": "removed — import from @rainbow-robotics/plugin-ui/react",
|
|
38
|
+
};
|
|
39
|
+
export const LEGACY_COMPONENTS = {
|
|
40
|
+
Slider: "RBSlider",
|
|
41
|
+
Button: "RBLabelButton",
|
|
42
|
+
IconButton: "RBIconButton",
|
|
43
|
+
Text: "a plain element or RBTextLabel",
|
|
44
|
+
TextInput: "RBTextInput / RBTextField",
|
|
45
|
+
Switch: "RBSwitch",
|
|
46
|
+
Select: "RBSelectBox",
|
|
47
|
+
};
|
|
48
|
+
const MIGRATION_RULES = [
|
|
49
|
+
"After `muscat-pk update`, or when the UI fails to build or the preview says \"no default export\", call check_project and fix every item it lists. Call it again until it reports no issues.",
|
|
50
|
+
"Each top-level ui/*.tsx is an entry and ends in `export default App`. The old `connect(App)` bootstrap and @rainbow-robotics/plugin-ui/connect are gone.",
|
|
51
|
+
"vite.config.ts uses the muscat() preset (`plugins: [muscat()]` from @rainbow-robotics/muscat-plugin-kit/vite); keep any other plugins the project adds. A hand-written config drops the entry's default export from the build.",
|
|
52
|
+
"rb.toast takes one object: rb.toast({ message, type }).",
|
|
53
|
+
`Old components → new: ${Object.entries(LEGACY_COMPONENTS).map(([o, n]) => `${o} → ${n}`).join(", ")}. Their props differ (no event.detail) — call get_component_example for the new component before rewriting.`,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// Claude Code reads this to start the MCP server; `muscat-pk` hands off to the project's local kit.
|
|
57
|
+
export const MCP_JSON = `${JSON.stringify({ mcpServers: { muscat: { command: "muscat-pk", args: ["mcp"] } } }, null, 2)}\n`;
|
|
58
|
+
|
|
32
59
|
const nativeTable = () => NATIVE_TO_RB.map(([el, rb]) => `- ${el}: ${rb}`).join("\n");
|
|
33
60
|
|
|
34
61
|
export const SERVER_INSTRUCTIONS = `Muscat plugin UI (ui/*.tsx) is built only from @rainbow-robotics/plugin-ui/react, styled with the design-token Tailwind classes (list_design_tokens).
|
|
@@ -39,6 +66,9 @@ ${UI_RULES.map((r) => `- ${r}`).join("\n")}
|
|
|
39
66
|
Native element -> plugin-ui component:
|
|
40
67
|
${nativeTable()}
|
|
41
68
|
|
|
69
|
+
Upgrading a project made with an older kit:
|
|
70
|
+
${MIGRATION_RULES.map((r) => `- ${r}`).join("\n")}
|
|
71
|
+
|
|
42
72
|
Backend, manifest and SDK questions: search_rb_sdk, list_rb_sdk, get_rb_sdk_api.`;
|
|
43
73
|
|
|
44
74
|
// AGENTS.md (Codex, Cursor, Gemini CLI, Copilot …). CLAUDE.md imports it with `@AGENTS.md`.
|
|
@@ -64,6 +94,13 @@ ${UI_RULES.map((r) => `- ${r}`).join("\n")}
|
|
|
64
94
|
Native element → plugin-ui component:
|
|
65
95
|
|
|
66
96
|
${nativeTable()}
|
|
97
|
+
|
|
98
|
+
## Upgrading from an older kit
|
|
99
|
+
|
|
100
|
+
\`muscat-pk update\` bumps the kit, plugin-ui and preview-host and lists what the project must change.
|
|
101
|
+
The MCP tool \`check_project\` runs the same check (known old APIs + \`tsc --noEmit\`).
|
|
102
|
+
|
|
103
|
+
${MIGRATION_RULES.map((r) => `- ${r}`).join("\n")}
|
|
67
104
|
`;
|
|
68
105
|
}
|
|
69
106
|
|
package/cli/src/mcp.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import { PYTHON_DEV_IMAGE } from "./python.mjs";
|
|
|
43
43
|
import { SHIPPED_STORIES_SRC, SHIPPED_DESIGN_TOKENS } from "./storybook.mjs";
|
|
44
44
|
import { CPP_DEV_IMAGE } from "./cpp.mjs";
|
|
45
45
|
import { NATIVE_TO_RB, SERVER_INSTRUCTIONS } from "./ai-rules.mjs";
|
|
46
|
+
import { checkProject, formatProjectCheck } from "./projectCheck.mjs";
|
|
46
47
|
|
|
47
48
|
const TEMPLATE_READMES = { python: "common/README.md", cpp: "readme-cpp/README.md" };
|
|
48
49
|
// Design tokens: prefer the shipped copy, fall back to the monorepo source.
|
|
@@ -1090,6 +1091,35 @@ print(json.dumps(res))
|
|
|
1090
1091
|
},
|
|
1091
1092
|
);
|
|
1092
1093
|
|
|
1094
|
+
server.registerTool(
|
|
1095
|
+
"check_project",
|
|
1096
|
+
{
|
|
1097
|
+
title: "Check the plugin project for changes the installed plugin-ui needs",
|
|
1098
|
+
description:
|
|
1099
|
+
"List what the plugin project must change to work with the plugin-ui it has installed: old APIs " +
|
|
1100
|
+
"(connect(App), Slider/Text/Switch…, rb.toast(message, type), a vite.config without the muscat() " +
|
|
1101
|
+
"preset, entries without a default export) and `tsc --noEmit` errors, each as file:line. Same check " +
|
|
1102
|
+
"as `muscat-pk update`. Call it after an update or when the UI fails to build, fix each item " +
|
|
1103
|
+
"(get_component_example for replacement components), and call it again until it reports no issues.",
|
|
1104
|
+
inputSchema: {
|
|
1105
|
+
path: z.string().optional().describe("Plugin project directory (default: the server's working directory)"),
|
|
1106
|
+
},
|
|
1107
|
+
},
|
|
1108
|
+
async ({ path }) => {
|
|
1109
|
+
let root;
|
|
1110
|
+
try {
|
|
1111
|
+
root = findPluginRoot(path ? resolve(path) : process.cwd());
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
return { isError: true, content: [{ type: "text", text: err.message }] };
|
|
1114
|
+
}
|
|
1115
|
+
const lines = formatProjectCheck(checkProject(root));
|
|
1116
|
+
const text = lines.length
|
|
1117
|
+
? `${root}: ${lines.filter((l) => /:\d+ {2}/.test(l)).length} item(s) to fix\n${lines.join("\n")}`
|
|
1118
|
+
: `${root}: no issues — the project matches the installed plugin-ui.`;
|
|
1119
|
+
return { content: [{ type: "text", text }] };
|
|
1120
|
+
},
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1093
1123
|
const transport = new StdioServerTransport();
|
|
1094
1124
|
await server.connect(transport);
|
|
1095
1125
|
}
|
package/cli/src/new.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "node:fs";
|
|
14
14
|
import { basename, dirname, resolve } from "node:path";
|
|
15
15
|
import { color, ensureIdeVenv, hasBin, kitVersion, parseArgs, prompt, templatesDir, log } from "./util.mjs";
|
|
16
|
-
import { agentsMd, CLAUDE_MD } from "./ai-rules.mjs";
|
|
16
|
+
import { agentsMd, CLAUDE_MD, MCP_JSON } from "./ai-rules.mjs";
|
|
17
17
|
|
|
18
18
|
// Like cpSync(src, dst, {recursive:true}) but reads from the embedded snapshot
|
|
19
19
|
// when the templates dir doesn't exist on disk (compiled binary mode).
|
|
@@ -281,6 +281,7 @@ export async function runNew(args) {
|
|
|
281
281
|
// Rules for AI agents, generated from the same source as the MCP server's instructions.
|
|
282
282
|
writeFileSync(resolve(target, "AGENTS.md"), agentsMd({ pluginName }));
|
|
283
283
|
writeFileSync(resolve(target, "CLAUDE.md"), CLAUDE_MD);
|
|
284
|
+
writeFileSync(resolve(target, ".mcp.json"), MCP_JSON);
|
|
284
285
|
|
|
285
286
|
if (!opts["no-install"]) {
|
|
286
287
|
try {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// What a project must change for the plugin-ui it has installed: known old APIs (ai-rules.mjs) plus
|
|
2
|
+
// `tsc --noEmit`. Shared by `muscat-pk update` and the MCP tool check_project, so a user and an
|
|
3
|
+
// agent see the same list.
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join, relative, resolve } from "node:path";
|
|
7
|
+
import { LEGACY_COMPONENTS, LEGACY_SUBPATHS } from "./ai-rules.mjs";
|
|
8
|
+
|
|
9
|
+
const isBinaryMode = typeof Bun !== "undefined";
|
|
10
|
+
|
|
11
|
+
// The clause can't hold quotes or `;`, so a match can't start at an earlier import statement.
|
|
12
|
+
const PLUGIN_UI_IMPORT = /import\s+([^;'"]*?)\s+from\s+["']@rainbow-robotics\/plugin-ui\/([\w-]+)["']/g;
|
|
13
|
+
|
|
14
|
+
function listUiFiles(dir) {
|
|
15
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
16
|
+
e.isDirectory() ? listUiFiles(join(dir, e.name)) : /\.[jt]sx?$/.test(e.name) ? [join(dir, e.name)] : [],
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// rb.toast takes one { message, type } object now; flag a string or multi-argument call.
|
|
21
|
+
const TOAST_CALL = /\.toast\(([^()]*)\)/g;
|
|
22
|
+
|
|
23
|
+
/** Uses of the pre-1.x plugin-ui API in the project (ui/ and vite.config), as { file, line, text }. */
|
|
24
|
+
function findLegacyUiUsage(root) {
|
|
25
|
+
const uiDir = resolve(root, "ui");
|
|
26
|
+
if (!existsSync(uiDir)) return [];
|
|
27
|
+
const found = [];
|
|
28
|
+
// The old hand-written config builds entries without their exports (connect() rendered as a side
|
|
29
|
+
// effect), so `export default App` is dropped from dist. The muscat() preset keeps it.
|
|
30
|
+
const viteConfig = ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"].find((f) => existsSync(resolve(root, f)));
|
|
31
|
+
if (viteConfig && !readFileSync(resolve(root, viteConfig), "utf8").includes("@rainbow-robotics/muscat-plugin-kit/vite")) {
|
|
32
|
+
found.push({
|
|
33
|
+
file: viteConfig,
|
|
34
|
+
line: 1,
|
|
35
|
+
text: 'not using the muscat() preset — the build drops the entry\'s default export. Use: import { muscat } from "@rainbow-robotics/muscat-plugin-kit/vite"; defineConfig({ plugins: [muscat()] })',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
for (const file of listUiFiles(uiDir)) {
|
|
39
|
+
const src = readFileSync(file, "utf8");
|
|
40
|
+
const rel = relative(root, file);
|
|
41
|
+
const lineOf = (index) => src.slice(0, index).split("\n").length;
|
|
42
|
+
for (const m of src.matchAll(PLUGIN_UI_IMPORT)) {
|
|
43
|
+
const [, clause, subpath] = m;
|
|
44
|
+
if (LEGACY_SUBPATHS[subpath]) {
|
|
45
|
+
found.push({ file: rel, line: lineOf(m.index), text: `@rainbow-robotics/plugin-ui/${subpath} ${LEGACY_SUBPATHS[subpath]}` });
|
|
46
|
+
} else if (subpath === "react") {
|
|
47
|
+
const named = clause.match(/\{([\s\S]*)\}/)?.[1] ?? "";
|
|
48
|
+
for (const spec of named.split(",")) {
|
|
49
|
+
const name = spec.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0];
|
|
50
|
+
if (LEGACY_COMPONENTS[name]) {
|
|
51
|
+
found.push({ file: rel, line: lineOf(m.index), text: `${name} → ${LEGACY_COMPONENTS[name]}` });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const m of src.matchAll(TOAST_CALL)) {
|
|
57
|
+
const arg = m[1].trim();
|
|
58
|
+
if (!arg.startsWith("{") && (/^["'`]/.test(arg) || arg.includes(","))) {
|
|
59
|
+
found.push({ file: rel, line: lineOf(m.index), text: "rb.toast(message, type) → rb.toast({ message, type })" });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Top-level ui/ files are the entries; the host mounts their default export.
|
|
63
|
+
if (relative(uiDir, file).split(/[\\/]/).length === 1 && !/export\s+default\b/.test(src)) {
|
|
64
|
+
found.push({ file: rel, line: 1, text: "no default export — the host renders the entry's default export" });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The known-API list above only knows past changes. Type-checking against the plugin-ui that was
|
|
71
|
+
// just installed catches whatever a later release removes or reshapes, with no list to keep up.
|
|
72
|
+
function typeScriptErrors(root) {
|
|
73
|
+
const tsc = resolve(root, "node_modules/typescript/bin/tsc");
|
|
74
|
+
if (!existsSync(tsc) || !existsSync(resolve(root, "tsconfig.json"))) return [];
|
|
75
|
+
const runtime = isBinaryMode ? "node" : process.execPath;
|
|
76
|
+
const r = spawnSync(runtime, [tsc, "--noEmit", "-p", root, "--pretty", "false"], { cwd: root, encoding: "utf8" });
|
|
77
|
+
return `${r.stdout ?? ""}`
|
|
78
|
+
.split("\n")
|
|
79
|
+
.map((l) => /^(.+?)\((\d+),\d+\): error (TS\d+): (.*)$/.exec(l))
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.map(([, file, line, code, text]) => ({ file: relative(root, resolve(root, file)), line: Number(line), text: `${code} ${text}` }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Everything the project must change, as { file, line, text, source: "known" | "tsc" }. */
|
|
85
|
+
export function checkProject(root) {
|
|
86
|
+
return [
|
|
87
|
+
...findLegacyUiUsage(root).map((f) => ({ ...f, source: "known" })),
|
|
88
|
+
...typeScriptErrors(root).map((e) => ({ ...e, source: "tsc" })),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Plain-text lines for a checkProject() result (empty when there is nothing to fix). */
|
|
93
|
+
export function formatProjectCheck(items) {
|
|
94
|
+
if (!items.length) return [];
|
|
95
|
+
const line = (i) => ` ${i.file}:${i.line} ${i.text}`;
|
|
96
|
+
const tsc = items.filter((i) => i.source === "tsc");
|
|
97
|
+
return [
|
|
98
|
+
...items.filter((i) => i.source !== "tsc").map(line),
|
|
99
|
+
...(tsc.length ? [" TypeScript (tsc --noEmit):", ...tsc.map(line)] : []),
|
|
100
|
+
" For working examples, generate a fresh project and read its ui/content.tsx: muscat-pk new <dir>",
|
|
101
|
+
];
|
|
102
|
+
}
|
package/cli/src/update.mjs
CHANGED
|
@@ -11,9 +11,14 @@
|
|
|
11
11
|
// After updating the CLI itself, also bumps the plugin project's local deps
|
|
12
12
|
// and migrates the project structure if it's outdated (src/ → ui/, root main.py → py/).
|
|
13
13
|
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
14
|
-
import { chmodSync, existsSync, mkdirSync,
|
|
15
|
-
import { join,
|
|
16
|
-
import {
|
|
14
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
import { agentsMd, CLAUDE_MD, MCP_JSON } from "./ai-rules.mjs";
|
|
17
|
+
import { checkProject, formatProjectCheck } from "./projectCheck.mjs";
|
|
18
|
+
import {
|
|
19
|
+
compareKitVersions, findPluginRoot, isLocalKitInstall, KIT_PUBLISHED_NAME, kitVersion, log, parseArgs, prompt,
|
|
20
|
+
resolveLocalCliBin,
|
|
21
|
+
} from "./util.mjs";
|
|
17
22
|
|
|
18
23
|
const PLUGIN_UI_PACKAGE = "@rainbow-robotics/plugin-ui";
|
|
19
24
|
const PREVIEW_HOST_PACKAGE = "@rainbow-robotics/preview-host";
|
|
@@ -224,90 +229,59 @@ function updateLocalDeps(root, tag) {
|
|
|
224
229
|
}
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
"dist-browser": "removed — import from @rainbow-robotics/plugin-ui/react",
|
|
235
|
-
};
|
|
236
|
-
const LEGACY_COMPONENTS = {
|
|
237
|
-
Slider: "RBSlider",
|
|
238
|
-
Button: "RBLabelButton",
|
|
239
|
-
IconButton: "RBIconButton",
|
|
240
|
-
Text: "a plain element or RBTextLabel",
|
|
241
|
-
TextInput: "RBTextInput / RBTextField",
|
|
242
|
-
Switch: "RBSwitch",
|
|
243
|
-
Select: "RBSelectBox",
|
|
244
|
-
};
|
|
245
|
-
// The clause can't hold quotes or `;`, so a match can't start at an earlier import statement.
|
|
246
|
-
const PLUGIN_UI_IMPORT = /import\s+([^;'"]*?)\s+from\s+["']@rainbow-robotics\/plugin-ui\/([\w-]+)["']/g;
|
|
247
|
-
|
|
248
|
-
function listUiFiles(dir) {
|
|
249
|
-
return readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
250
|
-
e.isDirectory() ? listUiFiles(join(dir, e.name)) : /\.[jt]sx?$/.test(e.name) ? [join(dir, e.name)] : [],
|
|
251
|
-
);
|
|
232
|
+
const PROJECT_CHECK_HEADER = "This project needs changes for the updated plugin-ui. Fix these before `muscat-pk dev` / `build`:";
|
|
233
|
+
|
|
234
|
+
function reportProject(root) {
|
|
235
|
+
const lines = formatProjectCheck(checkProject(root));
|
|
236
|
+
if (!lines.length) return;
|
|
237
|
+
log.warn(PROJECT_CHECK_HEADER);
|
|
238
|
+
for (const l of lines) log.info(l);
|
|
252
239
|
}
|
|
253
240
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
for (const file of listUiFiles(uiDir)) {
|
|
273
|
-
const src = readFileSync(file, "utf8");
|
|
274
|
-
const rel = relative(root, file);
|
|
275
|
-
const lineOf = (index) => src.slice(0, index).split("\n").length;
|
|
276
|
-
for (const m of src.matchAll(PLUGIN_UI_IMPORT)) {
|
|
277
|
-
const [, clause, subpath] = m;
|
|
278
|
-
if (LEGACY_SUBPATHS[subpath]) {
|
|
279
|
-
found.push({ file: rel, line: lineOf(m.index), text: `@rainbow-robotics/plugin-ui/${subpath} ${LEGACY_SUBPATHS[subpath]}` });
|
|
280
|
-
} else if (subpath === "react") {
|
|
281
|
-
const named = clause.match(/\{([\s\S]*)\}/)?.[1] ?? "";
|
|
282
|
-
for (const spec of named.split(",")) {
|
|
283
|
-
const name = spec.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0];
|
|
284
|
-
if (LEGACY_COMPONENTS[name]) {
|
|
285
|
-
found.push({ file: rel, line: lineOf(m.index), text: `${name} → ${LEGACY_COMPONENTS[name]}` });
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
for (const m of src.matchAll(TOAST_CALL)) {
|
|
291
|
-
const arg = m[1].trim();
|
|
292
|
-
if (!arg.startsWith("{") && (/^["'`]/.test(arg) || arg.includes(","))) {
|
|
293
|
-
found.push({ file: rel, line: lineOf(m.index), text: "rb.toast(message, type) → rb.toast({ message, type })" });
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
// Top-level ui/ files are the entries; the host mounts their default export.
|
|
297
|
-
if (relative(uiDir, file).split(/[\\/]/).length === 1 && !/export\s+default\b/.test(src)) {
|
|
298
|
-
found.push({ file: rel, line: 1, text: "no default export — the host renders the entry's default export" });
|
|
299
|
-
}
|
|
241
|
+
/** `muscat-pk update:check` — the post-update project check, run by the freshly installed kit. */
|
|
242
|
+
export async function runUpdateCheck() {
|
|
243
|
+
reportProject(findPluginRoot());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Prefer the kit update just installed: it knows the plugin-ui it shipped with, this process may not.
|
|
247
|
+
function checkProjectAfterUpdate(root) {
|
|
248
|
+
const localBin = resolveLocalCliBin(root);
|
|
249
|
+
let localVersion = "";
|
|
250
|
+
try {
|
|
251
|
+
localVersion = JSON.parse(readFileSync(resolve(localBin, "../../../package.json"), "utf8")).version;
|
|
252
|
+
} catch { /* no local kit */ }
|
|
253
|
+
if (localBin && localVersion && compareKitVersions(localVersion, kitVersion()) >= 0) {
|
|
254
|
+
const runtime = isBinaryMode ? "node" : process.execPath;
|
|
255
|
+
const r = spawnSync(runtime, [localBin, "update:check"], { cwd: root, stdio: "inherit" });
|
|
256
|
+
if (!r.error && r.status === 0) return;
|
|
300
257
|
}
|
|
301
|
-
|
|
258
|
+
reportProject(root);
|
|
302
259
|
}
|
|
303
260
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
261
|
+
// ── AI agent files ────────────────────────────────────────────────────────────
|
|
262
|
+
// Projects made before `new` scaffolded these have no MCP server or agent rules, so an agent asked
|
|
263
|
+
// to fix the update can't call check_project. Only missing files are written; edited ones stay.
|
|
264
|
+
|
|
265
|
+
function pluginNameOf(root) {
|
|
266
|
+
try {
|
|
267
|
+
const { name } = JSON.parse(readFileSync(resolve(root, "manifest.json"), "utf8"));
|
|
268
|
+
if (typeof name === "string") return name;
|
|
269
|
+
if (name && typeof name === "object") return name.en ?? Object.values(name)[0] ?? "";
|
|
270
|
+
} catch { /* fall through */ }
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function ensureAiFiles(root) {
|
|
275
|
+
const files = {
|
|
276
|
+
".mcp.json": () => MCP_JSON,
|
|
277
|
+
"AGENTS.md": () => agentsMd({ pluginName: pluginNameOf(root) || "Plugin" }),
|
|
278
|
+
"CLAUDE.md": () => CLAUDE_MD,
|
|
279
|
+
};
|
|
280
|
+
for (const [file, content] of Object.entries(files)) {
|
|
281
|
+
if (existsSync(resolve(root, file))) continue;
|
|
282
|
+
writeFileSync(resolve(root, file), content());
|
|
283
|
+
log.ok(`${file} added (MCP server / AI agent rules)`);
|
|
284
|
+
}
|
|
311
285
|
}
|
|
312
286
|
|
|
313
287
|
// ── main ─────────────────────────────────────────────────────────────────────
|
|
@@ -336,7 +310,7 @@ export async function runUpdate(args = []) {
|
|
|
336
310
|
|
|
337
311
|
// 지금 도는 게 프로젝트의 로컬 설치면 그 프로젝트만 건드린다. 이 머신의 다른
|
|
338
312
|
// 프로젝트들이 쓰는 글로벌 CLI까지 같이 올리려면 --global을 명시해야 한다.
|
|
339
|
-
const runningLocal = isLocalKitInstall();
|
|
313
|
+
const runningLocal = isLocalKitInstall() || process.env.MUSCAT_PK_UPDATE_PROJECT_ONLY === "1";
|
|
340
314
|
|
|
341
315
|
// 1. Update the CLI itself
|
|
342
316
|
if (!runningLocal || opts.global) {
|
|
@@ -391,11 +365,17 @@ export async function runUpdate(args = []) {
|
|
|
391
365
|
log.warn(`Structure check failed: ${err.message}`);
|
|
392
366
|
}
|
|
393
367
|
|
|
368
|
+
try {
|
|
369
|
+
ensureAiFiles(root);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
log.warn(`AI agent files: ${err.message}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
394
374
|
updateLocalDeps(root, tag);
|
|
395
375
|
|
|
396
376
|
try {
|
|
397
|
-
|
|
377
|
+
checkProjectAfterUpdate(root);
|
|
398
378
|
} catch (err) {
|
|
399
|
-
log.warn(`
|
|
379
|
+
log.warn(`Project check failed: ${err.message}`);
|
|
400
380
|
}
|
|
401
381
|
}
|
package/cli/src/util.mjs
CHANGED
|
@@ -403,6 +403,18 @@ export function isNewer(a, b) {
|
|
|
403
403
|
return a3 > b3;
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/** Compare "x.y.z[-dev.N]" including the dev number (a release sorts after its dev builds). <0, 0, >0. */
|
|
407
|
+
export function compareKitVersions(a, b) {
|
|
408
|
+
const parse = (v) => {
|
|
409
|
+
const [rel, pre] = String(v).split("-");
|
|
410
|
+
const nums = rel.split(".").map((n) => parseInt(n, 10) || 0);
|
|
411
|
+
return [...nums, pre === undefined ? Infinity : parseInt(pre.split(".").pop(), 10) || 0];
|
|
412
|
+
};
|
|
413
|
+
const pa = parse(a), pb = parse(b);
|
|
414
|
+
for (let i = 0; i < 4; i++) if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1;
|
|
415
|
+
return 0;
|
|
416
|
+
}
|
|
417
|
+
|
|
406
418
|
/**
|
|
407
419
|
* Non-blocking check for a newer published kit. Prints an update suggestion if
|
|
408
420
|
* one exists. Fails silently when offline. Honors `RB_REGISTRY` and is skipped
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"generatedAt":
|
|
1
|
+
{"generatedAt":1792691105012,"userSince":1750997484097,"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}}}
|
|
@@ -1132,6 +1132,10 @@ letting it guess. `muscat-pk mcp` is an MCP server exposing:
|
|
|
1132
1132
|
backend) classes and signatures; with `lang: "cpp"`, the C++ SDK headers
|
|
1133
1133
|
(`rainbow-rb-sdk-dev`, read out of the `muscat-pk` cpp dev image — build it
|
|
1134
1134
|
once with `muscat-pk dev:back`).
|
|
1135
|
+
- `check_project` — what this project must change for the plugin-ui it has
|
|
1136
|
+
installed (old APIs such as `connect(App)` or `Slider`, a `vite.config` without
|
|
1137
|
+
the `muscat()` preset, `tsc` errors), the same list `muscat-pk update` prints.
|
|
1138
|
+
Ask the assistant to fix the project after an update; it re-checks until clean.
|
|
1135
1139
|
|
|
1136
1140
|
**Browse the components live** with `muscat-pk storybook`.
|
|
1137
1141
|
|
|
@@ -898,6 +898,10 @@ letting it guess. `muscat-pk mcp` is an MCP server exposing:
|
|
|
898
898
|
backend) classes and signatures; with `lang: "cpp"`, the C++ SDK headers
|
|
899
899
|
(`rainbow-rb-sdk-dev`, read out of the `muscat-pk` cpp dev image — build it
|
|
900
900
|
once with `muscat-pk dev:back`).
|
|
901
|
+
- `check_project` — what this project must change for the plugin-ui it has
|
|
902
|
+
installed (old APIs such as `connect(App)` or `Slider`, a `vite.config` without
|
|
903
|
+
the `muscat()` preset, `tsc` errors), the same list `muscat-pk update` prints.
|
|
904
|
+
Ask the assistant to fix the project after an update; it re-checks until clean.
|
|
901
905
|
|
|
902
906
|
**Browse the components live** with `muscat-pk storybook`.
|
|
903
907
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainbow-robotics/muscat-plugin-kit",
|
|
3
|
-
"version": "1.10.2-dev.
|
|
3
|
+
"version": "1.10.2-dev.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
55
|
-
"@rainbow-robotics/plugin-ui": "1.10.2-dev.
|
|
56
|
-
"@rainbow-robotics/preview-host": "1.10.2-dev.
|
|
55
|
+
"@rainbow-robotics/plugin-ui": "1.10.2-dev.3",
|
|
56
|
+
"@rainbow-robotics/preview-host": "1.10.2-dev.3",
|
|
57
57
|
"@types/react": "^19.0.10",
|
|
58
58
|
"postcss": "^8.5.8",
|
|
59
59
|
"typescript": "^5.8.0",
|