dominus-cli 0.2.0 → 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 +597 -148
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +19 -4
- package/dist/mcp.js.map +1 -1
- package/package.json +17 -16
- package/plugin/Dominus.rbxm +0 -0
- package/plugin/src/Executor.lua +1 -0
- package/plugin/src/Explorer.lua +123 -1
- package/plugin/src/Properties.lua +213 -28
- package/plugin/src/UIBuilder.lua +494 -32
- package/plugin/src/init.server.lua +111 -22
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import fs5, { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
3
|
+
import path4, { join } from 'path';
|
|
4
|
+
import os3, { tmpdir } from 'os';
|
|
5
5
|
import { nanoid } from 'nanoid';
|
|
6
6
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
7
7
|
import { EventEmitter } from 'events';
|
|
@@ -18,6 +18,7 @@ import { execSync } from 'child_process';
|
|
|
18
18
|
import { randomBytes } from 'crypto';
|
|
19
19
|
import Spinner from 'ink-spinner';
|
|
20
20
|
import OpenAI from 'openai';
|
|
21
|
+
import https from 'https';
|
|
21
22
|
|
|
22
23
|
var __defProp = Object.defineProperty;
|
|
23
24
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -55,14 +56,14 @@ function resolveProviderSettings(config) {
|
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
function ensureDir() {
|
|
58
|
-
if (!
|
|
59
|
-
|
|
59
|
+
if (!fs5.existsSync(DOMINUS_DIR)) {
|
|
60
|
+
fs5.mkdirSync(DOMINUS_DIR, { recursive: true });
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
function readJson(filePath, defaults) {
|
|
63
64
|
try {
|
|
64
|
-
if (
|
|
65
|
-
const raw =
|
|
65
|
+
if (fs5.existsSync(filePath)) {
|
|
66
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
66
67
|
return { ...defaults, ...JSON.parse(raw) };
|
|
67
68
|
}
|
|
68
69
|
} catch {
|
|
@@ -71,7 +72,7 @@ function readJson(filePath, defaults) {
|
|
|
71
72
|
}
|
|
72
73
|
function writeJson(filePath, data) {
|
|
73
74
|
ensureDir();
|
|
74
|
-
|
|
75
|
+
fs5.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
75
76
|
}
|
|
76
77
|
function loadConfig() {
|
|
77
78
|
ensureDir();
|
|
@@ -123,10 +124,10 @@ function createConfigAccess() {
|
|
|
123
124
|
var DOMINUS_DIR, CONFIG_PATH, DB_PATH, RULES_PATH, PROVIDERS, DEFAULTS, DEFAULT_RULES;
|
|
124
125
|
var init_config = __esm({
|
|
125
126
|
"src/config/config.ts"() {
|
|
126
|
-
DOMINUS_DIR =
|
|
127
|
-
CONFIG_PATH =
|
|
128
|
-
DB_PATH =
|
|
129
|
-
RULES_PATH =
|
|
127
|
+
DOMINUS_DIR = path4.join(os3.homedir(), ".dominus");
|
|
128
|
+
CONFIG_PATH = path4.join(DOMINUS_DIR, "config.json");
|
|
129
|
+
DB_PATH = path4.join(DOMINUS_DIR, "dominus.db");
|
|
130
|
+
RULES_PATH = path4.join(DOMINUS_DIR, "rules.json");
|
|
130
131
|
PROVIDERS = {
|
|
131
132
|
openai: {
|
|
132
133
|
name: "OpenAI",
|
|
@@ -220,6 +221,7 @@ var init_protocol = __esm({
|
|
|
220
221
|
SET_SCRIPT: "cli:set:script",
|
|
221
222
|
RUN_CODE: "cli:run",
|
|
222
223
|
GET_PROPERTIES: "cli:get:properties",
|
|
224
|
+
GET_DESCENDANTS_PROPERTIES: "cli:get:descendants:properties",
|
|
223
225
|
SET_PROPERTIES: "cli:set:properties",
|
|
224
226
|
INSERT_INSTANCE: "cli:insert",
|
|
225
227
|
DELETE_INSTANCE: "cli:delete",
|
|
@@ -233,7 +235,13 @@ var init_protocol = __esm({
|
|
|
233
235
|
END_TEST: "cli:test:end",
|
|
234
236
|
BUILD_UI: "cli:build:ui",
|
|
235
237
|
GET_REFLECTION: "cli:get:reflection",
|
|
236
|
-
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"
|
|
237
245
|
};
|
|
238
246
|
}
|
|
239
247
|
});
|
|
@@ -641,14 +649,14 @@ __export(store_exports, {
|
|
|
641
649
|
function persist() {
|
|
642
650
|
if (!db) return;
|
|
643
651
|
const data = db.export();
|
|
644
|
-
|
|
652
|
+
fs5.writeFileSync(dbPath, Buffer.from(data));
|
|
645
653
|
}
|
|
646
654
|
async function initMemoryStore() {
|
|
647
655
|
if (db) return;
|
|
648
656
|
dbPath = getDbPath();
|
|
649
657
|
const SQL = await initSqlJs();
|
|
650
|
-
if (
|
|
651
|
-
const buffer =
|
|
658
|
+
if (fs5.existsSync(dbPath)) {
|
|
659
|
+
const buffer = fs5.readFileSync(dbPath);
|
|
652
660
|
db = new SQL.Database(buffer);
|
|
653
661
|
} else {
|
|
654
662
|
db = new SQL.Database();
|
|
@@ -939,10 +947,10 @@ var init_read_script = __esm({
|
|
|
939
947
|
if (!ctx.isStudioConnected()) {
|
|
940
948
|
return { success: false, error: "Studio is not connected" };
|
|
941
949
|
}
|
|
942
|
-
const
|
|
943
|
-
const response = await ctx.sendToStudio(CliMsg.GET_SCRIPT, { path:
|
|
950
|
+
const path5 = params.path;
|
|
951
|
+
const response = await ctx.sendToStudio(CliMsg.GET_SCRIPT, { path: path5 });
|
|
944
952
|
if (!response.payload) {
|
|
945
|
-
return { success: false, error: `Script not found: ${
|
|
953
|
+
return { success: false, error: `Script not found: ${path5}` };
|
|
946
954
|
}
|
|
947
955
|
return {
|
|
948
956
|
success: true,
|
|
@@ -988,14 +996,14 @@ var init_edit_script = __esm({
|
|
|
988
996
|
if (!ctx.isStudioConnected()) {
|
|
989
997
|
return { success: false, error: "Studio is not connected" };
|
|
990
998
|
}
|
|
991
|
-
const
|
|
999
|
+
const path5 = params.path;
|
|
992
1000
|
const source = params.source;
|
|
993
|
-
const response = await ctx.sendToStudio(CliMsg.SET_SCRIPT, { path:
|
|
1001
|
+
const response = await ctx.sendToStudio(CliMsg.SET_SCRIPT, { path: path5, source });
|
|
994
1002
|
const payload = response.payload;
|
|
995
1003
|
if (!payload.success) {
|
|
996
1004
|
return { success: false, error: payload.error ?? "Failed to edit script" };
|
|
997
1005
|
}
|
|
998
|
-
return { success: true, data: { path:
|
|
1006
|
+
return { success: true, data: { path: path5, linesWritten: source.split("\n").length } };
|
|
999
1007
|
}
|
|
1000
1008
|
};
|
|
1001
1009
|
}
|
|
@@ -1118,16 +1126,65 @@ var init_get_properties = __esm({
|
|
|
1118
1126
|
}
|
|
1119
1127
|
});
|
|
1120
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
|
+
|
|
1121
1178
|
// src/tools/studio/set-properties.ts
|
|
1122
1179
|
var set_properties_exports = {};
|
|
1123
1180
|
__export(set_properties_exports, {
|
|
1124
|
-
tool: () =>
|
|
1181
|
+
tool: () => tool7
|
|
1125
1182
|
});
|
|
1126
|
-
var
|
|
1183
|
+
var tool7;
|
|
1127
1184
|
var init_set_properties = __esm({
|
|
1128
1185
|
"src/tools/studio/set-properties.ts"() {
|
|
1129
1186
|
init_protocol();
|
|
1130
|
-
|
|
1187
|
+
tool7 = {
|
|
1131
1188
|
name: "set_properties",
|
|
1132
1189
|
description: "Set properties on an instance in Roblox Studio.",
|
|
1133
1190
|
parameters: {
|
|
@@ -1165,13 +1222,13 @@ var init_set_properties = __esm({
|
|
|
1165
1222
|
// src/tools/studio/insert-instance.ts
|
|
1166
1223
|
var insert_instance_exports = {};
|
|
1167
1224
|
__export(insert_instance_exports, {
|
|
1168
|
-
tool: () =>
|
|
1225
|
+
tool: () => tool8
|
|
1169
1226
|
});
|
|
1170
|
-
var
|
|
1227
|
+
var tool8;
|
|
1171
1228
|
var init_insert_instance = __esm({
|
|
1172
1229
|
"src/tools/studio/insert-instance.ts"() {
|
|
1173
1230
|
init_protocol();
|
|
1174
|
-
|
|
1231
|
+
tool8 = {
|
|
1175
1232
|
name: "insert_instance",
|
|
1176
1233
|
description: "Insert a new instance into the Roblox Studio DataModel.",
|
|
1177
1234
|
parameters: {
|
|
@@ -1219,13 +1276,13 @@ var init_insert_instance = __esm({
|
|
|
1219
1276
|
// src/tools/studio/delete-instance.ts
|
|
1220
1277
|
var delete_instance_exports = {};
|
|
1221
1278
|
__export(delete_instance_exports, {
|
|
1222
|
-
tool: () =>
|
|
1279
|
+
tool: () => tool9
|
|
1223
1280
|
});
|
|
1224
|
-
var
|
|
1281
|
+
var tool9;
|
|
1225
1282
|
var init_delete_instance = __esm({
|
|
1226
1283
|
"src/tools/studio/delete-instance.ts"() {
|
|
1227
1284
|
init_protocol();
|
|
1228
|
-
|
|
1285
|
+
tool9 = {
|
|
1229
1286
|
name: "delete_instance",
|
|
1230
1287
|
description: "Delete an instance from the Roblox Studio DataModel by path.",
|
|
1231
1288
|
parameters: {
|
|
@@ -1256,13 +1313,13 @@ var init_delete_instance = __esm({
|
|
|
1256
1313
|
// src/tools/studio/get-output.ts
|
|
1257
1314
|
var get_output_exports = {};
|
|
1258
1315
|
__export(get_output_exports, {
|
|
1259
|
-
tool: () =>
|
|
1316
|
+
tool: () => tool10
|
|
1260
1317
|
});
|
|
1261
|
-
var
|
|
1318
|
+
var tool10;
|
|
1262
1319
|
var init_get_output = __esm({
|
|
1263
1320
|
"src/tools/studio/get-output.ts"() {
|
|
1264
1321
|
init_protocol();
|
|
1265
|
-
|
|
1322
|
+
tool10 = {
|
|
1266
1323
|
name: "get_output",
|
|
1267
1324
|
description: "Get recent output log entries from Roblox Studio (prints, warnings, errors).",
|
|
1268
1325
|
parameters: {
|
|
@@ -1297,13 +1354,13 @@ var init_get_output = __esm({
|
|
|
1297
1354
|
// src/tools/studio/get-selection.ts
|
|
1298
1355
|
var get_selection_exports = {};
|
|
1299
1356
|
__export(get_selection_exports, {
|
|
1300
|
-
tool: () =>
|
|
1357
|
+
tool: () => tool11
|
|
1301
1358
|
});
|
|
1302
|
-
var
|
|
1359
|
+
var tool11;
|
|
1303
1360
|
var init_get_selection = __esm({
|
|
1304
1361
|
"src/tools/studio/get-selection.ts"() {
|
|
1305
1362
|
init_protocol();
|
|
1306
|
-
|
|
1363
|
+
tool11 = {
|
|
1307
1364
|
name: "get_selection",
|
|
1308
1365
|
description: "Get the currently selected instances in Roblox Studio.",
|
|
1309
1366
|
parameters: {
|
|
@@ -1324,13 +1381,13 @@ var init_get_selection = __esm({
|
|
|
1324
1381
|
// src/tools/studio/search-scripts.ts
|
|
1325
1382
|
var search_scripts_exports = {};
|
|
1326
1383
|
__export(search_scripts_exports, {
|
|
1327
|
-
tool: () =>
|
|
1384
|
+
tool: () => tool12
|
|
1328
1385
|
});
|
|
1329
|
-
var
|
|
1386
|
+
var tool12;
|
|
1330
1387
|
var init_search_scripts = __esm({
|
|
1331
1388
|
"src/tools/studio/search-scripts.ts"() {
|
|
1332
1389
|
init_protocol();
|
|
1333
|
-
|
|
1390
|
+
tool12 = {
|
|
1334
1391
|
name: "search_scripts",
|
|
1335
1392
|
description: "Search across all scripts in Roblox Studio for a text pattern. Returns matching lines with context.",
|
|
1336
1393
|
parameters: {
|
|
@@ -1365,13 +1422,13 @@ var init_search_scripts = __esm({
|
|
|
1365
1422
|
// src/tools/studio/run-tests.ts
|
|
1366
1423
|
var run_tests_exports = {};
|
|
1367
1424
|
__export(run_tests_exports, {
|
|
1368
|
-
tool: () =>
|
|
1425
|
+
tool: () => tool13
|
|
1369
1426
|
});
|
|
1370
|
-
var
|
|
1427
|
+
var tool13;
|
|
1371
1428
|
var init_run_tests = __esm({
|
|
1372
1429
|
"src/tools/studio/run-tests.ts"() {
|
|
1373
1430
|
init_protocol();
|
|
1374
|
-
|
|
1431
|
+
tool13 = {
|
|
1375
1432
|
name: "run_tests",
|
|
1376
1433
|
description: "Run tests in Roblox Studio using StudioTestService. Can run all tests or a specific test by name.",
|
|
1377
1434
|
parameters: {
|
|
@@ -1405,13 +1462,13 @@ var init_run_tests = __esm({
|
|
|
1405
1462
|
// src/tools/studio/get-class-info.ts
|
|
1406
1463
|
var get_class_info_exports = {};
|
|
1407
1464
|
__export(get_class_info_exports, {
|
|
1408
|
-
tool: () =>
|
|
1465
|
+
tool: () => tool14
|
|
1409
1466
|
});
|
|
1410
|
-
var
|
|
1467
|
+
var tool14;
|
|
1411
1468
|
var init_get_class_info = __esm({
|
|
1412
1469
|
"src/tools/studio/get-class-info.ts"() {
|
|
1413
1470
|
init_protocol();
|
|
1414
|
-
|
|
1471
|
+
tool14 = {
|
|
1415
1472
|
name: "get_class_info",
|
|
1416
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).",
|
|
1417
1474
|
parameters: {
|
|
@@ -1442,13 +1499,13 @@ var init_get_class_info = __esm({
|
|
|
1442
1499
|
// src/tools/studio/create-ui.ts
|
|
1443
1500
|
var create_ui_exports = {};
|
|
1444
1501
|
__export(create_ui_exports, {
|
|
1445
|
-
tool: () =>
|
|
1502
|
+
tool: () => tool15
|
|
1446
1503
|
});
|
|
1447
|
-
var
|
|
1504
|
+
var tool15;
|
|
1448
1505
|
var init_create_ui = __esm({
|
|
1449
1506
|
"src/tools/studio/create-ui.ts"() {
|
|
1450
1507
|
init_protocol();
|
|
1451
|
-
|
|
1508
|
+
tool15 = {
|
|
1452
1509
|
name: "create_ui",
|
|
1453
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}',
|
|
1454
1511
|
parameters: {
|
|
@@ -1460,7 +1517,11 @@ var init_create_ui = __esm({
|
|
|
1460
1517
|
},
|
|
1461
1518
|
tree: {
|
|
1462
1519
|
type: "object",
|
|
1463
|
-
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 [
|
|
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."
|
|
1464
1525
|
}
|
|
1465
1526
|
},
|
|
1466
1527
|
required: ["tree"]
|
|
@@ -1471,6 +1532,7 @@ var init_create_ui = __esm({
|
|
|
1471
1532
|
}
|
|
1472
1533
|
const response = await ctx.sendToStudio(CliMsg.BUILD_UI, {
|
|
1473
1534
|
parent: params.parent ?? "StarterGui",
|
|
1535
|
+
animate: params.animate,
|
|
1474
1536
|
tree: params.tree
|
|
1475
1537
|
});
|
|
1476
1538
|
const payload = response.payload;
|
|
@@ -1489,13 +1551,13 @@ var init_create_ui = __esm({
|
|
|
1489
1551
|
// src/tools/studio/execute-run-test.ts
|
|
1490
1552
|
var execute_run_test_exports = {};
|
|
1491
1553
|
__export(execute_run_test_exports, {
|
|
1492
|
-
tool: () =>
|
|
1554
|
+
tool: () => tool16
|
|
1493
1555
|
});
|
|
1494
|
-
var
|
|
1556
|
+
var tool16;
|
|
1495
1557
|
var init_execute_run_test = __esm({
|
|
1496
1558
|
"src/tools/studio/execute-run-test.ts"() {
|
|
1497
1559
|
init_protocol();
|
|
1498
|
-
|
|
1560
|
+
tool16 = {
|
|
1499
1561
|
name: "execute_run_test",
|
|
1500
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.",
|
|
1501
1563
|
parameters: {
|
|
@@ -1534,13 +1596,13 @@ var init_execute_run_test = __esm({
|
|
|
1534
1596
|
// src/tools/studio/execute-play-test.ts
|
|
1535
1597
|
var execute_play_test_exports = {};
|
|
1536
1598
|
__export(execute_play_test_exports, {
|
|
1537
|
-
tool: () =>
|
|
1599
|
+
tool: () => tool17
|
|
1538
1600
|
});
|
|
1539
|
-
var
|
|
1601
|
+
var tool17;
|
|
1540
1602
|
var init_execute_play_test = __esm({
|
|
1541
1603
|
"src/tools/studio/execute-play-test.ts"() {
|
|
1542
1604
|
init_protocol();
|
|
1543
|
-
|
|
1605
|
+
tool17 = {
|
|
1544
1606
|
name: "execute_play_test",
|
|
1545
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.",
|
|
1546
1608
|
parameters: {
|
|
@@ -1579,13 +1641,13 @@ var init_execute_play_test = __esm({
|
|
|
1579
1641
|
// src/tools/studio/create-part.ts
|
|
1580
1642
|
var create_part_exports = {};
|
|
1581
1643
|
__export(create_part_exports, {
|
|
1582
|
-
tool: () =>
|
|
1644
|
+
tool: () => tool18
|
|
1583
1645
|
});
|
|
1584
|
-
var
|
|
1646
|
+
var tool18;
|
|
1585
1647
|
var init_create_part = __esm({
|
|
1586
1648
|
"src/tools/studio/create-part.ts"() {
|
|
1587
1649
|
init_protocol();
|
|
1588
|
-
|
|
1650
|
+
tool18 = {
|
|
1589
1651
|
name: "create_part",
|
|
1590
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.",
|
|
1591
1653
|
parameters: {
|
|
@@ -1678,11 +1740,11 @@ var init_create_part = __esm({
|
|
|
1678
1740
|
// src/tools/studio/clone-instance.ts
|
|
1679
1741
|
var clone_instance_exports = {};
|
|
1680
1742
|
__export(clone_instance_exports, {
|
|
1681
|
-
tool: () =>
|
|
1743
|
+
tool: () => tool19
|
|
1682
1744
|
});
|
|
1683
1745
|
function buildCloneCode(params) {
|
|
1684
|
-
const
|
|
1685
|
-
const parts =
|
|
1746
|
+
const path5 = params.path;
|
|
1747
|
+
const parts = path5.split(".");
|
|
1686
1748
|
let resolve = "game";
|
|
1687
1749
|
for (const p of parts) {
|
|
1688
1750
|
resolve += `["${p}"]`;
|
|
@@ -1713,11 +1775,11 @@ function buildCloneCode(params) {
|
|
|
1713
1775
|
lines.push(`print("Cloned: " .. clone:GetFullName())`);
|
|
1714
1776
|
return lines.join("\n");
|
|
1715
1777
|
}
|
|
1716
|
-
var
|
|
1778
|
+
var tool19;
|
|
1717
1779
|
var init_clone_instance = __esm({
|
|
1718
1780
|
"src/tools/studio/clone-instance.ts"() {
|
|
1719
1781
|
init_protocol();
|
|
1720
|
-
|
|
1782
|
+
tool19 = {
|
|
1721
1783
|
name: "clone_instance",
|
|
1722
1784
|
description: "Clone an existing instance in Roblox Studio and optionally reposition/rename it. Great for duplicating parts, models, or entire trees.",
|
|
1723
1785
|
parameters: {
|
|
@@ -1753,13 +1815,13 @@ var init_clone_instance = __esm({
|
|
|
1753
1815
|
// src/tools/studio/group-instances.ts
|
|
1754
1816
|
var group_instances_exports = {};
|
|
1755
1817
|
__export(group_instances_exports, {
|
|
1756
|
-
tool: () =>
|
|
1818
|
+
tool: () => tool20
|
|
1757
1819
|
});
|
|
1758
|
-
var
|
|
1820
|
+
var tool20;
|
|
1759
1821
|
var init_group_instances = __esm({
|
|
1760
1822
|
"src/tools/studio/group-instances.ts"() {
|
|
1761
1823
|
init_protocol();
|
|
1762
|
-
|
|
1824
|
+
tool20 = {
|
|
1763
1825
|
name: "group_instances",
|
|
1764
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).',
|
|
1765
1827
|
parameters: {
|
|
@@ -1819,12 +1881,12 @@ var init_group_instances = __esm({
|
|
|
1819
1881
|
// src/tools/studio/build-multiple.ts
|
|
1820
1882
|
var build_multiple_exports = {};
|
|
1821
1883
|
__export(build_multiple_exports, {
|
|
1822
|
-
tool: () =>
|
|
1884
|
+
tool: () => tool21
|
|
1823
1885
|
});
|
|
1824
1886
|
function generateBatchCode(parts, groupName, groupParent) {
|
|
1825
1887
|
const lines = [];
|
|
1826
|
-
function resolveParent(
|
|
1827
|
-
const segs =
|
|
1888
|
+
function resolveParent(path5) {
|
|
1889
|
+
const segs = path5.split(".");
|
|
1828
1890
|
let r = "game";
|
|
1829
1891
|
for (const s of segs) r += `["${s}"]`;
|
|
1830
1892
|
return r;
|
|
@@ -1878,11 +1940,11 @@ function generateBatchCode(parts, groupName, groupParent) {
|
|
|
1878
1940
|
lines.push(`print("Built ${parts.length} parts${groupName ? ` in ${groupName}` : ""}")`);
|
|
1879
1941
|
return lines.join("\n");
|
|
1880
1942
|
}
|
|
1881
|
-
var
|
|
1943
|
+
var tool21;
|
|
1882
1944
|
var init_build_multiple = __esm({
|
|
1883
1945
|
"src/tools/studio/build-multiple.ts"() {
|
|
1884
1946
|
init_protocol();
|
|
1885
|
-
|
|
1947
|
+
tool21 = {
|
|
1886
1948
|
name: "build_multiple",
|
|
1887
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.",
|
|
1888
1950
|
parameters: {
|
|
@@ -1951,16 +2013,234 @@ var init_build_multiple = __esm({
|
|
|
1951
2013
|
}
|
|
1952
2014
|
});
|
|
1953
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
|
+
|
|
1954
2234
|
// src/tools/memory/recall.ts
|
|
1955
2235
|
var recall_exports = {};
|
|
1956
2236
|
__export(recall_exports, {
|
|
1957
|
-
tool: () =>
|
|
2237
|
+
tool: () => tool26
|
|
1958
2238
|
});
|
|
1959
|
-
var
|
|
2239
|
+
var tool26;
|
|
1960
2240
|
var init_recall = __esm({
|
|
1961
2241
|
"src/tools/memory/recall.ts"() {
|
|
1962
2242
|
init_store();
|
|
1963
|
-
|
|
2243
|
+
tool26 = {
|
|
1964
2244
|
name: "recall_memory",
|
|
1965
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.",
|
|
1966
2246
|
parameters: {
|
|
@@ -1999,13 +2279,13 @@ var init_recall = __esm({
|
|
|
1999
2279
|
// src/tools/memory/remember.ts
|
|
2000
2280
|
var remember_exports = {};
|
|
2001
2281
|
__export(remember_exports, {
|
|
2002
|
-
tool: () =>
|
|
2282
|
+
tool: () => tool27
|
|
2003
2283
|
});
|
|
2004
|
-
var
|
|
2284
|
+
var tool27;
|
|
2005
2285
|
var init_remember = __esm({
|
|
2006
2286
|
"src/tools/memory/remember.ts"() {
|
|
2007
2287
|
init_store();
|
|
2008
|
-
|
|
2288
|
+
tool27 = {
|
|
2009
2289
|
name: "remember",
|
|
2010
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.",
|
|
2011
2291
|
parameters: {
|
|
@@ -2045,12 +2325,12 @@ var init_remember = __esm({
|
|
|
2045
2325
|
// src/tools/cloud/upload-asset.ts
|
|
2046
2326
|
var upload_asset_exports = {};
|
|
2047
2327
|
__export(upload_asset_exports, {
|
|
2048
|
-
tool: () =>
|
|
2328
|
+
tool: () => tool28
|
|
2049
2329
|
});
|
|
2050
|
-
var
|
|
2330
|
+
var tool28;
|
|
2051
2331
|
var init_upload_asset = __esm({
|
|
2052
2332
|
"src/tools/cloud/upload-asset.ts"() {
|
|
2053
|
-
|
|
2333
|
+
tool28 = {
|
|
2054
2334
|
name: "upload_asset",
|
|
2055
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.",
|
|
2056
2336
|
parameters: {
|
|
@@ -2088,11 +2368,11 @@ var init_upload_asset = __esm({
|
|
|
2088
2368
|
const assetType = params.assetType;
|
|
2089
2369
|
const name = params.name;
|
|
2090
2370
|
const description = params.description ?? "";
|
|
2091
|
-
if (!
|
|
2371
|
+
if (!fs5.existsSync(filePath)) {
|
|
2092
2372
|
return { success: false, error: `File not found: ${filePath}` };
|
|
2093
2373
|
}
|
|
2094
|
-
const fileBuffer =
|
|
2095
|
-
const fileName =
|
|
2374
|
+
const fileBuffer = fs5.readFileSync(filePath);
|
|
2375
|
+
const fileName = path4.basename(filePath);
|
|
2096
2376
|
const typeMapping = {
|
|
2097
2377
|
Image: "Decal",
|
|
2098
2378
|
Audio: "Audio",
|
|
@@ -2146,6 +2426,57 @@ var init_upload_asset = __esm({
|
|
|
2146
2426
|
}
|
|
2147
2427
|
});
|
|
2148
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
|
+
|
|
2149
2480
|
// src/mcp/server.ts
|
|
2150
2481
|
var server_exports = {};
|
|
2151
2482
|
__export(server_exports, {
|
|
@@ -2166,15 +2497,15 @@ async function startMcpServer() {
|
|
|
2166
2497
|
}
|
|
2167
2498
|
const mcp = new McpServer({
|
|
2168
2499
|
name: "dominus",
|
|
2169
|
-
version: "0.2.
|
|
2500
|
+
version: "0.2.1"
|
|
2170
2501
|
});
|
|
2171
2502
|
mcp.tool(
|
|
2172
2503
|
"read_script",
|
|
2173
2504
|
"Read the source code of a script in Roblox Studio",
|
|
2174
2505
|
{ path: z.string().describe('Full instance path, e.g. "ServerScriptService.GameManager"') },
|
|
2175
|
-
async ({ path:
|
|
2506
|
+
async ({ path: path5 }) => {
|
|
2176
2507
|
assertConnected();
|
|
2177
|
-
const res = await bridge.sendRequest(CliMsg.GET_SCRIPT, { path:
|
|
2508
|
+
const res = await bridge.sendRequest(CliMsg.GET_SCRIPT, { path: path5 });
|
|
2178
2509
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2179
2510
|
}
|
|
2180
2511
|
);
|
|
@@ -2185,9 +2516,9 @@ async function startMcpServer() {
|
|
|
2185
2516
|
path: z.string().describe("Full instance path"),
|
|
2186
2517
|
source: z.string().describe("New complete source code")
|
|
2187
2518
|
},
|
|
2188
|
-
async ({ path:
|
|
2519
|
+
async ({ path: path5, source }) => {
|
|
2189
2520
|
assertConnected();
|
|
2190
|
-
const res = await bridge.sendRequest(CliMsg.SET_SCRIPT, { path:
|
|
2521
|
+
const res = await bridge.sendRequest(CliMsg.SET_SCRIPT, { path: path5, source });
|
|
2191
2522
|
return { content: [{ type: "text", text: JSON.stringify(res.payload) }] };
|
|
2192
2523
|
}
|
|
2193
2524
|
);
|
|
@@ -2221,9 +2552,22 @@ async function startMcpServer() {
|
|
|
2221
2552
|
"get_properties",
|
|
2222
2553
|
"Get all properties of an instance in Roblox Studio",
|
|
2223
2554
|
{ path: z.string().describe("Full instance path") },
|
|
2224
|
-
async ({ path:
|
|
2555
|
+
async ({ path: path5 }) => {
|
|
2225
2556
|
assertConnected();
|
|
2226
|
-
const res = await bridge.sendRequest(CliMsg.GET_PROPERTIES, { path:
|
|
2557
|
+
const res = await bridge.sendRequest(CliMsg.GET_PROPERTIES, { path: path5 });
|
|
2558
|
+
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2559
|
+
}
|
|
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);
|
|
2227
2571
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2228
2572
|
}
|
|
2229
2573
|
);
|
|
@@ -2234,9 +2578,9 @@ async function startMcpServer() {
|
|
|
2234
2578
|
path: z.string().describe("Full instance path"),
|
|
2235
2579
|
properties: z.record(z.unknown()).describe("Key-value pairs of properties to set")
|
|
2236
2580
|
},
|
|
2237
|
-
async ({ path:
|
|
2581
|
+
async ({ path: path5, properties }) => {
|
|
2238
2582
|
assertConnected();
|
|
2239
|
-
const res = await bridge.sendRequest(CliMsg.SET_PROPERTIES, { path:
|
|
2583
|
+
const res = await bridge.sendRequest(CliMsg.SET_PROPERTIES, { path: path5, properties });
|
|
2240
2584
|
return { content: [{ type: "text", text: JSON.stringify(res.payload) }] };
|
|
2241
2585
|
}
|
|
2242
2586
|
);
|
|
@@ -2264,9 +2608,9 @@ async function startMcpServer() {
|
|
|
2264
2608
|
"delete_instance",
|
|
2265
2609
|
"Delete an instance from the Roblox Studio DataModel",
|
|
2266
2610
|
{ path: z.string().describe("Full instance path to delete") },
|
|
2267
|
-
async ({ path:
|
|
2611
|
+
async ({ path: path5 }) => {
|
|
2268
2612
|
assertConnected();
|
|
2269
|
-
const res = await bridge.sendRequest(CliMsg.DELETE_INSTANCE, { path:
|
|
2613
|
+
const res = await bridge.sendRequest(CliMsg.DELETE_INSTANCE, { path: path5 });
|
|
2270
2614
|
return { content: [{ type: "text", text: JSON.stringify(res.payload) }] };
|
|
2271
2615
|
}
|
|
2272
2616
|
);
|
|
@@ -2326,13 +2670,14 @@ async function startMcpServer() {
|
|
|
2326
2670
|
parent: z.string().optional().describe('Parent: "StarterGui", "PlayerGui", or path. Default: StarterGui'),
|
|
2327
2671
|
tree: z.record(z.unknown()).describe(
|
|
2328
2672
|
'Root UI node: {ClassName, Name, properties..., Children: [{...}, ...]}. UDim2 as [xScale, xOffset, yScale, yOffset], Color3 as "#hex", Font as "GothamBold"'
|
|
2329
|
-
)
|
|
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.")
|
|
2330
2675
|
},
|
|
2331
|
-
async ({ parent, tree }) => {
|
|
2676
|
+
async ({ parent, tree, animate }) => {
|
|
2332
2677
|
assertConnected();
|
|
2333
2678
|
const res = await bridge.sendRequest(
|
|
2334
2679
|
CliMsg.BUILD_UI,
|
|
2335
|
-
{ parent: parent ?? "StarterGui", tree }
|
|
2680
|
+
{ parent: parent ?? "StarterGui", tree, animate }
|
|
2336
2681
|
);
|
|
2337
2682
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2338
2683
|
}
|
|
@@ -2463,8 +2808,8 @@ async function startMcpServer() {
|
|
|
2463
2808
|
const parentParts = parent.split(".");
|
|
2464
2809
|
let parentResolve = "game";
|
|
2465
2810
|
for (const p of parentParts) parentResolve += `["${p}"]`;
|
|
2466
|
-
const resolves = params.paths.map((
|
|
2467
|
-
const parts =
|
|
2811
|
+
const resolves = params.paths.map((path5) => {
|
|
2812
|
+
const parts = path5.split(".");
|
|
2468
2813
|
let r = "game";
|
|
2469
2814
|
for (const part of parts) r += `["${part}"]`;
|
|
2470
2815
|
return r;
|
|
@@ -2504,8 +2849,8 @@ async function startMcpServer() {
|
|
|
2504
2849
|
const parts = params.parts;
|
|
2505
2850
|
const groupName = params.groupName;
|
|
2506
2851
|
const groupParent = params.groupParent ?? "Workspace";
|
|
2507
|
-
function resolveP(
|
|
2508
|
-
const segs =
|
|
2852
|
+
function resolveP(path5) {
|
|
2853
|
+
const segs = path5.split(".");
|
|
2509
2854
|
let r = "game";
|
|
2510
2855
|
for (const s of segs) r += `["${s}"]`;
|
|
2511
2856
|
return r;
|
|
@@ -3774,13 +4119,13 @@ var AIProvider = class {
|
|
|
3774
4119
|
yield { type: "done" };
|
|
3775
4120
|
}
|
|
3776
4121
|
};
|
|
3777
|
-
function toolToOpenAI(
|
|
4122
|
+
function toolToOpenAI(tool29) {
|
|
3778
4123
|
return {
|
|
3779
4124
|
type: "function",
|
|
3780
4125
|
function: {
|
|
3781
|
-
name:
|
|
3782
|
-
description:
|
|
3783
|
-
parameters:
|
|
4126
|
+
name: tool29.name,
|
|
4127
|
+
description: tool29.description,
|
|
4128
|
+
parameters: tool29.parameters
|
|
3784
4129
|
}
|
|
3785
4130
|
};
|
|
3786
4131
|
}
|
|
@@ -4236,11 +4581,11 @@ var AgentLoop = class {
|
|
|
4236
4581
|
// src/tools/registry.ts
|
|
4237
4582
|
var ToolRegistry = class {
|
|
4238
4583
|
tools = /* @__PURE__ */ new Map();
|
|
4239
|
-
register(
|
|
4240
|
-
if (this.tools.has(
|
|
4241
|
-
throw new Error(`Tool already registered: ${
|
|
4584
|
+
register(tool29) {
|
|
4585
|
+
if (this.tools.has(tool29.name)) {
|
|
4586
|
+
throw new Error(`Tool already registered: ${tool29.name}`);
|
|
4242
4587
|
}
|
|
4243
|
-
this.tools.set(
|
|
4588
|
+
this.tools.set(tool29.name, tool29);
|
|
4244
4589
|
}
|
|
4245
4590
|
get(name) {
|
|
4246
4591
|
return this.tools.get(name);
|
|
@@ -4252,12 +4597,12 @@ var ToolRegistry = class {
|
|
|
4252
4597
|
return this.getAll();
|
|
4253
4598
|
}
|
|
4254
4599
|
async execute(call, ctx) {
|
|
4255
|
-
const
|
|
4256
|
-
if (!
|
|
4600
|
+
const tool29 = this.tools.get(call.name);
|
|
4601
|
+
if (!tool29) {
|
|
4257
4602
|
return { success: false, error: `Unknown tool: ${call.name}` };
|
|
4258
4603
|
}
|
|
4259
4604
|
try {
|
|
4260
|
-
return await
|
|
4605
|
+
return await tool29.execute(call.arguments, ctx);
|
|
4261
4606
|
} catch (err) {
|
|
4262
4607
|
const message = err instanceof Error ? err.message : String(err);
|
|
4263
4608
|
return { success: false, error: message };
|
|
@@ -4275,6 +4620,7 @@ function createDefaultRegistry() {
|
|
|
4275
4620
|
Promise.resolve().then(() => (init_run_code(), run_code_exports)),
|
|
4276
4621
|
Promise.resolve().then(() => (init_get_explorer(), get_explorer_exports)),
|
|
4277
4622
|
Promise.resolve().then(() => (init_get_properties(), get_properties_exports)),
|
|
4623
|
+
Promise.resolve().then(() => (init_get_descendants_properties(), get_descendants_properties_exports)),
|
|
4278
4624
|
Promise.resolve().then(() => (init_set_properties(), set_properties_exports)),
|
|
4279
4625
|
Promise.resolve().then(() => (init_insert_instance(), insert_instance_exports)),
|
|
4280
4626
|
Promise.resolve().then(() => (init_delete_instance(), delete_instance_exports)),
|
|
@@ -4290,6 +4636,10 @@ function createDefaultRegistry() {
|
|
|
4290
4636
|
Promise.resolve().then(() => (init_clone_instance(), clone_instance_exports)),
|
|
4291
4637
|
Promise.resolve().then(() => (init_group_instances(), group_instances_exports)),
|
|
4292
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)),
|
|
4293
4643
|
Promise.resolve().then(() => (init_recall(), recall_exports)),
|
|
4294
4644
|
Promise.resolve().then(() => (init_remember(), remember_exports)),
|
|
4295
4645
|
Promise.resolve().then(() => (init_upload_asset(), upload_asset_exports))
|
|
@@ -4300,13 +4650,108 @@ function createDefaultRegistry() {
|
|
|
4300
4650
|
if (m.tool) registry.register(m.tool);
|
|
4301
4651
|
})
|
|
4302
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
|
+
});
|
|
4303
4657
|
return registry;
|
|
4304
4658
|
}
|
|
4305
4659
|
|
|
4306
4660
|
// src/index.tsx
|
|
4307
4661
|
init_store();
|
|
4308
4662
|
init_config();
|
|
4309
|
-
var
|
|
4663
|
+
var CACHE_DIR = path4.join(os3.homedir(), ".dominus");
|
|
4664
|
+
var CACHE_FILE = path4.join(CACHE_DIR, "update-check.json");
|
|
4665
|
+
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
4666
|
+
var NPM_PACKAGE = "dominus-cli";
|
|
4667
|
+
function readCache() {
|
|
4668
|
+
try {
|
|
4669
|
+
if (fs5.existsSync(CACHE_FILE)) {
|
|
4670
|
+
return JSON.parse(fs5.readFileSync(CACHE_FILE, "utf-8"));
|
|
4671
|
+
}
|
|
4672
|
+
} catch {
|
|
4673
|
+
}
|
|
4674
|
+
return { lastCheck: 0, latestVersion: null };
|
|
4675
|
+
}
|
|
4676
|
+
function writeCache(cache) {
|
|
4677
|
+
try {
|
|
4678
|
+
if (!fs5.existsSync(CACHE_DIR)) fs5.mkdirSync(CACHE_DIR, { recursive: true });
|
|
4679
|
+
fs5.writeFileSync(CACHE_FILE, JSON.stringify(cache), "utf-8");
|
|
4680
|
+
} catch {
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
function fetchLatestVersion() {
|
|
4684
|
+
return new Promise((resolve) => {
|
|
4685
|
+
const req = https.get(
|
|
4686
|
+
`https://registry.npmjs.org/${NPM_PACKAGE}/latest`,
|
|
4687
|
+
{ headers: { Accept: "application/json" }, timeout: 5e3 },
|
|
4688
|
+
(res) => {
|
|
4689
|
+
if (res.statusCode !== 200) {
|
|
4690
|
+
resolve(null);
|
|
4691
|
+
res.resume();
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4694
|
+
let body = "";
|
|
4695
|
+
res.on("data", (c) => body += c);
|
|
4696
|
+
res.on("end", () => {
|
|
4697
|
+
try {
|
|
4698
|
+
resolve(JSON.parse(body).version ?? null);
|
|
4699
|
+
} catch {
|
|
4700
|
+
resolve(null);
|
|
4701
|
+
}
|
|
4702
|
+
});
|
|
4703
|
+
}
|
|
4704
|
+
);
|
|
4705
|
+
req.on("error", () => resolve(null));
|
|
4706
|
+
req.on("timeout", () => {
|
|
4707
|
+
req.destroy();
|
|
4708
|
+
resolve(null);
|
|
4709
|
+
});
|
|
4710
|
+
});
|
|
4711
|
+
}
|
|
4712
|
+
function compareVersions(current, latest) {
|
|
4713
|
+
const a = current.split(".").map(Number);
|
|
4714
|
+
const b = latest.split(".").map(Number);
|
|
4715
|
+
for (let i = 0; i < 3; i++) {
|
|
4716
|
+
if ((b[i] ?? 0) > (a[i] ?? 0)) return 1;
|
|
4717
|
+
if ((b[i] ?? 0) < (a[i] ?? 0)) return -1;
|
|
4718
|
+
}
|
|
4719
|
+
return 0;
|
|
4720
|
+
}
|
|
4721
|
+
async function checkForUpdate(currentVersion) {
|
|
4722
|
+
try {
|
|
4723
|
+
const cache = readCache();
|
|
4724
|
+
const now = Date.now();
|
|
4725
|
+
if (now - cache.lastCheck < CHECK_INTERVAL_MS && cache.latestVersion) {
|
|
4726
|
+
if (compareVersions(currentVersion, cache.latestVersion) > 0) {
|
|
4727
|
+
return { updateAvailable: true, currentVersion, latestVersion: cache.latestVersion };
|
|
4728
|
+
}
|
|
4729
|
+
return null;
|
|
4730
|
+
}
|
|
4731
|
+
const latest = await fetchLatestVersion();
|
|
4732
|
+
writeCache({ lastCheck: now, latestVersion: latest });
|
|
4733
|
+
if (latest && compareVersions(currentVersion, latest) > 0) {
|
|
4734
|
+
return { updateAvailable: true, currentVersion, latestVersion: latest };
|
|
4735
|
+
}
|
|
4736
|
+
} catch {
|
|
4737
|
+
}
|
|
4738
|
+
return null;
|
|
4739
|
+
}
|
|
4740
|
+
function formatUpdateMessage(info) {
|
|
4741
|
+
const line1 = `Update available: ${info.currentVersion} \u2192 ${info.latestVersion}`;
|
|
4742
|
+
const line2 = `Run: npm i -g ${NPM_PACKAGE}`;
|
|
4743
|
+
const width = Math.max(line1.length, line2.length) + 4;
|
|
4744
|
+
const pad = (s) => ` \u2502 ${s}${" ".repeat(width - s.length - 4)} \u2502`;
|
|
4745
|
+
return [
|
|
4746
|
+
"",
|
|
4747
|
+
` \u256D${"\u2500".repeat(width)}\u256E`,
|
|
4748
|
+
pad(line1),
|
|
4749
|
+
pad(line2),
|
|
4750
|
+
` \u2570${"\u2500".repeat(width)}\u256F`,
|
|
4751
|
+
""
|
|
4752
|
+
].join("\n");
|
|
4753
|
+
}
|
|
4754
|
+
var VERSION = "0.2.1";
|
|
4310
4755
|
var program = new Command();
|
|
4311
4756
|
program.name("dominus").description("AI-powered autonomous agent for Roblox Studio development").version(VERSION);
|
|
4312
4757
|
program.command("start", { isDefault: true }).description("Launch the Dominus TUI agent").option("-p, --port <port>", "WebSocket server port", "18088").action(async (opts) => {
|
|
@@ -4326,6 +4771,10 @@ program.command("start", { isDefault: true }).description("Launch the Dominus TU
|
|
|
4326
4771
|
setConfigValue("userName", firstName);
|
|
4327
4772
|
}
|
|
4328
4773
|
}
|
|
4774
|
+
checkForUpdate(VERSION).then((info) => {
|
|
4775
|
+
if (info) process.stderr.write(formatUpdateMessage(info));
|
|
4776
|
+
}).catch(() => {
|
|
4777
|
+
});
|
|
4329
4778
|
await launchApp(port);
|
|
4330
4779
|
});
|
|
4331
4780
|
var configCmd = program.command("config").description("Manage Dominus configuration");
|
|
@@ -4416,24 +4865,24 @@ rulesCmd.command("list").description("List all rules").action(() => {
|
|
|
4416
4865
|
});
|
|
4417
4866
|
program.command("install-plugin").description("Build & install the Dominus plugin into Roblox Studio").action(async () => {
|
|
4418
4867
|
const { execSync: execSync2 } = await import('child_process');
|
|
4419
|
-
const packageRoot =
|
|
4420
|
-
const pluginDir =
|
|
4421
|
-
const pluginProject =
|
|
4422
|
-
const studioPlugins =
|
|
4423
|
-
|
|
4868
|
+
const packageRoot = path4.resolve(import.meta.dirname ?? __dirname, "..");
|
|
4869
|
+
const pluginDir = path4.join(packageRoot, "plugin");
|
|
4870
|
+
const pluginProject = path4.join(pluginDir, "default.project.json");
|
|
4871
|
+
const studioPlugins = path4.join(
|
|
4872
|
+
os3.homedir(),
|
|
4424
4873
|
"AppData",
|
|
4425
4874
|
"Local",
|
|
4426
4875
|
"Roblox",
|
|
4427
4876
|
"Plugins"
|
|
4428
4877
|
);
|
|
4429
|
-
const destFile =
|
|
4430
|
-
if (!
|
|
4878
|
+
const destFile = path4.join(studioPlugins, "Dominus.rbxm");
|
|
4879
|
+
if (!fs5.existsSync(pluginProject)) {
|
|
4431
4880
|
console.log(" \u2716 Plugin source not found.");
|
|
4432
4881
|
console.log(` Expected at: ${pluginDir}`);
|
|
4433
4882
|
return;
|
|
4434
4883
|
}
|
|
4435
|
-
if (!
|
|
4436
|
-
|
|
4884
|
+
if (!fs5.existsSync(studioPlugins)) {
|
|
4885
|
+
fs5.mkdirSync(studioPlugins, { recursive: true });
|
|
4437
4886
|
}
|
|
4438
4887
|
let rojoAvailable = false;
|
|
4439
4888
|
try {
|
|
@@ -4460,49 +4909,49 @@ program.command("mcp").description("Start the Dominus MCP server (for Cursor, Cl
|
|
|
4460
4909
|
await startMcpServer2();
|
|
4461
4910
|
});
|
|
4462
4911
|
function getMcpTargets() {
|
|
4463
|
-
const home =
|
|
4464
|
-
const appData = process.env.APPDATA ||
|
|
4465
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
4912
|
+
const home = os3.homedir();
|
|
4913
|
+
const appData = process.env.APPDATA || path4.join(home, "AppData", "Roaming");
|
|
4914
|
+
const localAppData = process.env.LOCALAPPDATA || path4.join(home, "AppData", "Local");
|
|
4466
4915
|
const cwd = process.cwd();
|
|
4467
4916
|
return [
|
|
4468
4917
|
{
|
|
4469
4918
|
name: "Cursor (project)",
|
|
4470
|
-
configPath:
|
|
4919
|
+
configPath: path4.join(cwd, ".cursor", "mcp.json"),
|
|
4471
4920
|
keyPath: ["mcpServers"]
|
|
4472
4921
|
},
|
|
4473
4922
|
{
|
|
4474
4923
|
name: "Cursor (global)",
|
|
4475
|
-
configPath:
|
|
4924
|
+
configPath: path4.join(home, ".cursor", "mcp.json"),
|
|
4476
4925
|
keyPath: ["mcpServers"]
|
|
4477
4926
|
},
|
|
4478
4927
|
{
|
|
4479
4928
|
name: "Claude Desktop",
|
|
4480
|
-
configPath:
|
|
4929
|
+
configPath: path4.join(appData, "Claude", "claude_desktop_config.json"),
|
|
4481
4930
|
keyPath: ["mcpServers"]
|
|
4482
4931
|
},
|
|
4483
4932
|
{
|
|
4484
4933
|
name: "Claude Code",
|
|
4485
|
-
configPath:
|
|
4934
|
+
configPath: path4.join(home, ".claude.json"),
|
|
4486
4935
|
keyPath: ["mcpServers"]
|
|
4487
4936
|
},
|
|
4488
4937
|
{
|
|
4489
4938
|
name: "Windsurf (project)",
|
|
4490
|
-
configPath:
|
|
4939
|
+
configPath: path4.join(cwd, ".windsurf", "mcp.json"),
|
|
4491
4940
|
keyPath: ["mcpServers"]
|
|
4492
4941
|
},
|
|
4493
4942
|
{
|
|
4494
4943
|
name: "Windsurf (global)",
|
|
4495
|
-
configPath:
|
|
4944
|
+
configPath: path4.join(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
4496
4945
|
keyPath: ["mcpServers"]
|
|
4497
4946
|
},
|
|
4498
4947
|
{
|
|
4499
4948
|
name: "VS Code (project)",
|
|
4500
|
-
configPath:
|
|
4949
|
+
configPath: path4.join(cwd, ".vscode", "mcp.json"),
|
|
4501
4950
|
keyPath: ["servers"]
|
|
4502
4951
|
},
|
|
4503
4952
|
{
|
|
4504
4953
|
name: "VS Code (global)",
|
|
4505
|
-
configPath:
|
|
4954
|
+
configPath: path4.join(localAppData, "Code", "User", "settings.json"),
|
|
4506
4955
|
keyPath: ["mcp", "servers"]
|
|
4507
4956
|
}
|
|
4508
4957
|
];
|
|
@@ -4514,9 +4963,9 @@ var dominusEntry = {
|
|
|
4514
4963
|
};
|
|
4515
4964
|
function injectMcpConfig(target) {
|
|
4516
4965
|
let config = {};
|
|
4517
|
-
if (
|
|
4966
|
+
if (fs5.existsSync(target.configPath)) {
|
|
4518
4967
|
try {
|
|
4519
|
-
config = JSON.parse(
|
|
4968
|
+
config = JSON.parse(fs5.readFileSync(target.configPath, "utf-8"));
|
|
4520
4969
|
} catch {
|
|
4521
4970
|
config = {};
|
|
4522
4971
|
}
|
|
@@ -4532,12 +4981,12 @@ function injectMcpConfig(target) {
|
|
|
4532
4981
|
return { status: "already" };
|
|
4533
4982
|
}
|
|
4534
4983
|
obj["dominus"] = { ...dominusEntry };
|
|
4535
|
-
const dir =
|
|
4536
|
-
if (!
|
|
4537
|
-
|
|
4984
|
+
const dir = path4.dirname(target.configPath);
|
|
4985
|
+
if (!fs5.existsSync(dir)) {
|
|
4986
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
4538
4987
|
}
|
|
4539
|
-
|
|
4540
|
-
return { status:
|
|
4988
|
+
fs5.writeFileSync(target.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4989
|
+
return { status: fs5.existsSync(target.configPath) ? "installed" : "created" };
|
|
4541
4990
|
}
|
|
4542
4991
|
program.command("mcp-install").description("Auto-install Dominus MCP into Cursor, VS Code, Claude Desktop, Windsurf, etc.").option("-a, --all", "Install to all detected editors").action(async (opts) => {
|
|
4543
4992
|
const targets = getMcpTargets();
|
|
@@ -4559,7 +5008,7 @@ program.command("mcp-install").description("Auto-install Dominus MCP into Cursor
|
|
|
4559
5008
|
}
|
|
4560
5009
|
const detected = targets.map((t) => ({
|
|
4561
5010
|
target: t,
|
|
4562
|
-
exists:
|
|
5011
|
+
exists: fs5.existsSync(path4.dirname(t.configPath))
|
|
4563
5012
|
}));
|
|
4564
5013
|
const toInstall = detected.filter((d) => d.exists);
|
|
4565
5014
|
if (toInstall.length === 0) {
|
|
@@ -4632,23 +5081,23 @@ program.command("mcp-setup").description("Print MCP configuration for manual set
|
|
|
4632
5081
|
console.log("");
|
|
4633
5082
|
});
|
|
4634
5083
|
function buildPluginXml(pluginDir, destFile) {
|
|
4635
|
-
const srcDir =
|
|
4636
|
-
if (!
|
|
5084
|
+
const srcDir = path4.join(pluginDir, "src");
|
|
5085
|
+
if (!fs5.existsSync(srcDir)) {
|
|
4637
5086
|
console.log(` \u2716 Plugin source directory not found: ${srcDir}`);
|
|
4638
5087
|
return;
|
|
4639
5088
|
}
|
|
4640
|
-
const files =
|
|
5089
|
+
const files = fs5.readdirSync(srcDir).filter((f) => f.endsWith(".lua"));
|
|
4641
5090
|
const mainFile = files.find((f) => f === "init.server.lua");
|
|
4642
5091
|
const moduleFiles = files.filter((f) => f !== "init.server.lua");
|
|
4643
5092
|
if (!mainFile) {
|
|
4644
5093
|
console.log(" \u2716 init.server.lua not found in plugin source");
|
|
4645
5094
|
return;
|
|
4646
5095
|
}
|
|
4647
|
-
const mainSource =
|
|
5096
|
+
const mainSource = fs5.readFileSync(path4.join(srcDir, mainFile), "utf-8");
|
|
4648
5097
|
let moduleItems = "";
|
|
4649
5098
|
for (const mf of moduleFiles) {
|
|
4650
5099
|
const modName = mf.replace(".lua", "");
|
|
4651
|
-
const modSource =
|
|
5100
|
+
const modSource = fs5.readFileSync(path4.join(srcDir, mf), "utf-8");
|
|
4652
5101
|
moduleItems += `
|
|
4653
5102
|
<Item class="ModuleScript" referent="${modName}">
|
|
4654
5103
|
<Properties>
|
|
@@ -4666,7 +5115,7 @@ function buildPluginXml(pluginDir, destFile) {
|
|
|
4666
5115
|
</Properties>${moduleItems}
|
|
4667
5116
|
</Item>
|
|
4668
5117
|
</roblox>`;
|
|
4669
|
-
|
|
5118
|
+
fs5.writeFileSync(destFile, rbxmx, "utf-8");
|
|
4670
5119
|
console.log(` \u2714 Plugin installed to: ${destFile}`);
|
|
4671
5120
|
console.log(" Restart Roblox Studio to load the plugin.");
|
|
4672
5121
|
}
|