dominus-cli 0.6.0 → 2.1.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/CHANGELOG.md +175 -0
- package/README.md +164 -248
- package/dist/install-plugin.js +30 -0
- package/dist/install-plugin.js.map +1 -0
- package/dist/mcp.js +1576 -1346
- package/dist/mcp.js.map +1 -1
- package/docs/DOMINUS_2_PLAN.md +75 -0
- package/package.json +24 -25
- package/plugin/Dominus.rbxm +0 -0
- package/plugin/src/CommandRouter.lua +94 -0
- package/plugin/src/Explorer.lua +530 -373
- package/plugin/src/InstanceRegistry.lua +122 -0
- package/plugin/src/Mutation.lua +135 -0
- package/plugin/src/Properties.lua +859 -725
- package/plugin/src/Protocol.lua +30 -30
- package/plugin/src/Reflection.lua +91 -59
- package/plugin/src/TestRunner.lua +69 -141
- package/plugin/src/UIBuilder.lua +857 -727
- package/plugin/src/ValueCodec.lua +230 -0
- package/plugin/src/Watcher.lua +85 -85
- package/plugin/src/WsClient.lua +153 -81
- package/plugin/src/init.server.lua +175 -362
- package/dist/index.js +0 -5796
- package/dist/index.js.map +0 -1
- package/plugin/src/Executor.lua +0 -117
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
local HttpService = game:GetService("HttpService")
|
|
2
|
+
|
|
3
|
+
local InstanceRegistry = {}
|
|
4
|
+
|
|
5
|
+
local instanceToId = setmetatable({}, { __mode = "k" })
|
|
6
|
+
local idToInstance = {}
|
|
7
|
+
|
|
8
|
+
local function register(instance)
|
|
9
|
+
local existing = instanceToId[instance]
|
|
10
|
+
if existing then
|
|
11
|
+
return existing
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
local id = HttpService:GenerateGUID(false)
|
|
15
|
+
instanceToId[instance] = id
|
|
16
|
+
idToInstance[id] = instance
|
|
17
|
+
instance.Destroying:Connect(function()
|
|
18
|
+
if idToInstance[id] == instance then
|
|
19
|
+
idToInstance[id] = nil
|
|
20
|
+
end
|
|
21
|
+
instanceToId[instance] = nil
|
|
22
|
+
end)
|
|
23
|
+
return id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
function InstanceRegistry.getPathSegments(instance)
|
|
27
|
+
local segments = {}
|
|
28
|
+
local current = instance
|
|
29
|
+
while current and current ~= game do
|
|
30
|
+
table.insert(segments, 1, current.Name)
|
|
31
|
+
current = current.Parent
|
|
32
|
+
end
|
|
33
|
+
return segments
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
function InstanceRegistry.getDisplayPath(instance)
|
|
37
|
+
local path = "game"
|
|
38
|
+
for _, segment in InstanceRegistry.getPathSegments(instance) do
|
|
39
|
+
local escaped = segment:gsub("\\", "\\\\"):gsub('"', '\\"')
|
|
40
|
+
path ..= '["' .. escaped .. '"]'
|
|
41
|
+
end
|
|
42
|
+
return path
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
function InstanceRegistry.toRef(instance)
|
|
46
|
+
return {
|
|
47
|
+
instanceId = register(instance),
|
|
48
|
+
pathSegments = InstanceRegistry.getPathSegments(instance),
|
|
49
|
+
displayPath = InstanceRegistry.getDisplayPath(instance),
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
local function resolveSegments(segments)
|
|
54
|
+
if type(segments) ~= "table" or #segments == 0 then
|
|
55
|
+
return nil, "pathSegments must contain at least one name"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
local current = game
|
|
59
|
+
for index, segment in segments do
|
|
60
|
+
if type(segment) ~= "string" then
|
|
61
|
+
return nil, "pathSegments[" .. index .. "] must be a string"
|
|
62
|
+
end
|
|
63
|
+
local match = nil
|
|
64
|
+
local count = 0
|
|
65
|
+
for _, child in current:GetChildren() do
|
|
66
|
+
if child.Name == segment then
|
|
67
|
+
match = child
|
|
68
|
+
count += 1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
if count == 0 and current == game then
|
|
72
|
+
local ok, service = pcall(function()
|
|
73
|
+
return game:GetService(segment)
|
|
74
|
+
end)
|
|
75
|
+
if ok then
|
|
76
|
+
match = service
|
|
77
|
+
count = 1
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
if count == 0 then
|
|
81
|
+
return nil, "Instance path does not exist at segment: " .. segment
|
|
82
|
+
end
|
|
83
|
+
if count > 1 then
|
|
84
|
+
return nil, "Instance path is ambiguous at duplicate sibling name: " .. segment
|
|
85
|
+
end
|
|
86
|
+
current = match
|
|
87
|
+
end
|
|
88
|
+
return current
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
function InstanceRegistry.resolve(target)
|
|
92
|
+
if typeof(target) == "Instance" then
|
|
93
|
+
return target
|
|
94
|
+
end
|
|
95
|
+
if type(target) ~= "table" then
|
|
96
|
+
return nil, "Target must be an instance reference"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if type(target.instanceId) == "string" then
|
|
100
|
+
local instance = idToInstance[target.instanceId]
|
|
101
|
+
if instance then
|
|
102
|
+
return instance
|
|
103
|
+
end
|
|
104
|
+
return nil, "Instance reference is stale: " .. target.instanceId
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if target.pathSegments then
|
|
108
|
+
local instance, err = resolveSegments(target.pathSegments)
|
|
109
|
+
if instance then
|
|
110
|
+
register(instance)
|
|
111
|
+
end
|
|
112
|
+
return instance, err
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
return nil, "Target requires instanceId or pathSegments"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
function InstanceRegistry.register(instance)
|
|
119
|
+
return register(instance)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
return InstanceRegistry
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
2
|
+
|
|
3
|
+
local InstanceRegistry = require(script.Parent.InstanceRegistry)
|
|
4
|
+
local ValueCodec = require(script.Parent.ValueCodec)
|
|
5
|
+
|
|
6
|
+
local Mutation = {}
|
|
7
|
+
local MAX_OPERATIONS = 100
|
|
8
|
+
local MAX_PROPERTIES = 100
|
|
9
|
+
|
|
10
|
+
local function resolve(target, label)
|
|
11
|
+
local instance, err = InstanceRegistry.resolve(target)
|
|
12
|
+
if not instance then
|
|
13
|
+
error(label .. ": " .. tostring(err))
|
|
14
|
+
end
|
|
15
|
+
return instance
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
local function setProperties(instance, properties)
|
|
19
|
+
assert(type(properties) == "table", "properties must be an object")
|
|
20
|
+
local count = 0
|
|
21
|
+
local applied = {}
|
|
22
|
+
for propertyName, value in properties do
|
|
23
|
+
count += 1
|
|
24
|
+
if count > MAX_PROPERTIES then
|
|
25
|
+
error("Too many properties in one operation")
|
|
26
|
+
end
|
|
27
|
+
applied[propertyName] = ValueCodec.setProperty(instance, propertyName, value)
|
|
28
|
+
end
|
|
29
|
+
return applied
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
function Mutation.apply(spec)
|
|
33
|
+
local operations = spec.operations
|
|
34
|
+
if type(operations) ~= "table" or #operations == 0 then
|
|
35
|
+
return { success = false, error = "operations must contain at least one mutation" }
|
|
36
|
+
end
|
|
37
|
+
if #operations > MAX_OPERATIONS then
|
|
38
|
+
return { success = false, error = "A mutation batch is limited to " .. MAX_OPERATIONS .. " operations" }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
local recording = ChangeHistoryService:TryBeginRecording("Dominus 2: Apply mutation batch")
|
|
42
|
+
if not recording then
|
|
43
|
+
return { success = false, error = "Another plugin recording is already active" }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
local results = {}
|
|
47
|
+
local ok, err = pcall(function()
|
|
48
|
+
for index, operation in operations do
|
|
49
|
+
assert(type(operation) == "table", "Operation " .. index .. " must be an object")
|
|
50
|
+
if operation.op == "setProperties" then
|
|
51
|
+
local target = resolve(operation.target, "Operation " .. index .. " target")
|
|
52
|
+
table.insert(results, {
|
|
53
|
+
op = operation.op,
|
|
54
|
+
target = InstanceRegistry.toRef(target),
|
|
55
|
+
properties = setProperties(target, operation.properties),
|
|
56
|
+
})
|
|
57
|
+
elseif operation.op == "create" then
|
|
58
|
+
local parent = resolve(operation.parent, "Operation " .. index .. " parent")
|
|
59
|
+
assert(type(operation.className) == "string", "create.className must be a string")
|
|
60
|
+
assert(type(operation.name) == "string", "create.name must be a string")
|
|
61
|
+
local instance = Instance.new(operation.className)
|
|
62
|
+
instance.Name = operation.name
|
|
63
|
+
if operation.properties then
|
|
64
|
+
setProperties(instance, operation.properties)
|
|
65
|
+
end
|
|
66
|
+
instance.Parent = parent
|
|
67
|
+
table.insert(results, { op = operation.op, created = InstanceRegistry.toRef(instance) })
|
|
68
|
+
elseif operation.op == "move" then
|
|
69
|
+
local target = resolve(operation.target, "Operation " .. index .. " target")
|
|
70
|
+
local parent = resolve(operation.parent, "Operation " .. index .. " parent")
|
|
71
|
+
assert(target ~= game and target.Parent ~= game, "Services cannot be moved")
|
|
72
|
+
target.Parent = parent
|
|
73
|
+
table.insert(results, { op = operation.op, target = InstanceRegistry.toRef(target) })
|
|
74
|
+
else
|
|
75
|
+
error("Unsupported mutation operation: " .. tostring(operation.op))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end)
|
|
79
|
+
|
|
80
|
+
if not ok then
|
|
81
|
+
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Cancel)
|
|
82
|
+
return { success = false, error = tostring(err), rolledBack = true }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
|
|
86
|
+
return { success = true, operationCount = #results, results = results }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
function Mutation.delete(spec)
|
|
90
|
+
if spec.confirm ~= true then
|
|
91
|
+
return { success = false, error = "confirm must be true" }
|
|
92
|
+
end
|
|
93
|
+
if type(spec.targets) ~= "table" or #spec.targets == 0 then
|
|
94
|
+
return { success = false, error = "targets must contain at least one instance reference" }
|
|
95
|
+
end
|
|
96
|
+
if #spec.targets > 50 then
|
|
97
|
+
return { success = false, error = "At most 50 instances can be deleted at once" }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
local targets = {}
|
|
101
|
+
local seen = {}
|
|
102
|
+
for index, targetRef in spec.targets do
|
|
103
|
+
local target = resolve(targetRef, "Target " .. index)
|
|
104
|
+
assert(target ~= game and target.Parent ~= game, "Services cannot be deleted")
|
|
105
|
+
local id = InstanceRegistry.register(target)
|
|
106
|
+
if not seen[id] then
|
|
107
|
+
seen[id] = true
|
|
108
|
+
table.insert(targets, target)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
local recording = ChangeHistoryService:TryBeginRecording("Dominus 2: Delete instances")
|
|
113
|
+
if not recording then
|
|
114
|
+
return { success = false, error = "Another plugin recording is already active" }
|
|
115
|
+
end
|
|
116
|
+
local deleted = {}
|
|
117
|
+
local ok, err = pcall(function()
|
|
118
|
+
for _, target in targets do
|
|
119
|
+
table.insert(deleted, {
|
|
120
|
+
name = target.Name,
|
|
121
|
+
className = target.ClassName,
|
|
122
|
+
pathSegments = InstanceRegistry.getPathSegments(target),
|
|
123
|
+
})
|
|
124
|
+
target:Destroy()
|
|
125
|
+
end
|
|
126
|
+
end)
|
|
127
|
+
if not ok then
|
|
128
|
+
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Cancel)
|
|
129
|
+
return { success = false, error = tostring(err), rolledBack = true }
|
|
130
|
+
end
|
|
131
|
+
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
|
|
132
|
+
return { success = true, deleted = deleted }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
return Mutation
|