dominus-cli 0.5.6 → 0.5.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/dist/index.js +211 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +206 -4
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -34,14 +34,23 @@ var CliMsg = {
|
|
|
34
34
|
INSERT_INSTANCE: "cli:insert",
|
|
35
35
|
DELETE_INSTANCE: "cli:delete",
|
|
36
36
|
GET_SELECTION: "cli:get:selection",
|
|
37
|
+
SELECT: "cli:select",
|
|
37
38
|
SEARCH_SCRIPTS: "cli:search:scripts",
|
|
38
39
|
GET_OUTPUT: "cli:get:output",
|
|
39
40
|
RUN_TESTS: "cli:run:tests",
|
|
40
41
|
EXECUTE_RUN_TEST: "cli:test:run",
|
|
41
42
|
EXECUTE_PLAY_TEST: "cli:test:play",
|
|
43
|
+
END_TEST: "cli:test:end",
|
|
42
44
|
BUILD_UI: "cli:build:ui",
|
|
43
45
|
GET_REFLECTION: "cli:get:reflection",
|
|
44
|
-
|
|
46
|
+
UPLOAD_ASSET: "cli:upload:asset",
|
|
47
|
+
SERIALIZE_UI: "cli:serialize:ui",
|
|
48
|
+
MOVE_INSTANCE: "cli:move:instance",
|
|
49
|
+
UNDO: "cli:undo",
|
|
50
|
+
REDO: "cli:redo",
|
|
51
|
+
BULK_SET_PROPERTIES: "cli:bulk:set:properties",
|
|
52
|
+
FIND_REPLACE_SCRIPTS: "cli:find:replace:scripts"
|
|
53
|
+
};
|
|
45
54
|
function createMessage(type, payload) {
|
|
46
55
|
return { id: nanoid(), type, payload, ts: Date.now() };
|
|
47
56
|
}
|
|
@@ -661,8 +670,27 @@ These are built-in lessons that ship with Dominus. Follow them exactly.
|
|
|
661
670
|
- Full UI tree \u2192 \`create_ui\` (one call, never loop \`insert_instance\` for UI)
|
|
662
671
|
- **Convert/export existing UI \u2192 \`serialize_ui\`** (NOT get_descendants_properties)
|
|
663
672
|
- Adjusting an existing instance \u2192 \`set_properties\` (not \`run_code\`)
|
|
673
|
+
- **Inspecting user's selection \u2192 \`get_selection\`, then \`get_properties\` or \`serialize_ui\`**
|
|
674
|
+
- **Reading properties \u2192 \`get_properties\` (NEVER \`run_code\` with dump scripts)**
|
|
675
|
+
- **Browsing the tree \u2192 \`get_explorer\`**
|
|
664
676
|
- Anything exotic or procedural \u2192 \`run_code\` as last resort
|
|
665
677
|
|
|
678
|
+
### NEVER use run_code for:
|
|
679
|
+
- Getting properties of an instance \u2192 use \`get_properties\` or \`serialize_ui\`
|
|
680
|
+
- Dumping selection info \u2192 use \`inspect_selection\` (ONE call does it all)
|
|
681
|
+
- Inspecting an instance + its children \u2192 use \`inspect_instance\`
|
|
682
|
+
- Converting UI to Roact/React \u2192 use \`convert_ui_to_code\`
|
|
683
|
+
- Creating instances \u2192 use \`create_part\`, \`create_ui\`, \`insert_instance\`, \`build_multiple\`
|
|
684
|
+
- Modifying properties \u2192 use \`set_properties\` or \`bulk_set_properties\`
|
|
685
|
+
- Reading scripts \u2192 use \`read_script\`
|
|
686
|
+
- Browsing the explorer \u2192 use \`get_explorer\`
|
|
687
|
+
\`run_code\` is ONLY for procedural logic, terrain generation, raycasts, physics queries, or custom algorithms with no tool equivalent.
|
|
688
|
+
|
|
689
|
+
### Composite skill tools (prefer these over chaining)
|
|
690
|
+
- \`inspect_selection\` \u2014 Returns paths + compact properties for all selected instances in ONE call. Use when user says "my selection" or "what I selected".
|
|
691
|
+
- \`inspect_instance\` \u2014 Returns compact properties + children tree for a single instance in ONE call. Combines get_properties + get_explorer.
|
|
692
|
+
- \`convert_ui_to_code\` \u2014 Serializes a UI tree and generates a Roact/React component. Use when user says "turn this into a component" or "convert to React".
|
|
693
|
+
|
|
666
694
|
### Verification habits
|
|
667
695
|
- After a build, call \`get_explorer\` or \`get_selection\` to confirm the tree looks right.
|
|
668
696
|
- After editing a script, read it back.
|
|
@@ -703,7 +731,7 @@ async function startMcpServer() {
|
|
|
703
731
|
}
|
|
704
732
|
const mcp = new McpServer({
|
|
705
733
|
name: "dominus",
|
|
706
|
-
version: "0.5.
|
|
734
|
+
version: "0.5.8"
|
|
707
735
|
}, {
|
|
708
736
|
instructions: INTERNAL_MEMORY
|
|
709
737
|
});
|
|
@@ -716,6 +744,96 @@ async function startMcpServer() {
|
|
|
716
744
|
for (const p of parts) r += `[${luaStr(p)}]`;
|
|
717
745
|
return r;
|
|
718
746
|
}
|
|
747
|
+
function generateComponentCode(tree, componentName, framework) {
|
|
748
|
+
const isReact = framework === "react";
|
|
749
|
+
const lib = isReact ? "React" : "Roact";
|
|
750
|
+
const lines = [];
|
|
751
|
+
lines.push(`local ${lib} = require(game.ReplicatedStorage.Packages.${isReact ? "React" : "Roact"})`);
|
|
752
|
+
if (isReact) {
|
|
753
|
+
lines.push(`local e = React.createElement`);
|
|
754
|
+
} else {
|
|
755
|
+
lines.push(`local e = Roact.createElement`);
|
|
756
|
+
}
|
|
757
|
+
lines.push("");
|
|
758
|
+
lines.push(`local function ${componentName}()`);
|
|
759
|
+
lines.push(` return ${renderNode(tree, 1)}`);
|
|
760
|
+
lines.push("end");
|
|
761
|
+
lines.push("");
|
|
762
|
+
lines.push(`return ${componentName}`);
|
|
763
|
+
return lines.join("\n");
|
|
764
|
+
}
|
|
765
|
+
function renderNode(node, depth) {
|
|
766
|
+
const indent = " ".repeat(depth);
|
|
767
|
+
const indent2 = " ".repeat(depth + 1);
|
|
768
|
+
const className = node.ClassName || "Frame";
|
|
769
|
+
const children = node.Children || [];
|
|
770
|
+
node.Name;
|
|
771
|
+
const props = [];
|
|
772
|
+
const skipKeys = /* @__PURE__ */ new Set(["ClassName", "Name", "Children", "className", "name", "children"]);
|
|
773
|
+
for (const [key, val] of Object.entries(node)) {
|
|
774
|
+
if (skipKeys.has(key)) continue;
|
|
775
|
+
props.push(`${indent2}${key} = ${luaValue(val, depth + 1)},`);
|
|
776
|
+
}
|
|
777
|
+
const childEntries = [];
|
|
778
|
+
for (const child of children) {
|
|
779
|
+
const childName = child.Name || child.ClassName || "Child";
|
|
780
|
+
childEntries.push(`${indent2}${childName} = ${renderNode(child, depth + 1)},`);
|
|
781
|
+
}
|
|
782
|
+
const hasProps = props.length > 0;
|
|
783
|
+
const hasChildren = childEntries.length > 0;
|
|
784
|
+
if (!hasProps && !hasChildren) {
|
|
785
|
+
return `e("${className}", {})`;
|
|
786
|
+
}
|
|
787
|
+
const parts = [];
|
|
788
|
+
parts.push(`e("${className}", {`);
|
|
789
|
+
if (hasProps) {
|
|
790
|
+
parts.push(...props);
|
|
791
|
+
}
|
|
792
|
+
if (hasChildren) {
|
|
793
|
+
parts.push(`${indent2}}, {`);
|
|
794
|
+
parts.push(...childEntries);
|
|
795
|
+
parts.push(`${indent}})`);
|
|
796
|
+
} else {
|
|
797
|
+
parts.push(`${indent}})`);
|
|
798
|
+
}
|
|
799
|
+
return parts.join("\n");
|
|
800
|
+
}
|
|
801
|
+
function luaValue(val, _depth) {
|
|
802
|
+
if (val === null || val === void 0) return "nil";
|
|
803
|
+
if (typeof val === "boolean") return String(val);
|
|
804
|
+
if (typeof val === "number") return String(val);
|
|
805
|
+
if (typeof val === "string") {
|
|
806
|
+
if (val.startsWith("#") && val.length === 7) {
|
|
807
|
+
return `Color3.fromHex("${val}")`;
|
|
808
|
+
}
|
|
809
|
+
if (/^[A-Z][a-zA-Z]+$/.test(val)) {
|
|
810
|
+
return `"${val}"`;
|
|
811
|
+
}
|
|
812
|
+
return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
|
|
813
|
+
}
|
|
814
|
+
if (Array.isArray(val)) {
|
|
815
|
+
if (val.length === 4 && val.every((v) => typeof v === "number")) {
|
|
816
|
+
return `UDim2.new(${val.join(", ")})`;
|
|
817
|
+
}
|
|
818
|
+
if (val.length === 2 && val.every((v) => typeof v === "number")) {
|
|
819
|
+
return `Vector2.new(${val.join(", ")})`;
|
|
820
|
+
}
|
|
821
|
+
if (val.length === 3 && val.every((v) => typeof v === "number")) {
|
|
822
|
+
const allSmall = val.every((v) => v <= 1);
|
|
823
|
+
if (allSmall) return `Color3.new(${val.join(", ")})`;
|
|
824
|
+
return `Color3.fromRGB(${val.join(", ")})`;
|
|
825
|
+
}
|
|
826
|
+
return `{${val.map((v) => luaValue(v, _depth)).join(", ")}}`;
|
|
827
|
+
}
|
|
828
|
+
if (typeof val === "object") {
|
|
829
|
+
const entries = Object.entries(val);
|
|
830
|
+
if (entries.length === 0) return "{}";
|
|
831
|
+
return `{
|
|
832
|
+
${entries.map(([k, v]) => `${" ".repeat(_depth + 1)}${k} = ${luaValue(v, _depth + 1)}`).join(",\n")}
|
|
833
|
+
${" ".repeat(_depth)}}`;
|
|
834
|
+
}
|
|
835
|
+
return String(val);
|
|
836
|
+
}
|
|
719
837
|
mcp.tool(
|
|
720
838
|
"read_script",
|
|
721
839
|
"Read the source code of a script in Roblox Studio",
|
|
@@ -741,7 +859,7 @@ async function startMcpServer() {
|
|
|
741
859
|
);
|
|
742
860
|
mcp.tool(
|
|
743
861
|
"run_code",
|
|
744
|
-
"Execute Luau code in Roblox Studio
|
|
862
|
+
"Execute arbitrary Luau code in Roblox Studio. LAST RESORT \u2014 only use when no dedicated tool exists. Do NOT use for: reading properties (use get_properties/inspect_selection/serialize_ui), creating instances (use create_part/create_ui/insert_instance/build_multiple), modifying properties (use set_properties/bulk_set_properties), or inspecting the tree (use get_explorer). Valid uses: complex procedural generation, terrain algorithms, raycasting, physics queries, custom logic with no tool equivalent.",
|
|
745
863
|
{ code: z.string().describe("Luau code to execute") },
|
|
746
864
|
async ({ code }) => {
|
|
747
865
|
assertConnected();
|
|
@@ -837,7 +955,7 @@ async function startMcpServer() {
|
|
|
837
955
|
);
|
|
838
956
|
mcp.tool(
|
|
839
957
|
"get_selection",
|
|
840
|
-
|
|
958
|
+
'Get the currently selected instances in Roblox Studio. Returns paths and class names of all selected objects. Use this FIRST when the user says "my selection" or "what I have selected". Then use get_properties or serialize_ui on the returned paths to inspect details.',
|
|
841
959
|
{},
|
|
842
960
|
async () => {
|
|
843
961
|
assertConnected();
|
|
@@ -1112,6 +1230,90 @@ async function startMcpServer() {
|
|
|
1112
1230
|
return { content: [{ type: "text", text: JSON.stringify(res.payload.tree, null, 2) }] };
|
|
1113
1231
|
}
|
|
1114
1232
|
);
|
|
1233
|
+
mcp.tool(
|
|
1234
|
+
"inspect_selection",
|
|
1235
|
+
`Inspect the user's current selection in Roblox Studio. Returns the selected instance paths AND their properties (compact) in a single call. Use this FIRST when the user says "my selection", "what I selected", or "the selected thing". Much better than calling get_selection + get_properties separately.`,
|
|
1236
|
+
{},
|
|
1237
|
+
async () => {
|
|
1238
|
+
assertConnected();
|
|
1239
|
+
const selRes = await bridge.sendRequest(CliMsg.GET_SELECTION, {});
|
|
1240
|
+
const paths = selRes.payload?.paths ?? [];
|
|
1241
|
+
if (paths.length === 0) {
|
|
1242
|
+
return { content: [{ type: "text", text: "No instances selected in Studio." }] };
|
|
1243
|
+
}
|
|
1244
|
+
const results = {};
|
|
1245
|
+
for (const p of paths.slice(0, 10)) {
|
|
1246
|
+
try {
|
|
1247
|
+
const propRes = await bridge.sendRequest(
|
|
1248
|
+
CliMsg.GET_PROPERTIES,
|
|
1249
|
+
{ path: p, compact: true }
|
|
1250
|
+
);
|
|
1251
|
+
results[p] = propRes.payload.properties ?? propRes.payload;
|
|
1252
|
+
} catch {
|
|
1253
|
+
results[p] = { error: "Failed to read properties" };
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
if (paths.length > 10) {
|
|
1257
|
+
results["_truncated"] = `${paths.length - 10} more instances not shown`;
|
|
1258
|
+
}
|
|
1259
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
mcp.tool(
|
|
1263
|
+
"inspect_instance",
|
|
1264
|
+
"Inspect a single instance: returns its compact properties AND its children tree in one call. Combines get_properties + get_explorer into a single round-trip. Use when you need to understand an instance and its subtree.",
|
|
1265
|
+
{
|
|
1266
|
+
path: z.string().describe('Full instance path (e.g. "Workspace.MyModel")'),
|
|
1267
|
+
childDepth: z.number().optional().default(2).describe("How deep to show the children tree (default 2)")
|
|
1268
|
+
},
|
|
1269
|
+
async ({ path: path2, childDepth }) => {
|
|
1270
|
+
assertConnected();
|
|
1271
|
+
const [propRes, treeRes] = await Promise.all([
|
|
1272
|
+
bridge.sendRequest(
|
|
1273
|
+
CliMsg.GET_PROPERTIES,
|
|
1274
|
+
{ path: path2, compact: true }
|
|
1275
|
+
),
|
|
1276
|
+
bridge.sendRequest(
|
|
1277
|
+
CliMsg.GET_EXPLORER,
|
|
1278
|
+
{ rootPath: path2, maxDepth: childDepth ?? 2 }
|
|
1279
|
+
)
|
|
1280
|
+
]);
|
|
1281
|
+
return {
|
|
1282
|
+
content: [{
|
|
1283
|
+
type: "text",
|
|
1284
|
+
text: JSON.stringify({
|
|
1285
|
+
properties: propRes.payload.properties ?? propRes.payload,
|
|
1286
|
+
children: treeRes.payload.tree ?? treeRes.payload
|
|
1287
|
+
}, null, 2)
|
|
1288
|
+
}]
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
);
|
|
1292
|
+
mcp.tool(
|
|
1293
|
+
"convert_ui_to_code",
|
|
1294
|
+
'Convert an existing Roblox UI tree into Roact/React-lua component code. Serializes the UI at the given path, then generates a ready-to-use Luau component. Use when the user asks to "turn this into a component", "convert to React", or "make a Roact version".',
|
|
1295
|
+
{
|
|
1296
|
+
path: z.string().describe('Path to the UI instance to convert (e.g. "StarterGui.MyScreenGui.MainFrame")'),
|
|
1297
|
+
framework: z.enum(["roact", "react"]).optional().default("react").describe('Target framework: "roact" (legacy) or "react" (react-lua, default)'),
|
|
1298
|
+
componentName: z.string().optional().describe("Name for the generated component (defaults to instance Name)")
|
|
1299
|
+
},
|
|
1300
|
+
async ({ path: path2, framework, componentName }) => {
|
|
1301
|
+
assertConnected();
|
|
1302
|
+
const res = await bridge.sendRequest(
|
|
1303
|
+
CliMsg.SERIALIZE_UI,
|
|
1304
|
+
{ path: path2, maxDepth: 50 },
|
|
1305
|
+
1e3 * 60 * 2
|
|
1306
|
+
);
|
|
1307
|
+
if (!res.payload.success || !res.payload.tree) {
|
|
1308
|
+
return { content: [{ type: "text", text: `Error serializing UI: ${res.payload.error ?? "Unknown error"}` }] };
|
|
1309
|
+
}
|
|
1310
|
+
const tree = res.payload.tree;
|
|
1311
|
+
const name = componentName || tree.Name || "GeneratedComponent";
|
|
1312
|
+
const fw = framework || "react";
|
|
1313
|
+
const code = generateComponentCode(tree, name, fw);
|
|
1314
|
+
return { content: [{ type: "text", text: code }] };
|
|
1315
|
+
}
|
|
1316
|
+
);
|
|
1115
1317
|
mcp.tool(
|
|
1116
1318
|
"recall_memory",
|
|
1117
1319
|
"Search persistent memory for facts about the current Roblox project",
|