dominus-cli 0.2.1 → 0.5.0

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
@@ -221,6 +221,7 @@ var init_protocol = __esm({
221
221
  SET_SCRIPT: "cli:set:script",
222
222
  RUN_CODE: "cli:run",
223
223
  GET_PROPERTIES: "cli:get:properties",
224
+ GET_DESCENDANTS_PROPERTIES: "cli:get:descendants:properties",
224
225
  SET_PROPERTIES: "cli:set:properties",
225
226
  INSERT_INSTANCE: "cli:insert",
226
227
  DELETE_INSTANCE: "cli:delete",
@@ -234,7 +235,13 @@ var init_protocol = __esm({
234
235
  END_TEST: "cli:test:end",
235
236
  BUILD_UI: "cli:build:ui",
236
237
  GET_REFLECTION: "cli:get:reflection",
237
- UPLOAD_ASSET: "cli:upload:asset"
238
+ UPLOAD_ASSET: "cli:upload:asset",
239
+ SERIALIZE_UI: "cli:serialize:ui",
240
+ MOVE_INSTANCE: "cli:move:instance",
241
+ UNDO: "cli:undo",
242
+ REDO: "cli:redo",
243
+ BULK_SET_PROPERTIES: "cli:bulk:set:properties",
244
+ FIND_REPLACE_SCRIPTS: "cli:find:replace:scripts"
238
245
  };
239
246
  }
240
247
  });
@@ -1119,16 +1126,65 @@ var init_get_properties = __esm({
1119
1126
  }
1120
1127
  });
1121
1128
 
1129
+ // src/tools/studio/get-descendants-properties.ts
1130
+ var get_descendants_properties_exports = {};
1131
+ __export(get_descendants_properties_exports, {
1132
+ tool: () => tool6
1133
+ });
1134
+ var tool6;
1135
+ var init_get_descendants_properties = __esm({
1136
+ "src/tools/studio/get-descendants-properties.ts"() {
1137
+ init_protocol();
1138
+ tool6 = {
1139
+ name: "get_descendants_properties",
1140
+ description: "Easily get all descendants of a UI instance (or any instance) and all of their properties.",
1141
+ parameters: {
1142
+ type: "object",
1143
+ properties: {
1144
+ path: {
1145
+ type: "string",
1146
+ description: 'Full instance path (e.g., "Workspace.Part", "StarterGui.ScreenGui")'
1147
+ },
1148
+ outputFile: {
1149
+ type: "string",
1150
+ description: "Optional file path to write the JSON result to, useful for very large trees where context limits apply."
1151
+ }
1152
+ },
1153
+ required: ["path"]
1154
+ },
1155
+ async execute(params, ctx) {
1156
+ if (!ctx.isStudioConnected()) {
1157
+ return { success: false, error: "Studio is not connected" };
1158
+ }
1159
+ const response = await ctx.sendToStudio(
1160
+ CliMsg.GET_DESCENDANTS_PROPERTIES,
1161
+ { path: params.path },
1162
+ 1e3 * 60 * 5
1163
+ // 5 minute timeout since getting ALL descendants can take a very long time
1164
+ );
1165
+ if (params.outputFile && typeof params.outputFile === "string") {
1166
+ const fs6 = await import('fs/promises');
1167
+ const path5 = await import('path');
1168
+ const outputPath = path5.resolve(process.cwd(), params.outputFile);
1169
+ await fs6.writeFile(outputPath, JSON.stringify(response.payload, null, 2), "utf-8");
1170
+ return { success: true, data: `Successfully saved descendants to ${outputPath}` };
1171
+ }
1172
+ return { success: true, data: response.payload };
1173
+ }
1174
+ };
1175
+ }
1176
+ });
1177
+
1122
1178
  // src/tools/studio/set-properties.ts
1123
1179
  var set_properties_exports = {};
1124
1180
  __export(set_properties_exports, {
1125
- tool: () => tool6
1181
+ tool: () => tool7
1126
1182
  });
1127
- var tool6;
1183
+ var tool7;
1128
1184
  var init_set_properties = __esm({
1129
1185
  "src/tools/studio/set-properties.ts"() {
1130
1186
  init_protocol();
1131
- tool6 = {
1187
+ tool7 = {
1132
1188
  name: "set_properties",
1133
1189
  description: "Set properties on an instance in Roblox Studio.",
1134
1190
  parameters: {
@@ -1166,13 +1222,13 @@ var init_set_properties = __esm({
1166
1222
  // src/tools/studio/insert-instance.ts
1167
1223
  var insert_instance_exports = {};
1168
1224
  __export(insert_instance_exports, {
1169
- tool: () => tool7
1225
+ tool: () => tool8
1170
1226
  });
1171
- var tool7;
1227
+ var tool8;
1172
1228
  var init_insert_instance = __esm({
1173
1229
  "src/tools/studio/insert-instance.ts"() {
1174
1230
  init_protocol();
1175
- tool7 = {
1231
+ tool8 = {
1176
1232
  name: "insert_instance",
1177
1233
  description: "Insert a new instance into the Roblox Studio DataModel.",
1178
1234
  parameters: {
@@ -1220,13 +1276,13 @@ var init_insert_instance = __esm({
1220
1276
  // src/tools/studio/delete-instance.ts
1221
1277
  var delete_instance_exports = {};
1222
1278
  __export(delete_instance_exports, {
1223
- tool: () => tool8
1279
+ tool: () => tool9
1224
1280
  });
1225
- var tool8;
1281
+ var tool9;
1226
1282
  var init_delete_instance = __esm({
1227
1283
  "src/tools/studio/delete-instance.ts"() {
1228
1284
  init_protocol();
1229
- tool8 = {
1285
+ tool9 = {
1230
1286
  name: "delete_instance",
1231
1287
  description: "Delete an instance from the Roblox Studio DataModel by path.",
1232
1288
  parameters: {
@@ -1257,13 +1313,13 @@ var init_delete_instance = __esm({
1257
1313
  // src/tools/studio/get-output.ts
1258
1314
  var get_output_exports = {};
1259
1315
  __export(get_output_exports, {
1260
- tool: () => tool9
1316
+ tool: () => tool10
1261
1317
  });
1262
- var tool9;
1318
+ var tool10;
1263
1319
  var init_get_output = __esm({
1264
1320
  "src/tools/studio/get-output.ts"() {
1265
1321
  init_protocol();
1266
- tool9 = {
1322
+ tool10 = {
1267
1323
  name: "get_output",
1268
1324
  description: "Get recent output log entries from Roblox Studio (prints, warnings, errors).",
1269
1325
  parameters: {
@@ -1298,13 +1354,13 @@ var init_get_output = __esm({
1298
1354
  // src/tools/studio/get-selection.ts
1299
1355
  var get_selection_exports = {};
1300
1356
  __export(get_selection_exports, {
1301
- tool: () => tool10
1357
+ tool: () => tool11
1302
1358
  });
1303
- var tool10;
1359
+ var tool11;
1304
1360
  var init_get_selection = __esm({
1305
1361
  "src/tools/studio/get-selection.ts"() {
1306
1362
  init_protocol();
1307
- tool10 = {
1363
+ tool11 = {
1308
1364
  name: "get_selection",
1309
1365
  description: "Get the currently selected instances in Roblox Studio.",
1310
1366
  parameters: {
@@ -1325,13 +1381,13 @@ var init_get_selection = __esm({
1325
1381
  // src/tools/studio/search-scripts.ts
1326
1382
  var search_scripts_exports = {};
1327
1383
  __export(search_scripts_exports, {
1328
- tool: () => tool11
1384
+ tool: () => tool12
1329
1385
  });
1330
- var tool11;
1386
+ var tool12;
1331
1387
  var init_search_scripts = __esm({
1332
1388
  "src/tools/studio/search-scripts.ts"() {
1333
1389
  init_protocol();
1334
- tool11 = {
1390
+ tool12 = {
1335
1391
  name: "search_scripts",
1336
1392
  description: "Search across all scripts in Roblox Studio for a text pattern. Returns matching lines with context.",
1337
1393
  parameters: {
@@ -1366,13 +1422,13 @@ var init_search_scripts = __esm({
1366
1422
  // src/tools/studio/run-tests.ts
1367
1423
  var run_tests_exports = {};
1368
1424
  __export(run_tests_exports, {
1369
- tool: () => tool12
1425
+ tool: () => tool13
1370
1426
  });
1371
- var tool12;
1427
+ var tool13;
1372
1428
  var init_run_tests = __esm({
1373
1429
  "src/tools/studio/run-tests.ts"() {
1374
1430
  init_protocol();
1375
- tool12 = {
1431
+ tool13 = {
1376
1432
  name: "run_tests",
1377
1433
  description: "Run tests in Roblox Studio using StudioTestService. Can run all tests or a specific test by name.",
1378
1434
  parameters: {
@@ -1406,13 +1462,13 @@ var init_run_tests = __esm({
1406
1462
  // src/tools/studio/get-class-info.ts
1407
1463
  var get_class_info_exports = {};
1408
1464
  __export(get_class_info_exports, {
1409
- tool: () => tool13
1465
+ tool: () => tool14
1410
1466
  });
1411
- var tool13;
1467
+ var tool14;
1412
1468
  var init_get_class_info = __esm({
1413
1469
  "src/tools/studio/get-class-info.ts"() {
1414
1470
  init_protocol();
1415
- tool13 = {
1471
+ tool14 = {
1416
1472
  name: "get_class_info",
1417
1473
  description: "Look up the full schema of a Roblox class using ReflectionService. Returns all properties (with their types), methods (with parameters), and events. ALWAYS call this before creating or modifying GUI/UI elements (Frame, TextLabel, ImageLabel, etc.) to know the correct property names and expected types (e.g., Size is UDim2 for GUI, Vector3 for Parts).",
1418
1474
  parameters: {
@@ -1443,13 +1499,13 @@ var init_get_class_info = __esm({
1443
1499
  // src/tools/studio/create-ui.ts
1444
1500
  var create_ui_exports = {};
1445
1501
  __export(create_ui_exports, {
1446
- tool: () => tool14
1502
+ tool: () => tool15
1447
1503
  });
1448
- var tool14;
1504
+ var tool15;
1449
1505
  var init_create_ui = __esm({
1450
1506
  "src/tools/studio/create-ui.ts"() {
1451
1507
  init_protocol();
1452
- tool14 = {
1508
+ tool15 = {
1453
1509
  name: "create_ui",
1454
1510
  description: 'Create an entire UI tree in Roblox Studio in a SINGLE call. Pass a declarative JSON tree with ClassName, Name, properties, and Children. All types are auto-coerced natively in Luau \u2014 UDim2, Color3, Font, enums all work. This is 10x faster than run_code for UI because it handles everything in one round-trip with no Luau syntax issues. Replaces any existing instance with the same name.\n\nExample: create a pastel hello world screen:\n{\n "parent": "StarterGui",\n "tree": {\n "ClassName": "ScreenGui",\n "Name": "HelloWorld",\n "ResetOnSpawn": false,\n "Children": [{\n "ClassName": "Frame",\n "Name": "Background",\n "Size": [1, 0, 1, 0],\n "BackgroundColor3": "#F0E6FF",\n "Children": [\n { "ClassName": "UICorner", "CornerRadius": [0, 12] },\n { "ClassName": "TextLabel", "Name": "Title", "Text": "Hello World!",\n "Size": [0.5, 0, 0.1, 0], "Position": [0.25, 0, 0.45, 0],\n "BackgroundTransparency": 1, "TextColor3": "#6C5CE7",\n "TextScaled": true, "Font": "GothamBold" }\n ]\n }]\n }\n}',
1455
1511
  parameters: {
@@ -1461,7 +1517,11 @@ var init_create_ui = __esm({
1461
1517
  },
1462
1518
  tree: {
1463
1519
  type: "object",
1464
- description: 'The root UI node. Each node has: ClassName (string), Name (string), any properties as key-value pairs, and Children (array of child nodes). Property values are auto-coerced:\n UDim2: [xScale, xOffset, yScale, yOffset] or {XScale, XOffset, YScale, YOffset}\n Color3: "#hex", "rgb(r,g,b)", [r,g,b], or BrickColor name\n UDim: [scale, offset] or number\n Vector2: {x, y} or [x, y]\n Font: "GothamBold", "SourceSans", etc. (Enum.Font names)\n Enums: string name like "Center", "Left", "Fit"\n FontFace: {Family, Weight, Style}'
1520
+ description: 'The root UI node. Each node has: ClassName (string), Name (string), any properties as key-value pairs, and Children (array of child nodes). Property values are auto-coerced:\n UDim2: [xScale, xOffset, yScale, yOffset] or {XScale, XOffset, YScale, YOffset}\n Color3: "#hex", "rgb(r,g,b)", [r,g,b], or BrickColor name\n UDim: [scale, offset] or number\n Vector2: [x, y] or {x, y} (auto-disambiguated from UDim by property name)\n NumberRange: [min, max] or number\n Rect: [minX, minY, maxX, maxY] or {MinX, MinY, MaxX, MaxY}\n Font: "GothamBold", "SourceSans", etc. (Enum.Font names)\n Enums: string name like "Center", "Left", "Fit"\n FontFace: {Family, Weight, Style}\n 2-element arrays are auto-disambiguated: AnchorPoint\u2192Vector2, CornerRadius/Padding\u2192UDim, etc.'
1521
+ },
1522
+ animate: {
1523
+ type: "boolean",
1524
+ description: "If true, task.wait() will be injected between creation of nodes so that the UI writes out dynamically."
1465
1525
  }
1466
1526
  },
1467
1527
  required: ["tree"]
@@ -1472,6 +1532,7 @@ var init_create_ui = __esm({
1472
1532
  }
1473
1533
  const response = await ctx.sendToStudio(CliMsg.BUILD_UI, {
1474
1534
  parent: params.parent ?? "StarterGui",
1535
+ animate: params.animate,
1475
1536
  tree: params.tree
1476
1537
  });
1477
1538
  const payload = response.payload;
@@ -1490,13 +1551,13 @@ var init_create_ui = __esm({
1490
1551
  // src/tools/studio/execute-run-test.ts
1491
1552
  var execute_run_test_exports = {};
1492
1553
  __export(execute_run_test_exports, {
1493
- tool: () => tool15
1554
+ tool: () => tool16
1494
1555
  });
1495
- var tool15;
1556
+ var tool16;
1496
1557
  var init_execute_run_test = __esm({
1497
1558
  "src/tools/studio/execute-run-test.ts"() {
1498
1559
  init_protocol();
1499
- tool15 = {
1560
+ tool16 = {
1500
1561
  name: "execute_run_test",
1501
1562
  description: "Start a Run mode test session in Roblox Studio using StudioTestService:ExecuteRunModeAsync(). Passes args that server scripts can retrieve via GetTestArgs(). Yields until the session ends (via EndTest or manual stop). Returns the value passed to EndTest(). Use this for headless automated tests \u2014 no player character, just server scripts running.",
1502
1563
  parameters: {
@@ -1535,13 +1596,13 @@ var init_execute_run_test = __esm({
1535
1596
  // src/tools/studio/execute-play-test.ts
1536
1597
  var execute_play_test_exports = {};
1537
1598
  __export(execute_play_test_exports, {
1538
- tool: () => tool16
1599
+ tool: () => tool17
1539
1600
  });
1540
- var tool16;
1601
+ var tool17;
1541
1602
  var init_execute_play_test = __esm({
1542
1603
  "src/tools/studio/execute-play-test.ts"() {
1543
1604
  init_protocol();
1544
- tool16 = {
1605
+ tool17 = {
1545
1606
  name: "execute_play_test",
1546
1607
  description: "Start a solo Play mode test session in Roblox Studio using StudioTestService:ExecutePlayModeAsync(). Passes args that server scripts can retrieve via GetTestArgs(). Yields until the session ends (via EndTest or manual stop). Returns the value passed to EndTest(). Use this for tests that need a player character and full client-server simulation.",
1547
1608
  parameters: {
@@ -1580,13 +1641,13 @@ var init_execute_play_test = __esm({
1580
1641
  // src/tools/studio/create-part.ts
1581
1642
  var create_part_exports = {};
1582
1643
  __export(create_part_exports, {
1583
- tool: () => tool17
1644
+ tool: () => tool18
1584
1645
  });
1585
- var tool17;
1646
+ var tool18;
1586
1647
  var init_create_part = __esm({
1587
1648
  "src/tools/studio/create-part.ts"() {
1588
1649
  init_protocol();
1589
- tool17 = {
1650
+ tool18 = {
1590
1651
  name: "create_part",
1591
1652
  description: "Create a Part in Roblox Studio with position, size, color, material and more \u2014 all in one call. Much faster than insert_instance + set_properties separately.",
1592
1653
  parameters: {
@@ -1679,7 +1740,7 @@ var init_create_part = __esm({
1679
1740
  // src/tools/studio/clone-instance.ts
1680
1741
  var clone_instance_exports = {};
1681
1742
  __export(clone_instance_exports, {
1682
- tool: () => tool18
1743
+ tool: () => tool19
1683
1744
  });
1684
1745
  function buildCloneCode(params) {
1685
1746
  const path5 = params.path;
@@ -1714,11 +1775,11 @@ function buildCloneCode(params) {
1714
1775
  lines.push(`print("Cloned: " .. clone:GetFullName())`);
1715
1776
  return lines.join("\n");
1716
1777
  }
1717
- var tool18;
1778
+ var tool19;
1718
1779
  var init_clone_instance = __esm({
1719
1780
  "src/tools/studio/clone-instance.ts"() {
1720
1781
  init_protocol();
1721
- tool18 = {
1782
+ tool19 = {
1722
1783
  name: "clone_instance",
1723
1784
  description: "Clone an existing instance in Roblox Studio and optionally reposition/rename it. Great for duplicating parts, models, or entire trees.",
1724
1785
  parameters: {
@@ -1754,13 +1815,13 @@ var init_clone_instance = __esm({
1754
1815
  // src/tools/studio/group-instances.ts
1755
1816
  var group_instances_exports = {};
1756
1817
  __export(group_instances_exports, {
1757
- tool: () => tool19
1818
+ tool: () => tool20
1758
1819
  });
1759
- var tool19;
1820
+ var tool20;
1760
1821
  var init_group_instances = __esm({
1761
1822
  "src/tools/studio/group-instances.ts"() {
1762
1823
  init_protocol();
1763
- tool19 = {
1824
+ tool20 = {
1764
1825
  name: "group_instances",
1765
1826
  description: 'Group multiple instances under a new Model or Folder. Useful for organizing parts into objects (e.g., grouping trunk + leaves into a "Tree" model).',
1766
1827
  parameters: {
@@ -1820,7 +1881,7 @@ var init_group_instances = __esm({
1820
1881
  // src/tools/studio/build-multiple.ts
1821
1882
  var build_multiple_exports = {};
1822
1883
  __export(build_multiple_exports, {
1823
- tool: () => tool20
1884
+ tool: () => tool21
1824
1885
  });
1825
1886
  function generateBatchCode(parts, groupName, groupParent) {
1826
1887
  const lines = [];
@@ -1879,11 +1940,11 @@ function generateBatchCode(parts, groupName, groupParent) {
1879
1940
  lines.push(`print("Built ${parts.length} parts${groupName ? ` in ${groupName}` : ""}")`);
1880
1941
  return lines.join("\n");
1881
1942
  }
1882
- var tool20;
1943
+ var tool21;
1883
1944
  var init_build_multiple = __esm({
1884
1945
  "src/tools/studio/build-multiple.ts"() {
1885
1946
  init_protocol();
1886
- tool20 = {
1947
+ tool21 = {
1887
1948
  name: "build_multiple",
1888
1949
  description: "Create multiple parts/instances in one batch call. Send an array of specs and they all get created in a single Studio round-trip. Ideal for building trees, houses, vehicles, terrain \u2014 anything composed of multiple parts.",
1889
1950
  parameters: {
@@ -1952,16 +2013,234 @@ var init_build_multiple = __esm({
1952
2013
  }
1953
2014
  });
1954
2015
 
2016
+ // src/tools/studio/serialize-ui.ts
2017
+ var serialize_ui_exports = {};
2018
+ __export(serialize_ui_exports, {
2019
+ tool: () => tool22
2020
+ });
2021
+ var tool22;
2022
+ var init_serialize_ui = __esm({
2023
+ "src/tools/studio/serialize-ui.ts"() {
2024
+ init_protocol();
2025
+ tool22 = {
2026
+ name: "serialize_ui",
2027
+ description: "Serialize an existing UI tree from Roblox Studio into create_ui-compatible JSON. This is the reverse of create_ui \u2014 read any instance and get back a declarative JSON tree you can modify and pass back to create_ui. Perfect for cloning, templating, or refactoring UIs.\n\nThe output includes ClassName, Name, all non-default properties (with values converted to JSON-friendly formats like [r,g,b] for Color3, [xS,xO,yS,yO] for UDim2, etc.), and Children.",
2028
+ parameters: {
2029
+ type: "object",
2030
+ properties: {
2031
+ path: {
2032
+ type: "string",
2033
+ description: 'Instance path to serialize (e.g. "StarterGui.MyScreenGui")'
2034
+ },
2035
+ maxDepth: {
2036
+ type: "number",
2037
+ description: "Maximum depth to serialize (default: 50)"
2038
+ }
2039
+ },
2040
+ required: ["path"]
2041
+ },
2042
+ async execute(params, ctx) {
2043
+ if (!ctx.isStudioConnected()) {
2044
+ return { success: false, error: "Studio is not connected" };
2045
+ }
2046
+ const response = await ctx.sendToStudio(CliMsg.SERIALIZE_UI, {
2047
+ path: params.path,
2048
+ maxDepth: params.maxDepth ?? 50
2049
+ }, 1e3 * 60 * 2);
2050
+ const payload = response.payload;
2051
+ if (!payload.success) {
2052
+ return { success: false, error: payload.error ?? "Serialization failed" };
2053
+ }
2054
+ return { success: true, data: payload.tree };
2055
+ }
2056
+ };
2057
+ }
2058
+ });
2059
+
2060
+ // src/tools/studio/move-instance.ts
2061
+ var move_instance_exports = {};
2062
+ __export(move_instance_exports, {
2063
+ tool: () => tool23
2064
+ });
2065
+ var tool23;
2066
+ var init_move_instance = __esm({
2067
+ "src/tools/studio/move-instance.ts"() {
2068
+ init_protocol();
2069
+ tool23 = {
2070
+ name: "move_instance",
2071
+ description: "Move (reparent) an instance to a new parent in Roblox Studio. This preserves the instance and all its descendants \u2014 much better than delete + recreate. Sets a ChangeHistoryService waypoint so the move can be undone.",
2072
+ parameters: {
2073
+ type: "object",
2074
+ properties: {
2075
+ path: {
2076
+ type: "string",
2077
+ description: 'Full path of the instance to move (e.g. "Workspace.OldFolder.MyPart")'
2078
+ },
2079
+ newParent: {
2080
+ type: "string",
2081
+ description: 'Full path of the new parent (e.g. "Workspace.NewFolder")'
2082
+ }
2083
+ },
2084
+ required: ["path", "newParent"]
2085
+ },
2086
+ async execute(params, ctx) {
2087
+ if (!ctx.isStudioConnected()) {
2088
+ return { success: false, error: "Studio is not connected" };
2089
+ }
2090
+ const response = await ctx.sendToStudio(CliMsg.MOVE_INSTANCE, {
2091
+ path: params.path,
2092
+ newParent: params.newParent
2093
+ });
2094
+ const payload = response.payload;
2095
+ if (!payload.success) {
2096
+ return { success: false, error: payload.error ?? "Move failed" };
2097
+ }
2098
+ return { success: true, data: { newPath: payload.newPath } };
2099
+ }
2100
+ };
2101
+ }
2102
+ });
2103
+
2104
+ // src/tools/studio/bulk-set-properties.ts
2105
+ var bulk_set_properties_exports = {};
2106
+ __export(bulk_set_properties_exports, {
2107
+ tool: () => tool24
2108
+ });
2109
+ var tool24;
2110
+ var init_bulk_set_properties = __esm({
2111
+ "src/tools/studio/bulk-set-properties.ts"() {
2112
+ init_protocol();
2113
+ tool24 = {
2114
+ name: "bulk_set_properties",
2115
+ description: 'Set properties on multiple instances at once in a single call. Much faster than calling set_properties repeatedly. Supports two modes:\n1. Explicit paths: provide an array of {path, properties} objects\n2. By class filter: provide a root path, className filter, and properties to apply to all matches\n\nExample (explicit): [{"path": "StarterGui.HUD.Title", "properties": {"TextColor3": "#fff"}}, ...]\nExample (filter): root="StarterGui.HUD", className="TextLabel", properties={"Font": "GothamBold"}',
2116
+ parameters: {
2117
+ type: "object",
2118
+ properties: {
2119
+ targets: {
2120
+ type: "array",
2121
+ description: "Array of {path, properties} objects for explicit mode.",
2122
+ items: {
2123
+ type: "object",
2124
+ description: "A target with path and properties to set."
2125
+ }
2126
+ },
2127
+ root: {
2128
+ type: "string",
2129
+ description: "Root instance path to search under (filter mode)"
2130
+ },
2131
+ className: {
2132
+ type: "string",
2133
+ description: "Only apply to instances of this ClassName (filter mode)"
2134
+ },
2135
+ properties: {
2136
+ type: "object",
2137
+ description: "Properties to set on all matched instances (filter mode)"
2138
+ }
2139
+ }
2140
+ },
2141
+ async execute(params, ctx) {
2142
+ if (!ctx.isStudioConnected()) {
2143
+ return { success: false, error: "Studio is not connected" };
2144
+ }
2145
+ const response = await ctx.sendToStudio(CliMsg.BULK_SET_PROPERTIES, {
2146
+ targets: params.targets,
2147
+ root: params.root,
2148
+ className: params.className,
2149
+ properties: params.properties
2150
+ }, 1e3 * 60);
2151
+ const payload = response.payload;
2152
+ if (!payload.success) {
2153
+ return { success: false, error: payload.error ?? "Bulk set failed" };
2154
+ }
2155
+ return {
2156
+ success: true,
2157
+ data: {
2158
+ modified: payload.modified,
2159
+ errors: payload.errors?.length ? payload.errors : void 0
2160
+ }
2161
+ };
2162
+ }
2163
+ };
2164
+ }
2165
+ });
2166
+
2167
+ // src/tools/studio/find-replace-scripts.ts
2168
+ var find_replace_scripts_exports = {};
2169
+ __export(find_replace_scripts_exports, {
2170
+ tool: () => tool25
2171
+ });
2172
+ var tool25;
2173
+ var init_find_replace_scripts = __esm({
2174
+ "src/tools/studio/find-replace-scripts.ts"() {
2175
+ init_protocol();
2176
+ tool25 = {
2177
+ name: "find_replace_scripts",
2178
+ description: 'Find and replace text across all scripts in the game (or under a root path). Supports plain text and Lua pattern matching. Returns a list of modified scripts with match counts.\n\nUse dryRun=true to preview changes without applying them.\nExample: find="workspace" replace="Workspace" root="ServerScriptService"',
2179
+ parameters: {
2180
+ type: "object",
2181
+ properties: {
2182
+ find: {
2183
+ type: "string",
2184
+ description: "Text or Lua pattern to search for"
2185
+ },
2186
+ replace: {
2187
+ type: "string",
2188
+ description: "Replacement text (supports Lua pattern captures like %1, %2)"
2189
+ },
2190
+ root: {
2191
+ type: "string",
2192
+ description: "Root path to limit search scope (optional, searches entire game if omitted)"
2193
+ },
2194
+ usePattern: {
2195
+ type: "boolean",
2196
+ description: "If true, treat find/replace as Lua patterns. Default: false (plain text)"
2197
+ },
2198
+ dryRun: {
2199
+ type: "boolean",
2200
+ description: "If true, report matches but do not modify scripts. Default: false"
2201
+ }
2202
+ },
2203
+ required: ["find", "replace"]
2204
+ },
2205
+ async execute(params, ctx) {
2206
+ if (!ctx.isStudioConnected()) {
2207
+ return { success: false, error: "Studio is not connected" };
2208
+ }
2209
+ const response = await ctx.sendToStudio(CliMsg.FIND_REPLACE_SCRIPTS, {
2210
+ find: params.find,
2211
+ replace: params.replace,
2212
+ root: params.root,
2213
+ usePattern: params.usePattern ?? false,
2214
+ dryRun: params.dryRun ?? false
2215
+ }, 1e3 * 60 * 5);
2216
+ const payload = response.payload;
2217
+ if (!payload.success) {
2218
+ return { success: false, error: payload.error ?? "Find/replace failed" };
2219
+ }
2220
+ return {
2221
+ success: true,
2222
+ data: {
2223
+ totalFiles: payload.totalFiles,
2224
+ totalMatches: payload.totalMatches,
2225
+ modified: payload.modified,
2226
+ dryRun: params.dryRun ?? false
2227
+ }
2228
+ };
2229
+ }
2230
+ };
2231
+ }
2232
+ });
2233
+
1955
2234
  // src/tools/memory/recall.ts
1956
2235
  var recall_exports = {};
1957
2236
  __export(recall_exports, {
1958
- tool: () => tool21
2237
+ tool: () => tool26
1959
2238
  });
1960
- var tool21;
2239
+ var tool26;
1961
2240
  var init_recall = __esm({
1962
2241
  "src/tools/memory/recall.ts"() {
1963
2242
  init_store();
1964
- tool21 = {
2243
+ tool26 = {
1965
2244
  name: "recall_memory",
1966
2245
  description: "Search your memory for previously learned facts about the current project. Use this to recall architecture decisions, patterns, known bugs, or user preferences.",
1967
2246
  parameters: {
@@ -2000,13 +2279,13 @@ var init_recall = __esm({
2000
2279
  // src/tools/memory/remember.ts
2001
2280
  var remember_exports = {};
2002
2281
  __export(remember_exports, {
2003
- tool: () => tool22
2282
+ tool: () => tool27
2004
2283
  });
2005
- var tool22;
2284
+ var tool27;
2006
2285
  var init_remember = __esm({
2007
2286
  "src/tools/memory/remember.ts"() {
2008
2287
  init_store();
2009
- tool22 = {
2288
+ tool27 = {
2010
2289
  name: "remember",
2011
2290
  description: "Store a fact about the current project in persistent memory. Use this to remember architecture decisions, patterns, bugs found, or user preferences so you can recall them in future sessions.",
2012
2291
  parameters: {
@@ -2046,12 +2325,12 @@ var init_remember = __esm({
2046
2325
  // src/tools/cloud/upload-asset.ts
2047
2326
  var upload_asset_exports = {};
2048
2327
  __export(upload_asset_exports, {
2049
- tool: () => tool23
2328
+ tool: () => tool28
2050
2329
  });
2051
- var tool23;
2330
+ var tool28;
2052
2331
  var init_upload_asset = __esm({
2053
2332
  "src/tools/cloud/upload-asset.ts"() {
2054
- tool23 = {
2333
+ tool28 = {
2055
2334
  name: "upload_asset",
2056
2335
  description: "Upload a local file (image, model, audio) to Roblox via the Open Cloud API. Requires an Open Cloud API key configured in dominus.",
2057
2336
  parameters: {
@@ -2147,6 +2426,57 @@ var init_upload_asset = __esm({
2147
2426
  }
2148
2427
  });
2149
2428
 
2429
+ // src/tools/studio/undo-redo.ts
2430
+ var undo_redo_exports = {};
2431
+ __export(undo_redo_exports, {
2432
+ redoTool: () => redoTool,
2433
+ undoTool: () => undoTool
2434
+ });
2435
+ var undoTool, redoTool;
2436
+ var init_undo_redo = __esm({
2437
+ "src/tools/studio/undo-redo.ts"() {
2438
+ init_protocol();
2439
+ undoTool = {
2440
+ name: "undo",
2441
+ description: "Undo the last action in Roblox Studio using ChangeHistoryService. Works just like Ctrl+Z. Use this to roll back mistakes.",
2442
+ parameters: {
2443
+ type: "object",
2444
+ properties: {}
2445
+ },
2446
+ async execute(_params, ctx) {
2447
+ if (!ctx.isStudioConnected()) {
2448
+ return { success: false, error: "Studio is not connected" };
2449
+ }
2450
+ const response = await ctx.sendToStudio(CliMsg.UNDO, {});
2451
+ const payload = response.payload;
2452
+ if (!payload.success) {
2453
+ return { success: false, error: payload.error ?? "Undo failed" };
2454
+ }
2455
+ return { success: true, data: "Undo performed" };
2456
+ }
2457
+ };
2458
+ redoTool = {
2459
+ name: "redo",
2460
+ description: "Redo the last undone action in Roblox Studio using ChangeHistoryService. Works just like Ctrl+Y.",
2461
+ parameters: {
2462
+ type: "object",
2463
+ properties: {}
2464
+ },
2465
+ async execute(_params, ctx) {
2466
+ if (!ctx.isStudioConnected()) {
2467
+ return { success: false, error: "Studio is not connected" };
2468
+ }
2469
+ const response = await ctx.sendToStudio(CliMsg.REDO, {});
2470
+ const payload = response.payload;
2471
+ if (!payload.success) {
2472
+ return { success: false, error: payload.error ?? "Redo failed" };
2473
+ }
2474
+ return { success: true, data: "Redo performed" };
2475
+ }
2476
+ };
2477
+ }
2478
+ });
2479
+
2150
2480
  // src/mcp/server.ts
2151
2481
  var server_exports = {};
2152
2482
  __export(server_exports, {
@@ -2228,6 +2558,19 @@ async function startMcpServer() {
2228
2558
  return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
2229
2559
  }
2230
2560
  );
2561
+ mcp.tool(
2562
+ "get_descendants_properties",
2563
+ "Easily get all descendants of a UI instance (or any instance) and all of their properties.",
2564
+ {
2565
+ path: z.string().describe('Full instance path (e.g., "Workspace.Part", "StarterGui.ScreenGui")'),
2566
+ outputFile: z.string().optional().describe("Optional file path to write to prevent limit errors/timeouts")
2567
+ },
2568
+ async ({ path: path5, outputFile }) => {
2569
+ assertConnected();
2570
+ const res = await bridge.sendRequest(CliMsg.GET_DESCENDANTS_PROPERTIES, { path: path5, outputFile }, 1e3 * 60 * 5);
2571
+ return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
2572
+ }
2573
+ );
2231
2574
  mcp.tool(
2232
2575
  "set_properties",
2233
2576
  "Set properties on an instance in Roblox Studio",
@@ -2327,13 +2670,14 @@ async function startMcpServer() {
2327
2670
  parent: z.string().optional().describe('Parent: "StarterGui", "PlayerGui", or path. Default: StarterGui'),
2328
2671
  tree: z.record(z.unknown()).describe(
2329
2672
  'Root UI node: {ClassName, Name, properties..., Children: [{...}, ...]}. UDim2 as [xScale, xOffset, yScale, yOffset], Color3 as "#hex", Font as "GothamBold"'
2330
- )
2673
+ ),
2674
+ animate: z.boolean().optional().describe("If true, task.wait() will be injected between creation of nodes so that the UI writes out dynamically.")
2331
2675
  },
2332
- async ({ parent, tree }) => {
2676
+ async ({ parent, tree, animate }) => {
2333
2677
  assertConnected();
2334
2678
  const res = await bridge.sendRequest(
2335
2679
  CliMsg.BUILD_UI,
2336
- { parent: parent ?? "StarterGui", tree }
2680
+ { parent: parent ?? "StarterGui", tree, animate }
2337
2681
  );
2338
2682
  return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
2339
2683
  }
@@ -3775,13 +4119,13 @@ var AIProvider = class {
3775
4119
  yield { type: "done" };
3776
4120
  }
3777
4121
  };
3778
- function toolToOpenAI(tool24) {
4122
+ function toolToOpenAI(tool29) {
3779
4123
  return {
3780
4124
  type: "function",
3781
4125
  function: {
3782
- name: tool24.name,
3783
- description: tool24.description,
3784
- parameters: tool24.parameters
4126
+ name: tool29.name,
4127
+ description: tool29.description,
4128
+ parameters: tool29.parameters
3785
4129
  }
3786
4130
  };
3787
4131
  }
@@ -4237,11 +4581,11 @@ var AgentLoop = class {
4237
4581
  // src/tools/registry.ts
4238
4582
  var ToolRegistry = class {
4239
4583
  tools = /* @__PURE__ */ new Map();
4240
- register(tool24) {
4241
- if (this.tools.has(tool24.name)) {
4242
- throw new Error(`Tool already registered: ${tool24.name}`);
4584
+ register(tool29) {
4585
+ if (this.tools.has(tool29.name)) {
4586
+ throw new Error(`Tool already registered: ${tool29.name}`);
4243
4587
  }
4244
- this.tools.set(tool24.name, tool24);
4588
+ this.tools.set(tool29.name, tool29);
4245
4589
  }
4246
4590
  get(name) {
4247
4591
  return this.tools.get(name);
@@ -4253,12 +4597,12 @@ var ToolRegistry = class {
4253
4597
  return this.getAll();
4254
4598
  }
4255
4599
  async execute(call, ctx) {
4256
- const tool24 = this.tools.get(call.name);
4257
- if (!tool24) {
4600
+ const tool29 = this.tools.get(call.name);
4601
+ if (!tool29) {
4258
4602
  return { success: false, error: `Unknown tool: ${call.name}` };
4259
4603
  }
4260
4604
  try {
4261
- return await tool24.execute(call.arguments, ctx);
4605
+ return await tool29.execute(call.arguments, ctx);
4262
4606
  } catch (err) {
4263
4607
  const message = err instanceof Error ? err.message : String(err);
4264
4608
  return { success: false, error: message };
@@ -4276,6 +4620,7 @@ function createDefaultRegistry() {
4276
4620
  Promise.resolve().then(() => (init_run_code(), run_code_exports)),
4277
4621
  Promise.resolve().then(() => (init_get_explorer(), get_explorer_exports)),
4278
4622
  Promise.resolve().then(() => (init_get_properties(), get_properties_exports)),
4623
+ Promise.resolve().then(() => (init_get_descendants_properties(), get_descendants_properties_exports)),
4279
4624
  Promise.resolve().then(() => (init_set_properties(), set_properties_exports)),
4280
4625
  Promise.resolve().then(() => (init_insert_instance(), insert_instance_exports)),
4281
4626
  Promise.resolve().then(() => (init_delete_instance(), delete_instance_exports)),
@@ -4291,6 +4636,10 @@ function createDefaultRegistry() {
4291
4636
  Promise.resolve().then(() => (init_clone_instance(), clone_instance_exports)),
4292
4637
  Promise.resolve().then(() => (init_group_instances(), group_instances_exports)),
4293
4638
  Promise.resolve().then(() => (init_build_multiple(), build_multiple_exports)),
4639
+ Promise.resolve().then(() => (init_serialize_ui(), serialize_ui_exports)),
4640
+ Promise.resolve().then(() => (init_move_instance(), move_instance_exports)),
4641
+ Promise.resolve().then(() => (init_bulk_set_properties(), bulk_set_properties_exports)),
4642
+ Promise.resolve().then(() => (init_find_replace_scripts(), find_replace_scripts_exports)),
4294
4643
  Promise.resolve().then(() => (init_recall(), recall_exports)),
4295
4644
  Promise.resolve().then(() => (init_remember(), remember_exports)),
4296
4645
  Promise.resolve().then(() => (init_upload_asset(), upload_asset_exports))
@@ -4301,6 +4650,10 @@ function createDefaultRegistry() {
4301
4650
  if (m.tool) registry.register(m.tool);
4302
4651
  })
4303
4652
  );
4653
+ void Promise.resolve().then(() => (init_undo_redo(), undo_redo_exports)).then((m) => {
4654
+ registry.register(m.undoTool);
4655
+ registry.register(m.redoTool);
4656
+ });
4304
4657
  return registry;
4305
4658
  }
4306
4659