dominus-cli 0.1.0 → 0.2.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 +199 -186
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +190 -28
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -459,6 +459,166 @@ var init_ws_server = __esm({
|
|
|
459
459
|
};
|
|
460
460
|
}
|
|
461
461
|
});
|
|
462
|
+
async function isPortInUse(port) {
|
|
463
|
+
return new Promise((resolve) => {
|
|
464
|
+
const ws = new WebSocket(`ws://localhost:${port}`);
|
|
465
|
+
const timer = setTimeout(() => {
|
|
466
|
+
ws.close();
|
|
467
|
+
resolve(false);
|
|
468
|
+
}, 1e3);
|
|
469
|
+
ws.on("open", () => {
|
|
470
|
+
clearTimeout(timer);
|
|
471
|
+
ws.close();
|
|
472
|
+
resolve(true);
|
|
473
|
+
});
|
|
474
|
+
ws.on("error", () => {
|
|
475
|
+
clearTimeout(timer);
|
|
476
|
+
resolve(false);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
var DominusClient;
|
|
481
|
+
var init_ws_client = __esm({
|
|
482
|
+
"src/transport/ws-client.ts"() {
|
|
483
|
+
init_protocol();
|
|
484
|
+
DominusClient = class extends EventEmitter {
|
|
485
|
+
ws = null;
|
|
486
|
+
port;
|
|
487
|
+
studioConnected = false;
|
|
488
|
+
connectionInfo = null;
|
|
489
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
490
|
+
constructor(port = 18088) {
|
|
491
|
+
super();
|
|
492
|
+
this.port = port;
|
|
493
|
+
}
|
|
494
|
+
connect() {
|
|
495
|
+
return new Promise((resolve, reject) => {
|
|
496
|
+
const url = `ws://localhost:${this.port}`;
|
|
497
|
+
const ws = new WebSocket(url);
|
|
498
|
+
ws.on("open", () => {
|
|
499
|
+
this.ws = ws;
|
|
500
|
+
ws.send(serializeMessage(createMessage("controller:connect", {})));
|
|
501
|
+
this.emit("server:started", { port: this.port, relay: true });
|
|
502
|
+
resolve();
|
|
503
|
+
});
|
|
504
|
+
ws.on("message", (raw) => {
|
|
505
|
+
try {
|
|
506
|
+
const msg = parseMessage(raw.toString());
|
|
507
|
+
this.handleMessage(msg);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
this.emit("server:error", err);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
ws.on("close", () => {
|
|
513
|
+
this.ws = null;
|
|
514
|
+
this.studioConnected = false;
|
|
515
|
+
this.emit("studio:disconnected");
|
|
516
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
517
|
+
clearTimeout(pending.timer);
|
|
518
|
+
pending.reject(new Error("Relay connection lost"));
|
|
519
|
+
this.pendingRequests.delete(id);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
ws.on("error", (err) => {
|
|
523
|
+
if (!this.ws) reject(err);
|
|
524
|
+
this.emit("server:error", err);
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
handleMessage(msg) {
|
|
529
|
+
if (msg.type === "controller:welcome") {
|
|
530
|
+
const p = msg.payload;
|
|
531
|
+
this.studioConnected = p.studioConnected;
|
|
532
|
+
if (this.studioConnected) {
|
|
533
|
+
this.emit("studio:connected", {});
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (msg.type === "studio:connected") {
|
|
538
|
+
this.studioConnected = true;
|
|
539
|
+
const payload = msg.payload;
|
|
540
|
+
this.connectionInfo = {
|
|
541
|
+
connectedAt: Date.now(),
|
|
542
|
+
studioVersion: payload.studioVersion,
|
|
543
|
+
placeId: payload.placeId,
|
|
544
|
+
placeName: payload.placeName
|
|
545
|
+
};
|
|
546
|
+
this.emit("studio:connected", payload);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (msg.type === "studio:disconnected") {
|
|
550
|
+
this.studioConnected = false;
|
|
551
|
+
this.connectionInfo = null;
|
|
552
|
+
this.emit("studio:disconnected");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (msg.type === StudioMsg.RESPONSE) {
|
|
556
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
557
|
+
if (pending) {
|
|
558
|
+
clearTimeout(pending.timer);
|
|
559
|
+
pending.resolve(msg);
|
|
560
|
+
this.pendingRequests.delete(msg.id);
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
this.emit(msg.type, msg.payload);
|
|
565
|
+
this.emit("message", msg);
|
|
566
|
+
}
|
|
567
|
+
sendRequest(type, payload, timeoutMs = 1e4) {
|
|
568
|
+
return new Promise((resolve, reject) => {
|
|
569
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
570
|
+
reject(new Error("Not connected to Dominus server"));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (!this.studioConnected) {
|
|
574
|
+
reject(new Error("Studio not connected"));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const msg = createMessage(type, payload);
|
|
578
|
+
const timer = setTimeout(() => {
|
|
579
|
+
this.pendingRequests.delete(msg.id);
|
|
580
|
+
reject(new Error(`Request timed out: ${type}`));
|
|
581
|
+
}, timeoutMs);
|
|
582
|
+
this.pendingRequests.set(msg.id, {
|
|
583
|
+
resolve,
|
|
584
|
+
reject,
|
|
585
|
+
timer
|
|
586
|
+
});
|
|
587
|
+
this.ws.send(serializeMessage(msg));
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
sendNotification(type, payload) {
|
|
591
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
592
|
+
const msg = createMessage(type, payload);
|
|
593
|
+
this.ws.send(serializeMessage(msg));
|
|
594
|
+
}
|
|
595
|
+
isConnected() {
|
|
596
|
+
return this.studioConnected;
|
|
597
|
+
}
|
|
598
|
+
getConnectionInfo() {
|
|
599
|
+
if (!this.connectionInfo) return null;
|
|
600
|
+
return { ...this.connectionInfo, ws: this.ws };
|
|
601
|
+
}
|
|
602
|
+
getPort() {
|
|
603
|
+
return this.port;
|
|
604
|
+
}
|
|
605
|
+
stop() {
|
|
606
|
+
return new Promise((resolve) => {
|
|
607
|
+
for (const [, pending] of this.pendingRequests) {
|
|
608
|
+
clearTimeout(pending.timer);
|
|
609
|
+
pending.reject(new Error("Client shutting down"));
|
|
610
|
+
}
|
|
611
|
+
this.pendingRequests.clear();
|
|
612
|
+
if (this.ws) {
|
|
613
|
+
this.ws.close();
|
|
614
|
+
this.ws = null;
|
|
615
|
+
}
|
|
616
|
+
resolve();
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
});
|
|
462
622
|
|
|
463
623
|
// src/memory/store.ts
|
|
464
624
|
var store_exports = {};
|
|
@@ -1994,11 +2154,19 @@ __export(server_exports, {
|
|
|
1994
2154
|
async function startMcpServer() {
|
|
1995
2155
|
const config = loadConfig();
|
|
1996
2156
|
await initMemoryStore();
|
|
1997
|
-
|
|
1998
|
-
|
|
2157
|
+
const portTaken = await isPortInUse(config.port);
|
|
2158
|
+
if (portTaken) {
|
|
2159
|
+
const client = new DominusClient(config.port);
|
|
2160
|
+
await client.connect();
|
|
2161
|
+
bridge = client;
|
|
2162
|
+
} else {
|
|
2163
|
+
const server = new DominusServer(config.port);
|
|
2164
|
+
await server.start();
|
|
2165
|
+
bridge = server;
|
|
2166
|
+
}
|
|
1999
2167
|
const mcp = new McpServer({
|
|
2000
2168
|
name: "dominus",
|
|
2001
|
-
version: "0.
|
|
2169
|
+
version: "0.2.0"
|
|
2002
2170
|
});
|
|
2003
2171
|
mcp.tool(
|
|
2004
2172
|
"read_script",
|
|
@@ -2006,7 +2174,7 @@ async function startMcpServer() {
|
|
|
2006
2174
|
{ path: z.string().describe('Full instance path, e.g. "ServerScriptService.GameManager"') },
|
|
2007
2175
|
async ({ path: path4 }) => {
|
|
2008
2176
|
assertConnected();
|
|
2009
|
-
const res = await
|
|
2177
|
+
const res = await bridge.sendRequest(CliMsg.GET_SCRIPT, { path: path4 });
|
|
2010
2178
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2011
2179
|
}
|
|
2012
2180
|
);
|
|
@@ -2019,7 +2187,7 @@ async function startMcpServer() {
|
|
|
2019
2187
|
},
|
|
2020
2188
|
async ({ path: path4, source }) => {
|
|
2021
2189
|
assertConnected();
|
|
2022
|
-
const res = await
|
|
2190
|
+
const res = await bridge.sendRequest(CliMsg.SET_SCRIPT, { path: path4, source });
|
|
2023
2191
|
return { content: [{ type: "text", text: JSON.stringify(res.payload) }] };
|
|
2024
2192
|
}
|
|
2025
2193
|
);
|
|
@@ -2029,7 +2197,7 @@ async function startMcpServer() {
|
|
|
2029
2197
|
{ code: z.string().describe("Luau code to execute") },
|
|
2030
2198
|
async ({ code }) => {
|
|
2031
2199
|
assertConnected();
|
|
2032
|
-
const res = await
|
|
2200
|
+
const res = await bridge.sendRequest(CliMsg.RUN_CODE, { code });
|
|
2033
2201
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2034
2202
|
}
|
|
2035
2203
|
);
|
|
@@ -2042,7 +2210,7 @@ async function startMcpServer() {
|
|
|
2042
2210
|
},
|
|
2043
2211
|
async ({ rootPath, maxDepth }) => {
|
|
2044
2212
|
assertConnected();
|
|
2045
|
-
const res = await
|
|
2213
|
+
const res = await bridge.sendRequest(CliMsg.GET_EXPLORER, {
|
|
2046
2214
|
rootPath: rootPath ?? null,
|
|
2047
2215
|
maxDepth
|
|
2048
2216
|
});
|
|
@@ -2055,7 +2223,7 @@ async function startMcpServer() {
|
|
|
2055
2223
|
{ path: z.string().describe("Full instance path") },
|
|
2056
2224
|
async ({ path: path4 }) => {
|
|
2057
2225
|
assertConnected();
|
|
2058
|
-
const res = await
|
|
2226
|
+
const res = await bridge.sendRequest(CliMsg.GET_PROPERTIES, { path: path4 });
|
|
2059
2227
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2060
2228
|
}
|
|
2061
2229
|
);
|
|
@@ -2068,7 +2236,7 @@ async function startMcpServer() {
|
|
|
2068
2236
|
},
|
|
2069
2237
|
async ({ path: path4, properties }) => {
|
|
2070
2238
|
assertConnected();
|
|
2071
|
-
const res = await
|
|
2239
|
+
const res = await bridge.sendRequest(CliMsg.SET_PROPERTIES, { path: path4, properties });
|
|
2072
2240
|
return { content: [{ type: "text", text: JSON.stringify(res.payload) }] };
|
|
2073
2241
|
}
|
|
2074
2242
|
);
|
|
@@ -2083,7 +2251,7 @@ async function startMcpServer() {
|
|
|
2083
2251
|
},
|
|
2084
2252
|
async ({ className, parentPath, name, properties }) => {
|
|
2085
2253
|
assertConnected();
|
|
2086
|
-
const res = await
|
|
2254
|
+
const res = await bridge.sendRequest(CliMsg.INSERT_INSTANCE, {
|
|
2087
2255
|
className,
|
|
2088
2256
|
parentPath,
|
|
2089
2257
|
name,
|
|
@@ -2098,7 +2266,7 @@ async function startMcpServer() {
|
|
|
2098
2266
|
{ path: z.string().describe("Full instance path to delete") },
|
|
2099
2267
|
async ({ path: path4 }) => {
|
|
2100
2268
|
assertConnected();
|
|
2101
|
-
const res = await
|
|
2269
|
+
const res = await bridge.sendRequest(CliMsg.DELETE_INSTANCE, { path: path4 });
|
|
2102
2270
|
return { content: [{ type: "text", text: JSON.stringify(res.payload) }] };
|
|
2103
2271
|
}
|
|
2104
2272
|
);
|
|
@@ -2108,7 +2276,7 @@ async function startMcpServer() {
|
|
|
2108
2276
|
{},
|
|
2109
2277
|
async () => {
|
|
2110
2278
|
assertConnected();
|
|
2111
|
-
const res = await
|
|
2279
|
+
const res = await bridge.sendRequest(CliMsg.GET_SELECTION, {});
|
|
2112
2280
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2113
2281
|
}
|
|
2114
2282
|
);
|
|
@@ -2121,7 +2289,7 @@ async function startMcpServer() {
|
|
|
2121
2289
|
},
|
|
2122
2290
|
async ({ query, maxResults }) => {
|
|
2123
2291
|
assertConnected();
|
|
2124
|
-
const res = await
|
|
2292
|
+
const res = await bridge.sendRequest(CliMsg.SEARCH_SCRIPTS, { query, maxResults });
|
|
2125
2293
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2126
2294
|
}
|
|
2127
2295
|
);
|
|
@@ -2134,7 +2302,7 @@ async function startMcpServer() {
|
|
|
2134
2302
|
},
|
|
2135
2303
|
async ({ limit, level }) => {
|
|
2136
2304
|
assertConnected();
|
|
2137
|
-
const res = await
|
|
2305
|
+
const res = await bridge.sendRequest(CliMsg.GET_OUTPUT, {
|
|
2138
2306
|
limit,
|
|
2139
2307
|
level: level ?? null
|
|
2140
2308
|
});
|
|
@@ -2147,7 +2315,7 @@ async function startMcpServer() {
|
|
|
2147
2315
|
{ className: z.string().describe('Roblox class name, e.g. "Frame", "TextLabel", "Part", "MeshPart"') },
|
|
2148
2316
|
async ({ className }) => {
|
|
2149
2317
|
assertConnected();
|
|
2150
|
-
const res = await
|
|
2318
|
+
const res = await bridge.sendRequest(CliMsg.GET_REFLECTION, { className });
|
|
2151
2319
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2152
2320
|
}
|
|
2153
2321
|
);
|
|
@@ -2162,7 +2330,7 @@ async function startMcpServer() {
|
|
|
2162
2330
|
},
|
|
2163
2331
|
async ({ parent, tree }) => {
|
|
2164
2332
|
assertConnected();
|
|
2165
|
-
const res = await
|
|
2333
|
+
const res = await bridge.sendRequest(
|
|
2166
2334
|
CliMsg.BUILD_UI,
|
|
2167
2335
|
{ parent: parent ?? "StarterGui", tree }
|
|
2168
2336
|
);
|
|
@@ -2175,7 +2343,7 @@ async function startMcpServer() {
|
|
|
2175
2343
|
{ testName: z.string().optional().describe("Specific test name, or omit to run all") },
|
|
2176
2344
|
async ({ testName }) => {
|
|
2177
2345
|
assertConnected();
|
|
2178
|
-
const res = await
|
|
2346
|
+
const res = await bridge.sendRequest(CliMsg.RUN_TESTS, {
|
|
2179
2347
|
testName: testName ?? null
|
|
2180
2348
|
});
|
|
2181
2349
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
@@ -2187,7 +2355,7 @@ async function startMcpServer() {
|
|
|
2187
2355
|
{ args: z.unknown().optional().describe("Arguments passed to the test session. Server scripts get this via StudioTestService:GetTestArgs()") },
|
|
2188
2356
|
async ({ args }) => {
|
|
2189
2357
|
assertConnected();
|
|
2190
|
-
const res = await
|
|
2358
|
+
const res = await bridge.sendRequest(CliMsg.EXECUTE_RUN_TEST, { args: args ?? null }, 12e4);
|
|
2191
2359
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2192
2360
|
}
|
|
2193
2361
|
);
|
|
@@ -2197,7 +2365,7 @@ async function startMcpServer() {
|
|
|
2197
2365
|
{ args: z.unknown().optional().describe("Arguments passed to the test session. Server scripts get this via StudioTestService:GetTestArgs()") },
|
|
2198
2366
|
async ({ args }) => {
|
|
2199
2367
|
assertConnected();
|
|
2200
|
-
const res = await
|
|
2368
|
+
const res = await bridge.sendRequest(CliMsg.EXECUTE_PLAY_TEST, { args: args ?? null }, 12e4);
|
|
2201
2369
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2202
2370
|
}
|
|
2203
2371
|
);
|
|
@@ -2236,7 +2404,7 @@ async function startMcpServer() {
|
|
|
2236
2404
|
if (params.canCollide !== void 0) properties.CanCollide = params.canCollide;
|
|
2237
2405
|
if (params.shape && params.shape !== "Block") properties.Shape = params.shape;
|
|
2238
2406
|
const className = params.shape === "Wedge" ? "WedgePart" : params.shape === "CornerWedge" ? "CornerWedgePart" : "Part";
|
|
2239
|
-
const res = await
|
|
2407
|
+
const res = await bridge.sendRequest(CliMsg.INSERT_INSTANCE, {
|
|
2240
2408
|
className,
|
|
2241
2409
|
parentPath: params.parent ?? "Workspace",
|
|
2242
2410
|
name: params.name,
|
|
@@ -2275,7 +2443,7 @@ async function startMcpServer() {
|
|
|
2275
2443
|
lines.push(`end`);
|
|
2276
2444
|
}
|
|
2277
2445
|
lines.push(`print("Cloned: " .. clone:GetFullName())`);
|
|
2278
|
-
const res = await
|
|
2446
|
+
const res = await bridge.sendRequest(CliMsg.RUN_CODE, { code: lines.join("\n") });
|
|
2279
2447
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2280
2448
|
}
|
|
2281
2449
|
);
|
|
@@ -2308,7 +2476,7 @@ async function startMcpServer() {
|
|
|
2308
2476
|
...resolves.map((r) => `${r}.Parent = group`),
|
|
2309
2477
|
`print("Grouped ${params.paths.length} instances into: " .. group:GetFullName())`
|
|
2310
2478
|
].join("\n");
|
|
2311
|
-
const res = await
|
|
2479
|
+
const res = await bridge.sendRequest(CliMsg.RUN_CODE, { code });
|
|
2312
2480
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2313
2481
|
}
|
|
2314
2482
|
);
|
|
@@ -2367,7 +2535,7 @@ async function startMcpServer() {
|
|
|
2367
2535
|
lines.push(`${v}.Parent = ${groupName ? "_group" : resolveP(p.parent ?? "Workspace")}`);
|
|
2368
2536
|
}
|
|
2369
2537
|
lines.push(`print("Built ${parts.length} parts${groupName ? ` in ${groupName}` : ""}")`);
|
|
2370
|
-
const res = await
|
|
2538
|
+
const res = await bridge.sendRequest(CliMsg.RUN_CODE, { code: lines.join("\n") });
|
|
2371
2539
|
return { content: [{ type: "text", text: JSON.stringify(res.payload, null, 2) }] };
|
|
2372
2540
|
}
|
|
2373
2541
|
);
|
|
@@ -2403,16 +2571,16 @@ async function startMcpServer() {
|
|
|
2403
2571
|
"studio://status",
|
|
2404
2572
|
"studio://status",
|
|
2405
2573
|
async () => {
|
|
2406
|
-
const info =
|
|
2574
|
+
const info = bridge.getConnectionInfo();
|
|
2407
2575
|
return {
|
|
2408
2576
|
contents: [{
|
|
2409
2577
|
uri: "studio://status",
|
|
2410
2578
|
text: JSON.stringify({
|
|
2411
|
-
connected:
|
|
2579
|
+
connected: bridge.isConnected(),
|
|
2412
2580
|
placeName: info?.placeName,
|
|
2413
2581
|
placeId: info?.placeId,
|
|
2414
2582
|
studioVersion: info?.studioVersion,
|
|
2415
|
-
port:
|
|
2583
|
+
port: bridge.getPort()
|
|
2416
2584
|
}, null, 2),
|
|
2417
2585
|
mimeType: "application/json"
|
|
2418
2586
|
}]
|
|
@@ -2423,16 +2591,17 @@ async function startMcpServer() {
|
|
|
2423
2591
|
await mcp.connect(transport);
|
|
2424
2592
|
}
|
|
2425
2593
|
function assertConnected() {
|
|
2426
|
-
if (!
|
|
2594
|
+
if (!bridge.isConnected()) {
|
|
2427
2595
|
throw new Error(
|
|
2428
2596
|
"Roblox Studio is not connected. Open Studio and click the Dominus plugin Connect button."
|
|
2429
2597
|
);
|
|
2430
2598
|
}
|
|
2431
2599
|
}
|
|
2432
|
-
var
|
|
2600
|
+
var bridge;
|
|
2433
2601
|
var init_server = __esm({
|
|
2434
2602
|
"src/mcp/server.ts"() {
|
|
2435
2603
|
init_ws_server();
|
|
2604
|
+
init_ws_client();
|
|
2436
2605
|
init_protocol();
|
|
2437
2606
|
init_store();
|
|
2438
2607
|
init_config();
|
|
@@ -3475,163 +3644,7 @@ var App = ({ server, agent: initialAgent, version }) => {
|
|
|
3475
3644
|
|
|
3476
3645
|
// src/index.tsx
|
|
3477
3646
|
init_ws_server();
|
|
3478
|
-
|
|
3479
|
-
// src/transport/ws-client.ts
|
|
3480
|
-
init_protocol();
|
|
3481
|
-
var DominusClient = class extends EventEmitter {
|
|
3482
|
-
ws = null;
|
|
3483
|
-
port;
|
|
3484
|
-
studioConnected = false;
|
|
3485
|
-
connectionInfo = null;
|
|
3486
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
3487
|
-
constructor(port = 18088) {
|
|
3488
|
-
super();
|
|
3489
|
-
this.port = port;
|
|
3490
|
-
}
|
|
3491
|
-
connect() {
|
|
3492
|
-
return new Promise((resolve, reject) => {
|
|
3493
|
-
const url = `ws://localhost:${this.port}`;
|
|
3494
|
-
const ws = new WebSocket(url);
|
|
3495
|
-
ws.on("open", () => {
|
|
3496
|
-
this.ws = ws;
|
|
3497
|
-
ws.send(serializeMessage(createMessage("controller:connect", {})));
|
|
3498
|
-
this.emit("server:started", { port: this.port, relay: true });
|
|
3499
|
-
resolve();
|
|
3500
|
-
});
|
|
3501
|
-
ws.on("message", (raw) => {
|
|
3502
|
-
try {
|
|
3503
|
-
const msg = parseMessage(raw.toString());
|
|
3504
|
-
this.handleMessage(msg);
|
|
3505
|
-
} catch (err) {
|
|
3506
|
-
this.emit("server:error", err);
|
|
3507
|
-
}
|
|
3508
|
-
});
|
|
3509
|
-
ws.on("close", () => {
|
|
3510
|
-
this.ws = null;
|
|
3511
|
-
this.studioConnected = false;
|
|
3512
|
-
this.emit("studio:disconnected");
|
|
3513
|
-
for (const [id, pending] of this.pendingRequests) {
|
|
3514
|
-
clearTimeout(pending.timer);
|
|
3515
|
-
pending.reject(new Error("Relay connection lost"));
|
|
3516
|
-
this.pendingRequests.delete(id);
|
|
3517
|
-
}
|
|
3518
|
-
});
|
|
3519
|
-
ws.on("error", (err) => {
|
|
3520
|
-
if (!this.ws) reject(err);
|
|
3521
|
-
this.emit("server:error", err);
|
|
3522
|
-
});
|
|
3523
|
-
});
|
|
3524
|
-
}
|
|
3525
|
-
handleMessage(msg) {
|
|
3526
|
-
if (msg.type === "controller:welcome") {
|
|
3527
|
-
const p = msg.payload;
|
|
3528
|
-
this.studioConnected = p.studioConnected;
|
|
3529
|
-
if (this.studioConnected) {
|
|
3530
|
-
this.emit("studio:connected", {});
|
|
3531
|
-
}
|
|
3532
|
-
return;
|
|
3533
|
-
}
|
|
3534
|
-
if (msg.type === "studio:connected") {
|
|
3535
|
-
this.studioConnected = true;
|
|
3536
|
-
const payload = msg.payload;
|
|
3537
|
-
this.connectionInfo = {
|
|
3538
|
-
connectedAt: Date.now(),
|
|
3539
|
-
studioVersion: payload.studioVersion,
|
|
3540
|
-
placeId: payload.placeId,
|
|
3541
|
-
placeName: payload.placeName
|
|
3542
|
-
};
|
|
3543
|
-
this.emit("studio:connected", payload);
|
|
3544
|
-
return;
|
|
3545
|
-
}
|
|
3546
|
-
if (msg.type === "studio:disconnected") {
|
|
3547
|
-
this.studioConnected = false;
|
|
3548
|
-
this.connectionInfo = null;
|
|
3549
|
-
this.emit("studio:disconnected");
|
|
3550
|
-
return;
|
|
3551
|
-
}
|
|
3552
|
-
if (msg.type === StudioMsg.RESPONSE) {
|
|
3553
|
-
const pending = this.pendingRequests.get(msg.id);
|
|
3554
|
-
if (pending) {
|
|
3555
|
-
clearTimeout(pending.timer);
|
|
3556
|
-
pending.resolve(msg);
|
|
3557
|
-
this.pendingRequests.delete(msg.id);
|
|
3558
|
-
}
|
|
3559
|
-
return;
|
|
3560
|
-
}
|
|
3561
|
-
this.emit(msg.type, msg.payload);
|
|
3562
|
-
this.emit("message", msg);
|
|
3563
|
-
}
|
|
3564
|
-
sendRequest(type, payload, timeoutMs = 1e4) {
|
|
3565
|
-
return new Promise((resolve, reject) => {
|
|
3566
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
3567
|
-
reject(new Error("Not connected to Dominus server"));
|
|
3568
|
-
return;
|
|
3569
|
-
}
|
|
3570
|
-
if (!this.studioConnected) {
|
|
3571
|
-
reject(new Error("Studio not connected"));
|
|
3572
|
-
return;
|
|
3573
|
-
}
|
|
3574
|
-
const msg = createMessage(type, payload);
|
|
3575
|
-
const timer = setTimeout(() => {
|
|
3576
|
-
this.pendingRequests.delete(msg.id);
|
|
3577
|
-
reject(new Error(`Request timed out: ${type}`));
|
|
3578
|
-
}, timeoutMs);
|
|
3579
|
-
this.pendingRequests.set(msg.id, {
|
|
3580
|
-
resolve,
|
|
3581
|
-
reject,
|
|
3582
|
-
timer
|
|
3583
|
-
});
|
|
3584
|
-
this.ws.send(serializeMessage(msg));
|
|
3585
|
-
});
|
|
3586
|
-
}
|
|
3587
|
-
sendNotification(type, payload) {
|
|
3588
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
3589
|
-
const msg = createMessage(type, payload);
|
|
3590
|
-
this.ws.send(serializeMessage(msg));
|
|
3591
|
-
}
|
|
3592
|
-
isConnected() {
|
|
3593
|
-
return this.studioConnected;
|
|
3594
|
-
}
|
|
3595
|
-
getConnectionInfo() {
|
|
3596
|
-
if (!this.connectionInfo) return null;
|
|
3597
|
-
return { ...this.connectionInfo, ws: this.ws };
|
|
3598
|
-
}
|
|
3599
|
-
getPort() {
|
|
3600
|
-
return this.port;
|
|
3601
|
-
}
|
|
3602
|
-
stop() {
|
|
3603
|
-
return new Promise((resolve) => {
|
|
3604
|
-
for (const [, pending] of this.pendingRequests) {
|
|
3605
|
-
clearTimeout(pending.timer);
|
|
3606
|
-
pending.reject(new Error("Client shutting down"));
|
|
3607
|
-
}
|
|
3608
|
-
this.pendingRequests.clear();
|
|
3609
|
-
if (this.ws) {
|
|
3610
|
-
this.ws.close();
|
|
3611
|
-
this.ws = null;
|
|
3612
|
-
}
|
|
3613
|
-
resolve();
|
|
3614
|
-
});
|
|
3615
|
-
}
|
|
3616
|
-
};
|
|
3617
|
-
async function isPortInUse(port) {
|
|
3618
|
-
return new Promise((resolve) => {
|
|
3619
|
-
const ws = new WebSocket(`ws://localhost:${port}`);
|
|
3620
|
-
const timer = setTimeout(() => {
|
|
3621
|
-
ws.close();
|
|
3622
|
-
resolve(false);
|
|
3623
|
-
}, 1e3);
|
|
3624
|
-
ws.on("open", () => {
|
|
3625
|
-
clearTimeout(timer);
|
|
3626
|
-
ws.close();
|
|
3627
|
-
resolve(true);
|
|
3628
|
-
});
|
|
3629
|
-
ws.on("error", () => {
|
|
3630
|
-
clearTimeout(timer);
|
|
3631
|
-
resolve(false);
|
|
3632
|
-
});
|
|
3633
|
-
});
|
|
3634
|
-
}
|
|
3647
|
+
init_ws_client();
|
|
3635
3648
|
|
|
3636
3649
|
// src/ai/provider.ts
|
|
3637
3650
|
init_config();
|
|
@@ -4293,7 +4306,7 @@ function createDefaultRegistry() {
|
|
|
4293
4306
|
// src/index.tsx
|
|
4294
4307
|
init_store();
|
|
4295
4308
|
init_config();
|
|
4296
|
-
var VERSION = "0.
|
|
4309
|
+
var VERSION = "0.2.0";
|
|
4297
4310
|
var program = new Command();
|
|
4298
4311
|
program.name("dominus").description("AI-powered autonomous agent for Roblox Studio development").version(VERSION);
|
|
4299
4312
|
program.command("start", { isDefault: true }).description("Launch the Dominus TUI agent").option("-p, --port <port>", "WebSocket server port", "18088").action(async (opts) => {
|