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 CHANGED
@@ -680,8 +680,27 @@ These are built-in lessons that ship with Dominus. Follow them exactly.
680
680
  - Full UI tree \u2192 \`create_ui\` (one call, never loop \`insert_instance\` for UI)
681
681
  - **Convert/export existing UI \u2192 \`serialize_ui\`** (NOT get_descendants_properties)
682
682
  - Adjusting an existing instance \u2192 \`set_properties\` (not \`run_code\`)
683
+ - **Inspecting user's selection \u2192 \`get_selection\`, then \`get_properties\` or \`serialize_ui\`**
684
+ - **Reading properties \u2192 \`get_properties\` (NEVER \`run_code\` with dump scripts)**
685
+ - **Browsing the tree \u2192 \`get_explorer\`**
683
686
  - Anything exotic or procedural \u2192 \`run_code\` as last resort
684
687
 
688
+ ### NEVER use run_code for:
689
+ - Getting properties of an instance \u2192 use \`get_properties\` or \`serialize_ui\`
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\`
693
+ - Creating instances \u2192 use \`create_part\`, \`create_ui\`, \`insert_instance\`, \`build_multiple\`
694
+ - Modifying properties \u2192 use \`set_properties\` or \`bulk_set_properties\`
695
+ - Reading scripts \u2192 use \`read_script\`
696
+ - Browsing the explorer \u2192 use \`get_explorer\`
697
+ \`run_code\` is ONLY for procedural logic, terrain generation, raycasts, physics queries, or custom algorithms with no tool equivalent.
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
+
685
704
  ### Verification habits
686
705
  - After a build, call \`get_explorer\` or \`get_selection\` to confirm the tree looks right.
687
706
  - After editing a script, read it back.
@@ -2586,7 +2605,7 @@ async function startMcpServer() {
2586
2605
  }
2587
2606
  const mcp = new McpServer({
2588
2607
  name: "dominus",
2589
- version: "0.5.5"
2608
+ version: "0.5.8"
2590
2609
  }, {
2591
2610
  instructions: INTERNAL_MEMORY
2592
2611
  });
@@ -2599,6 +2618,96 @@ async function startMcpServer() {
2599
2618
  for (const p of parts) r += `[${luaStr(p)}]`;
2600
2619
  return r;
2601
2620
  }
2621
+ function generateComponentCode(tree, componentName, framework) {
2622
+ const isReact = framework === "react";
2623
+ const lib = isReact ? "React" : "Roact";
2624
+ const lines = [];
2625
+ lines.push(`local ${lib} = require(game.ReplicatedStorage.Packages.${isReact ? "React" : "Roact"})`);
2626
+ if (isReact) {
2627
+ lines.push(`local e = React.createElement`);
2628
+ } else {
2629
+ lines.push(`local e = Roact.createElement`);
2630
+ }
2631
+ lines.push("");
2632
+ lines.push(`local function ${componentName}()`);
2633
+ lines.push(` return ${renderNode(tree, 1)}`);
2634
+ lines.push("end");
2635
+ lines.push("");
2636
+ lines.push(`return ${componentName}`);
2637
+ return lines.join("\n");
2638
+ }
2639
+ function renderNode(node, depth) {
2640
+ const indent = " ".repeat(depth);
2641
+ const indent2 = " ".repeat(depth + 1);
2642
+ const className = node.ClassName || "Frame";
2643
+ const children = node.Children || [];
2644
+ node.Name;
2645
+ const props = [];
2646
+ const skipKeys = /* @__PURE__ */ new Set(["ClassName", "Name", "Children", "className", "name", "children"]);
2647
+ for (const [key, val] of Object.entries(node)) {
2648
+ if (skipKeys.has(key)) continue;
2649
+ props.push(`${indent2}${key} = ${luaValue(val, depth + 1)},`);
2650
+ }
2651
+ const childEntries = [];
2652
+ for (const child of children) {
2653
+ const childName = child.Name || child.ClassName || "Child";
2654
+ childEntries.push(`${indent2}${childName} = ${renderNode(child, depth + 1)},`);
2655
+ }
2656
+ const hasProps = props.length > 0;
2657
+ const hasChildren = childEntries.length > 0;
2658
+ if (!hasProps && !hasChildren) {
2659
+ return `e("${className}", {})`;
2660
+ }
2661
+ const parts = [];
2662
+ parts.push(`e("${className}", {`);
2663
+ if (hasProps) {
2664
+ parts.push(...props);
2665
+ }
2666
+ if (hasChildren) {
2667
+ parts.push(`${indent2}}, {`);
2668
+ parts.push(...childEntries);
2669
+ parts.push(`${indent}})`);
2670
+ } else {
2671
+ parts.push(`${indent}})`);
2672
+ }
2673
+ return parts.join("\n");
2674
+ }
2675
+ function luaValue(val, _depth) {
2676
+ if (val === null || val === void 0) return "nil";
2677
+ if (typeof val === "boolean") return String(val);
2678
+ if (typeof val === "number") return String(val);
2679
+ if (typeof val === "string") {
2680
+ if (val.startsWith("#") && val.length === 7) {
2681
+ return `Color3.fromHex("${val}")`;
2682
+ }
2683
+ if (/^[A-Z][a-zA-Z]+$/.test(val)) {
2684
+ return `"${val}"`;
2685
+ }
2686
+ return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
2687
+ }
2688
+ if (Array.isArray(val)) {
2689
+ if (val.length === 4 && val.every((v) => typeof v === "number")) {
2690
+ return `UDim2.new(${val.join(", ")})`;
2691
+ }
2692
+ if (val.length === 2 && val.every((v) => typeof v === "number")) {
2693
+ return `Vector2.new(${val.join(", ")})`;
2694
+ }
2695
+ if (val.length === 3 && val.every((v) => typeof v === "number")) {
2696
+ const allSmall = val.every((v) => v <= 1);
2697
+ if (allSmall) return `Color3.new(${val.join(", ")})`;
2698
+ return `Color3.fromRGB(${val.join(", ")})`;
2699
+ }
2700
+ return `{${val.map((v) => luaValue(v, _depth)).join(", ")}}`;
2701
+ }
2702
+ if (typeof val === "object") {
2703
+ const entries = Object.entries(val);
2704
+ if (entries.length === 0) return "{}";
2705
+ return `{
2706
+ ${entries.map(([k, v]) => `${" ".repeat(_depth + 1)}${k} = ${luaValue(v, _depth + 1)}`).join(",\n")}
2707
+ ${" ".repeat(_depth)}}`;
2708
+ }
2709
+ return String(val);
2710
+ }
2602
2711
  mcp.tool(
2603
2712
  "read_script",
2604
2713
  "Read the source code of a script in Roblox Studio",
@@ -2624,7 +2733,7 @@ async function startMcpServer() {
2624
2733
  );
2625
2734
  mcp.tool(
2626
2735
  "run_code",
2627
- "Execute Luau code in Roblox Studio and return output",
2736
+ "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.",
2628
2737
  { code: z.string().describe("Luau code to execute") },
2629
2738
  async ({ code }) => {
2630
2739
  assertConnected();
@@ -2720,7 +2829,7 @@ async function startMcpServer() {
2720
2829
  );
2721
2830
  mcp.tool(
2722
2831
  "get_selection",
2723
- "Get the currently selected instances in Roblox Studio",
2832
+ '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.',
2724
2833
  {},
2725
2834
  async () => {
2726
2835
  assertConnected();
@@ -2995,6 +3104,90 @@ async function startMcpServer() {
2995
3104
  return { content: [{ type: "text", text: JSON.stringify(res.payload.tree, null, 2) }] };
2996
3105
  }
2997
3106
  );
3107
+ mcp.tool(
3108
+ "inspect_selection",
3109
+ `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.`,
3110
+ {},
3111
+ async () => {
3112
+ assertConnected();
3113
+ const selRes = await bridge.sendRequest(CliMsg.GET_SELECTION, {});
3114
+ const paths = selRes.payload?.paths ?? [];
3115
+ if (paths.length === 0) {
3116
+ return { content: [{ type: "text", text: "No instances selected in Studio." }] };
3117
+ }
3118
+ const results = {};
3119
+ for (const p of paths.slice(0, 10)) {
3120
+ try {
3121
+ const propRes = await bridge.sendRequest(
3122
+ CliMsg.GET_PROPERTIES,
3123
+ { path: p, compact: true }
3124
+ );
3125
+ results[p] = propRes.payload.properties ?? propRes.payload;
3126
+ } catch {
3127
+ results[p] = { error: "Failed to read properties" };
3128
+ }
3129
+ }
3130
+ if (paths.length > 10) {
3131
+ results["_truncated"] = `${paths.length - 10} more instances not shown`;
3132
+ }
3133
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
3134
+ }
3135
+ );
3136
+ mcp.tool(
3137
+ "inspect_instance",
3138
+ "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.",
3139
+ {
3140
+ path: z.string().describe('Full instance path (e.g. "Workspace.MyModel")'),
3141
+ childDepth: z.number().optional().default(2).describe("How deep to show the children tree (default 2)")
3142
+ },
3143
+ async ({ path: path5, childDepth }) => {
3144
+ assertConnected();
3145
+ const [propRes, treeRes] = await Promise.all([
3146
+ bridge.sendRequest(
3147
+ CliMsg.GET_PROPERTIES,
3148
+ { path: path5, compact: true }
3149
+ ),
3150
+ bridge.sendRequest(
3151
+ CliMsg.GET_EXPLORER,
3152
+ { rootPath: path5, maxDepth: childDepth ?? 2 }
3153
+ )
3154
+ ]);
3155
+ return {
3156
+ content: [{
3157
+ type: "text",
3158
+ text: JSON.stringify({
3159
+ properties: propRes.payload.properties ?? propRes.payload,
3160
+ children: treeRes.payload.tree ?? treeRes.payload
3161
+ }, null, 2)
3162
+ }]
3163
+ };
3164
+ }
3165
+ );
3166
+ mcp.tool(
3167
+ "convert_ui_to_code",
3168
+ '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".',
3169
+ {
3170
+ path: z.string().describe('Path to the UI instance to convert (e.g. "StarterGui.MyScreenGui.MainFrame")'),
3171
+ framework: z.enum(["roact", "react"]).optional().default("react").describe('Target framework: "roact" (legacy) or "react" (react-lua, default)'),
3172
+ componentName: z.string().optional().describe("Name for the generated component (defaults to instance Name)")
3173
+ },
3174
+ async ({ path: path5, framework, componentName }) => {
3175
+ assertConnected();
3176
+ const res = await bridge.sendRequest(
3177
+ CliMsg.SERIALIZE_UI,
3178
+ { path: path5, maxDepth: 50 },
3179
+ 1e3 * 60 * 2
3180
+ );
3181
+ if (!res.payload.success || !res.payload.tree) {
3182
+ return { content: [{ type: "text", text: `Error serializing UI: ${res.payload.error ?? "Unknown error"}` }] };
3183
+ }
3184
+ const tree = res.payload.tree;
3185
+ const name = componentName || tree.Name || "GeneratedComponent";
3186
+ const fw = framework || "react";
3187
+ const code = generateComponentCode(tree, name, fw);
3188
+ return { content: [{ type: "text", text: code }] };
3189
+ }
3190
+ );
2998
3191
  mcp.tool(
2999
3192
  "recall_memory",
3000
3193
  "Search persistent memory for facts about the current Roblox project",
@@ -4259,10 +4452,24 @@ You are deeply knowledgeable about:
4259
4452
 
4260
4453
  You have direct access to Roblox Studio through tools. You can read/edit scripts, run code, browse the explorer tree, manage instances, and run tests -- all in real-time via WebSocket.
4261
4454
 
4455
+ ## CRITICAL: NEVER use run_code for tasks that have a dedicated tool
4456
+ run_code is a LAST RESORT. You have 30+ specialized tools \u2014 use them.
4457
+ - Reading properties \u2192 get_properties, get_descendants_properties
4458
+ - Inspecting selection \u2192 inspect_selection (ONE call returns paths + properties)
4459
+ - Inspecting an instance \u2192 inspect_instance (ONE call returns properties + children)
4460
+ - Converting UI to code \u2192 convert_ui_to_code (ONE call serializes + generates component)
4461
+ - Creating parts \u2192 create_part, build_multiple
4462
+ - Creating UI \u2192 create_ui
4463
+ - Modifying properties \u2192 set_properties, bulk_set_properties
4464
+ - Browsing the tree \u2192 get_explorer
4465
+ - Reading/editing scripts \u2192 read_script, edit_script
4466
+ - Serializing UI \u2192 serialize_ui
4467
+ Only use run_code for: procedural generation, terrain algorithms, raycasting, physics queries, or other logic that no tool covers.
4468
+
4262
4469
  When the user asks you to do something, you should:
4263
4470
  1. Use your tools to gather context (read scripts, check explorer, recall memory)
4264
4471
  2. Plan your approach for complex tasks
4265
- 3. Execute changes using your tools (edit scripts, run code, insert instances)
4472
+ 3. Execute changes using the appropriate dedicated tools (NOT run_code unless truly necessary)
4266
4473
  4. Verify your changes worked (read back, check output for errors)
4267
4474
  5. Remember important facts for future sessions`);
4268
4475
  if (opts.connected) {