dominus-cli 0.5.7 → 0.5.9
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 +194 -6
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +199 -4
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -687,13 +687,20 @@ These are built-in lessons that ship with Dominus. Follow them exactly.
|
|
|
687
687
|
|
|
688
688
|
### NEVER use run_code for:
|
|
689
689
|
- Getting properties of an instance \u2192 use \`get_properties\` or \`serialize_ui\`
|
|
690
|
-
- Dumping selection info \u2192 use \`
|
|
690
|
+
- Dumping selection info \u2192 use \`inspect_selection\` (ONE call does it all)
|
|
691
|
+
- Inspecting an instance + its children \u2192 use \`inspect_instance\`
|
|
692
|
+
- Converting UI to Roact/React \u2192 use \`convert_ui_to_code\`
|
|
691
693
|
- Creating instances \u2192 use \`create_part\`, \`create_ui\`, \`insert_instance\`, \`build_multiple\`
|
|
692
694
|
- Modifying properties \u2192 use \`set_properties\` or \`bulk_set_properties\`
|
|
693
695
|
- Reading scripts \u2192 use \`read_script\`
|
|
694
696
|
- Browsing the explorer \u2192 use \`get_explorer\`
|
|
695
697
|
\`run_code\` is ONLY for procedural logic, terrain generation, raycasts, physics queries, or custom algorithms with no tool equivalent.
|
|
696
698
|
|
|
699
|
+
### Composite skill tools (prefer these over chaining)
|
|
700
|
+
- \`inspect_selection\` \u2014 Returns paths + compact properties for all selected instances in ONE call. Use when user says "my selection" or "what I selected".
|
|
701
|
+
- \`inspect_instance\` \u2014 Returns compact properties + children tree for a single instance in ONE call. Combines get_properties + get_explorer.
|
|
702
|
+
- \`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".
|
|
703
|
+
|
|
697
704
|
### Verification habits
|
|
698
705
|
- After a build, call \`get_explorer\` or \`get_selection\` to confirm the tree looks right.
|
|
699
706
|
- After editing a script, read it back.
|
|
@@ -2598,7 +2605,7 @@ async function startMcpServer() {
|
|
|
2598
2605
|
}
|
|
2599
2606
|
const mcp = new McpServer({
|
|
2600
2607
|
name: "dominus",
|
|
2601
|
-
version: "0.5.
|
|
2608
|
+
version: "0.5.8"
|
|
2602
2609
|
}, {
|
|
2603
2610
|
instructions: INTERNAL_MEMORY
|
|
2604
2611
|
});
|
|
@@ -2611,6 +2618,101 @@ async function startMcpServer() {
|
|
|
2611
2618
|
for (const p of parts) r += `[${luaStr(p)}]`;
|
|
2612
2619
|
return r;
|
|
2613
2620
|
}
|
|
2621
|
+
function cleanNum(n) {
|
|
2622
|
+
if (Number.isInteger(n)) return String(n);
|
|
2623
|
+
const rounded = Math.round(n * 1e3) / 1e3;
|
|
2624
|
+
return String(rounded);
|
|
2625
|
+
}
|
|
2626
|
+
function generateComponentCode(tree, componentName, framework) {
|
|
2627
|
+
const isReact = framework === "react";
|
|
2628
|
+
const lib = isReact ? "React" : "Roact";
|
|
2629
|
+
const lines = [];
|
|
2630
|
+
lines.push(`local ${lib} = require(game.ReplicatedStorage.Packages.${isReact ? "React" : "Roact"})`);
|
|
2631
|
+
if (isReact) {
|
|
2632
|
+
lines.push(`local e = React.createElement`);
|
|
2633
|
+
} else {
|
|
2634
|
+
lines.push(`local e = Roact.createElement`);
|
|
2635
|
+
}
|
|
2636
|
+
lines.push("");
|
|
2637
|
+
lines.push(`local function ${componentName}()`);
|
|
2638
|
+
lines.push(` return ${renderNode(tree, 1)}`);
|
|
2639
|
+
lines.push("end");
|
|
2640
|
+
lines.push("");
|
|
2641
|
+
lines.push(`return ${componentName}`);
|
|
2642
|
+
return lines.join("\n");
|
|
2643
|
+
}
|
|
2644
|
+
function renderNode(node, depth) {
|
|
2645
|
+
const indent = " ".repeat(depth);
|
|
2646
|
+
const indent2 = " ".repeat(depth + 1);
|
|
2647
|
+
const className = node.ClassName || "Frame";
|
|
2648
|
+
const children = node.Children || [];
|
|
2649
|
+
node.Name;
|
|
2650
|
+
const props = [];
|
|
2651
|
+
const skipKeys = /* @__PURE__ */ new Set(["ClassName", "Name", "Children", "className", "name", "children"]);
|
|
2652
|
+
for (const [key, val] of Object.entries(node)) {
|
|
2653
|
+
if (skipKeys.has(key)) continue;
|
|
2654
|
+
props.push(`${indent2}${key} = ${luaValue(val, depth + 1)},`);
|
|
2655
|
+
}
|
|
2656
|
+
const childEntries = [];
|
|
2657
|
+
for (const child of children) {
|
|
2658
|
+
const childName = child.Name || child.ClassName || "Child";
|
|
2659
|
+
childEntries.push(`${indent2}${childName} = ${renderNode(child, depth + 1)},`);
|
|
2660
|
+
}
|
|
2661
|
+
const hasProps = props.length > 0;
|
|
2662
|
+
const hasChildren = childEntries.length > 0;
|
|
2663
|
+
if (!hasProps && !hasChildren) {
|
|
2664
|
+
return `e("${className}", {})`;
|
|
2665
|
+
}
|
|
2666
|
+
const parts = [];
|
|
2667
|
+
parts.push(`e("${className}", {`);
|
|
2668
|
+
if (hasProps) {
|
|
2669
|
+
parts.push(...props);
|
|
2670
|
+
}
|
|
2671
|
+
if (hasChildren) {
|
|
2672
|
+
parts.push(`${indent2}}, {`);
|
|
2673
|
+
parts.push(...childEntries);
|
|
2674
|
+
parts.push(`${indent}})`);
|
|
2675
|
+
} else {
|
|
2676
|
+
parts.push(`${indent}})`);
|
|
2677
|
+
}
|
|
2678
|
+
return parts.join("\n");
|
|
2679
|
+
}
|
|
2680
|
+
function luaValue(val, _depth) {
|
|
2681
|
+
if (val === null || val === void 0) return "nil";
|
|
2682
|
+
if (typeof val === "boolean") return String(val);
|
|
2683
|
+
if (typeof val === "number") return cleanNum(val);
|
|
2684
|
+
if (typeof val === "string") {
|
|
2685
|
+
if (val.startsWith("#") && val.length === 7) {
|
|
2686
|
+
return `Color3.fromHex("${val}")`;
|
|
2687
|
+
}
|
|
2688
|
+
if (/^[A-Z][a-zA-Z]+$/.test(val)) {
|
|
2689
|
+
return `"${val}"`;
|
|
2690
|
+
}
|
|
2691
|
+
return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
|
|
2692
|
+
}
|
|
2693
|
+
if (Array.isArray(val)) {
|
|
2694
|
+
if (val.length === 4 && val.every((v) => typeof v === "number")) {
|
|
2695
|
+
return `UDim2.new(${val.map(cleanNum).join(", ")})`;
|
|
2696
|
+
}
|
|
2697
|
+
if (val.length === 2 && val.every((v) => typeof v === "number")) {
|
|
2698
|
+
return `Vector2.new(${val.map(cleanNum).join(", ")})`;
|
|
2699
|
+
}
|
|
2700
|
+
if (val.length === 3 && val.every((v) => typeof v === "number")) {
|
|
2701
|
+
const allSmall = val.every((v) => v <= 1);
|
|
2702
|
+
if (allSmall) return `Color3.new(${val.map(cleanNum).join(", ")})`;
|
|
2703
|
+
return `Color3.fromRGB(${val.map((v) => String(Math.round(v))).join(", ")})`;
|
|
2704
|
+
}
|
|
2705
|
+
return `{${val.map((v) => luaValue(v, _depth)).join(", ")}}`;
|
|
2706
|
+
}
|
|
2707
|
+
if (typeof val === "object") {
|
|
2708
|
+
const entries = Object.entries(val);
|
|
2709
|
+
if (entries.length === 0) return "{}";
|
|
2710
|
+
return `{
|
|
2711
|
+
${entries.map(([k, v]) => `${" ".repeat(_depth + 1)}${k} = ${luaValue(v, _depth + 1)}`).join(",\n")}
|
|
2712
|
+
${" ".repeat(_depth)}}`;
|
|
2713
|
+
}
|
|
2714
|
+
return String(val);
|
|
2715
|
+
}
|
|
2614
2716
|
mcp.tool(
|
|
2615
2717
|
"read_script",
|
|
2616
2718
|
"Read the source code of a script in Roblox Studio",
|
|
@@ -2636,7 +2738,7 @@ async function startMcpServer() {
|
|
|
2636
2738
|
);
|
|
2637
2739
|
mcp.tool(
|
|
2638
2740
|
"run_code",
|
|
2639
|
-
"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/
|
|
2741
|
+
"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.",
|
|
2640
2742
|
{ code: z.string().describe("Luau code to execute") },
|
|
2641
2743
|
async ({ code }) => {
|
|
2642
2744
|
assertConnected();
|
|
@@ -3007,6 +3109,90 @@ async function startMcpServer() {
|
|
|
3007
3109
|
return { content: [{ type: "text", text: JSON.stringify(res.payload.tree, null, 2) }] };
|
|
3008
3110
|
}
|
|
3009
3111
|
);
|
|
3112
|
+
mcp.tool(
|
|
3113
|
+
"inspect_selection",
|
|
3114
|
+
`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.`,
|
|
3115
|
+
{},
|
|
3116
|
+
async () => {
|
|
3117
|
+
assertConnected();
|
|
3118
|
+
const selRes = await bridge.sendRequest(CliMsg.GET_SELECTION, {});
|
|
3119
|
+
const paths = selRes.payload?.paths ?? [];
|
|
3120
|
+
if (paths.length === 0) {
|
|
3121
|
+
return { content: [{ type: "text", text: "No instances selected in Studio." }] };
|
|
3122
|
+
}
|
|
3123
|
+
const results = {};
|
|
3124
|
+
for (const p of paths.slice(0, 10)) {
|
|
3125
|
+
try {
|
|
3126
|
+
const propRes = await bridge.sendRequest(
|
|
3127
|
+
CliMsg.GET_PROPERTIES,
|
|
3128
|
+
{ path: p, compact: true }
|
|
3129
|
+
);
|
|
3130
|
+
results[p] = propRes.payload.properties ?? propRes.payload;
|
|
3131
|
+
} catch {
|
|
3132
|
+
results[p] = { error: "Failed to read properties" };
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
if (paths.length > 10) {
|
|
3136
|
+
results["_truncated"] = `${paths.length - 10} more instances not shown`;
|
|
3137
|
+
}
|
|
3138
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
3139
|
+
}
|
|
3140
|
+
);
|
|
3141
|
+
mcp.tool(
|
|
3142
|
+
"inspect_instance",
|
|
3143
|
+
"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.",
|
|
3144
|
+
{
|
|
3145
|
+
path: z.string().describe('Full instance path (e.g. "Workspace.MyModel")'),
|
|
3146
|
+
childDepth: z.number().optional().default(2).describe("How deep to show the children tree (default 2)")
|
|
3147
|
+
},
|
|
3148
|
+
async ({ path: path5, childDepth }) => {
|
|
3149
|
+
assertConnected();
|
|
3150
|
+
const [propRes, treeRes] = await Promise.all([
|
|
3151
|
+
bridge.sendRequest(
|
|
3152
|
+
CliMsg.GET_PROPERTIES,
|
|
3153
|
+
{ path: path5, compact: true }
|
|
3154
|
+
),
|
|
3155
|
+
bridge.sendRequest(
|
|
3156
|
+
CliMsg.GET_EXPLORER,
|
|
3157
|
+
{ rootPath: path5, maxDepth: childDepth ?? 2 }
|
|
3158
|
+
)
|
|
3159
|
+
]);
|
|
3160
|
+
return {
|
|
3161
|
+
content: [{
|
|
3162
|
+
type: "text",
|
|
3163
|
+
text: JSON.stringify({
|
|
3164
|
+
properties: propRes.payload.properties ?? propRes.payload,
|
|
3165
|
+
children: treeRes.payload.tree ?? treeRes.payload
|
|
3166
|
+
}, null, 2)
|
|
3167
|
+
}]
|
|
3168
|
+
};
|
|
3169
|
+
}
|
|
3170
|
+
);
|
|
3171
|
+
mcp.tool(
|
|
3172
|
+
"convert_ui_to_code",
|
|
3173
|
+
'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".',
|
|
3174
|
+
{
|
|
3175
|
+
path: z.string().describe('Path to the UI instance to convert (e.g. "StarterGui.MyScreenGui.MainFrame")'),
|
|
3176
|
+
framework: z.enum(["roact", "react"]).optional().default("react").describe('Target framework: "roact" (legacy) or "react" (react-lua, default)'),
|
|
3177
|
+
componentName: z.string().optional().describe("Name for the generated component (defaults to instance Name)")
|
|
3178
|
+
},
|
|
3179
|
+
async ({ path: path5, framework, componentName }) => {
|
|
3180
|
+
assertConnected();
|
|
3181
|
+
const res = await bridge.sendRequest(
|
|
3182
|
+
CliMsg.SERIALIZE_UI,
|
|
3183
|
+
{ path: path5, maxDepth: 50 },
|
|
3184
|
+
1e3 * 60 * 2
|
|
3185
|
+
);
|
|
3186
|
+
if (!res.payload.success || !res.payload.tree) {
|
|
3187
|
+
return { content: [{ type: "text", text: `Error serializing UI: ${res.payload.error ?? "Unknown error"}` }] };
|
|
3188
|
+
}
|
|
3189
|
+
const tree = res.payload.tree;
|
|
3190
|
+
const name = componentName || tree.Name || "GeneratedComponent";
|
|
3191
|
+
const fw = framework || "react";
|
|
3192
|
+
const code = generateComponentCode(tree, name, fw);
|
|
3193
|
+
return { content: [{ type: "text", text: code }] };
|
|
3194
|
+
}
|
|
3195
|
+
);
|
|
3010
3196
|
mcp.tool(
|
|
3011
3197
|
"recall_memory",
|
|
3012
3198
|
"Search persistent memory for facts about the current Roblox project",
|
|
@@ -4273,14 +4459,16 @@ You have direct access to Roblox Studio through tools. You can read/edit scripts
|
|
|
4273
4459
|
|
|
4274
4460
|
## CRITICAL: NEVER use run_code for tasks that have a dedicated tool
|
|
4275
4461
|
run_code is a LAST RESORT. You have 30+ specialized tools \u2014 use them.
|
|
4276
|
-
- Reading properties \u2192 get_properties,
|
|
4277
|
-
- Inspecting selection \u2192
|
|
4462
|
+
- Reading properties \u2192 get_properties, get_descendants_properties
|
|
4463
|
+
- Inspecting selection \u2192 inspect_selection (ONE call returns paths + properties)
|
|
4464
|
+
- Inspecting an instance \u2192 inspect_instance (ONE call returns properties + children)
|
|
4465
|
+
- Converting UI to code \u2192 convert_ui_to_code (ONE call serializes + generates component)
|
|
4278
4466
|
- Creating parts \u2192 create_part, build_multiple
|
|
4279
4467
|
- Creating UI \u2192 create_ui
|
|
4280
4468
|
- Modifying properties \u2192 set_properties, bulk_set_properties
|
|
4281
4469
|
- Browsing the tree \u2192 get_explorer
|
|
4282
4470
|
- Reading/editing scripts \u2192 read_script, edit_script
|
|
4283
|
-
-
|
|
4471
|
+
- Serializing UI \u2192 serialize_ui
|
|
4284
4472
|
Only use run_code for: procedural generation, terrain algorithms, raycasting, physics queries, or other logic that no tool covers.
|
|
4285
4473
|
|
|
4286
4474
|
When the user asks you to do something, you should:
|