dominus-cli 0.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.
@@ -0,0 +1,424 @@
1
+ --[[
2
+ Properties module: read/write instance properties
3
+ Uses ReflectionService to determine expected types for smart coercion.
4
+ ]]
5
+
6
+ local Explorer = require(script.Parent.Explorer)
7
+ local ReflectionService = game:GetService("ReflectionService")
8
+
9
+ local Properties = {}
10
+
11
+ local IGNORED_PROPERTIES = {
12
+ Parent = true,
13
+ ClassName = true,
14
+ Archivable = true,
15
+ }
16
+
17
+ -- Cache property type maps per class to avoid repeated reflection calls
18
+ local typeCache = {}
19
+
20
+ local function getPropertyTypeMap(className)
21
+ if typeCache[className] then return typeCache[className] end
22
+
23
+ local typeMap = {}
24
+ local ok, props = pcall(function()
25
+ return ReflectionService:GetPropertiesOfClass(className)
26
+ end)
27
+ if ok and props then
28
+ for _, prop in props do
29
+ if prop.ValueType then
30
+ typeMap[prop.Name] = tostring(prop.ValueType)
31
+ end
32
+ end
33
+ end
34
+ typeCache[className] = typeMap
35
+ return typeMap
36
+ end
37
+
38
+ function Properties.get(path)
39
+ local instance = Explorer.resolveInstance(path)
40
+ if not instance then
41
+ return { success = false, error = "Instance not found: " .. path }
42
+ end
43
+
44
+ local properties = {}
45
+
46
+ local ok, apiData = pcall(function()
47
+ local info = {}
48
+ local propData = ReflectionService:GetPropertiesOfClass(instance.ClassName)
49
+ for _, prop in propData do
50
+ if not IGNORED_PROPERTIES[prop.Name] then
51
+ local valueOk, value = pcall(function()
52
+ return instance[prop.Name]
53
+ end)
54
+ if valueOk then
55
+ table.insert(info, {
56
+ name = prop.Name,
57
+ value = tostring(value),
58
+ type = prop.ValueType and tostring(prop.ValueType) or typeof(value),
59
+ category = prop.Category or "Other",
60
+ })
61
+ end
62
+ end
63
+ end
64
+ return info
65
+ end)
66
+
67
+ if ok then
68
+ properties = apiData
69
+ else
70
+ local commonProps = {"Name", "Position", "Size", "Color", "Material", "Transparency", "Anchored", "CanCollide"}
71
+ for _, propName in commonProps do
72
+ local valOk, val = pcall(function()
73
+ return instance[propName]
74
+ end)
75
+ if valOk and val ~= nil then
76
+ table.insert(properties, {
77
+ name = propName,
78
+ value = tostring(val),
79
+ type = typeof(val),
80
+ })
81
+ end
82
+ end
83
+ end
84
+
85
+ return { properties = properties }
86
+ end
87
+
88
+ --[[
89
+ Smart type coercion: converts JSON-friendly values into Roblox types.
90
+ Uses the expected property type from ReflectionService when available.
91
+ ]]
92
+ local function coerceValue(instance, key, value)
93
+ local expectedType = nil
94
+ local typeMap = getPropertyTypeMap(instance.ClassName)
95
+ expectedType = typeMap[key]
96
+
97
+ -- If we don't know the expected type, try reading the current value
98
+ if not expectedType then
99
+ local curOk, curVal = pcall(function() return instance[key] end)
100
+ if curOk and curVal ~= nil then
101
+ expectedType = typeof(curVal)
102
+ end
103
+ end
104
+
105
+ -- ═══════════════════════════════════════════
106
+ -- UDim2 (GUI Size, Position, etc.)
107
+ -- ═══════════════════════════════════════════
108
+ if expectedType == "UDim2" then
109
+ if type(value) == "table" then
110
+ -- {xScale, xOffset, yScale, yOffset} or named keys
111
+ if value.XScale or value.xScale or value.X then
112
+ return UDim2.new(
113
+ value.XScale or value.xScale or value.X and value.X.Scale or 0,
114
+ value.XOffset or value.xOffset or value.X and value.X.Offset or 0,
115
+ value.YScale or value.yScale or value.Y and value.Y.Scale or 0,
116
+ value.YOffset or value.yOffset or value.Y and value.Y.Offset or 0
117
+ )
118
+ end
119
+ -- {X = {Scale, Offset}, Y = {Scale, Offset}}
120
+ if value.X and type(value.X) == "table" then
121
+ return UDim2.new(
122
+ value.X.Scale or value.X[1] or 0,
123
+ value.X.Offset or value.X[2] or 0,
124
+ value.Y and (value.Y.Scale or value.Y[1]) or 0,
125
+ value.Y and (value.Y.Offset or value.Y[2]) or 0
126
+ )
127
+ end
128
+ -- Array: {xScale, xOffset, yScale, yOffset}
129
+ if value[1] ~= nil then
130
+ return UDim2.new(value[1] or 0, value[2] or 0, value[3] or 0, value[4] or 0)
131
+ end
132
+ -- fromScale shorthand: {scale_x, scale_y} when only 2 elements
133
+ return UDim2.new(0, 0, 0, 0)
134
+ end
135
+ if type(value) == "string" then
136
+ -- "0.5, 0, 0.5, 0" format
137
+ local a, b, c, d = value:match("([%d%.%-]+)%s*,%s*([%d%.%-]+)%s*,%s*([%d%.%-]+)%s*,%s*([%d%.%-]+)")
138
+ if a then return UDim2.new(tonumber(a), tonumber(b), tonumber(c), tonumber(d)) end
139
+ end
140
+ end
141
+
142
+ -- ═══════════════════════════════════════════
143
+ -- UDim
144
+ -- ═══════════════════════════════════════════
145
+ if expectedType == "UDim" then
146
+ if type(value) == "table" then
147
+ return UDim.new(value.Scale or value[1] or 0, value.Offset or value[2] or 0)
148
+ end
149
+ if type(value) == "number" then
150
+ return UDim.new(value, 0)
151
+ end
152
+ end
153
+
154
+ -- ═══════════════════════════════════════════
155
+ -- Vector3
156
+ -- ═══════════════════════════════════════════
157
+ if expectedType == "Vector3" then
158
+ if type(value) == "table" then
159
+ return Vector3.new(
160
+ value.X or value.x or value[1] or 0,
161
+ value.Y or value.y or value[2] or 0,
162
+ value.Z or value.z or value[3] or 0
163
+ )
164
+ end
165
+ end
166
+
167
+ -- ═══════════════════════════════════════════
168
+ -- Vector2
169
+ -- ═══════════════════════════════════════════
170
+ if expectedType == "Vector2" then
171
+ if type(value) == "table" then
172
+ return Vector2.new(
173
+ value.X or value.x or value[1] or 0,
174
+ value.Y or value.y or value[2] or 0
175
+ )
176
+ end
177
+ end
178
+
179
+ -- ═══════════════════════════════════════════
180
+ -- Color3
181
+ -- ═══════════════════════════════════════════
182
+ if expectedType == "Color3" then
183
+ if type(value) == "string" then
184
+ if value:sub(1, 1) == "#" then
185
+ return Color3.fromHex(value)
186
+ end
187
+ if value:match("^rgb") then
188
+ local r, g, b = value:match("(%d+)%D+(%d+)%D+(%d+)")
189
+ if r then return Color3.fromRGB(tonumber(r), tonumber(g), tonumber(b)) end
190
+ end
191
+ local ok, bc = pcall(BrickColor.new, value)
192
+ if ok then return bc.Color end
193
+ end
194
+ if type(value) == "table" then
195
+ -- {R, G, B} 0-1 or {r, g, b}
196
+ if value.R or value.r then
197
+ return Color3.new(value.R or value.r or 0, value.G or value.g or 0, value.B or value.b or 0)
198
+ end
199
+ -- {red, green, blue} 0-255
200
+ if value.red or value.Red then
201
+ return Color3.fromRGB(value.red or value.Red or 0, value.green or value.Green or 0, value.blue or value.Blue or 0)
202
+ end
203
+ -- Array [r, g, b]
204
+ if value[1] then
205
+ if value[1] > 1 or value[2] > 1 or value[3] > 1 then
206
+ return Color3.fromRGB(value[1], value[2], value[3])
207
+ end
208
+ return Color3.new(value[1], value[2], value[3])
209
+ end
210
+ end
211
+ end
212
+
213
+ -- ═══════════════════════════════════════════
214
+ -- BrickColor
215
+ -- ═══════════════════════════════════════════
216
+ if expectedType == "BrickColor" then
217
+ if type(value) == "string" then
218
+ return BrickColor.new(value)
219
+ end
220
+ if type(value) == "number" then
221
+ return BrickColor.new(value)
222
+ end
223
+ end
224
+
225
+ -- ═══════════════════════════════════════════
226
+ -- CFrame
227
+ -- ═══════════════════════════════════════════
228
+ if expectedType == "CFrame" then
229
+ if type(value) == "table" then
230
+ if value.Position or value.position then
231
+ local pos = value.Position or value.position
232
+ local cf = CFrame.new(pos.x or pos.X or pos[1] or 0, pos.y or pos.Y or pos[2] or 0, pos.z or pos.Z or pos[3] or 0)
233
+ if value.Rotation or value.rotation then
234
+ local rot = value.Rotation or value.rotation
235
+ cf = cf * CFrame.Angles(
236
+ math.rad(rot.x or rot.X or rot[1] or 0),
237
+ math.rad(rot.y or rot.Y or rot[2] or 0),
238
+ math.rad(rot.z or rot.Z or rot[3] or 0)
239
+ )
240
+ end
241
+ return cf
242
+ end
243
+ -- Simple {x, y, z}
244
+ if value[1] then
245
+ return CFrame.new(value[1], value[2] or 0, value[3] or 0)
246
+ end
247
+ end
248
+ end
249
+
250
+ -- ═══════════════════════════════════════════
251
+ -- Rect (for GUI ImageRectOffset, etc.)
252
+ -- ═══════════════════════════════════════════
253
+ if expectedType == "Rect" then
254
+ if type(value) == "table" then
255
+ return Rect.new(value[1] or value.Min and value.Min[1] or 0, value[2] or value.Min and value.Min[2] or 0,
256
+ value[3] or value.Max and value.Max[1] or 0, value[4] or value.Max and value.Max[2] or 0)
257
+ end
258
+ end
259
+
260
+ -- ═══════════════════════════════════════════
261
+ -- NumberRange
262
+ -- ═══════════════════════════════════════════
263
+ if expectedType == "NumberRange" then
264
+ if type(value) == "table" then
265
+ return NumberRange.new(value.Min or value[1] or 0, value.Max or value[2] or 1)
266
+ end
267
+ if type(value) == "number" then
268
+ return NumberRange.new(value)
269
+ end
270
+ end
271
+
272
+ -- ═══════════════════════════════════════════
273
+ -- NumberSequence (for transparency gradients, etc.)
274
+ -- ═══════════════════════════════════════════
275
+ if expectedType == "NumberSequence" then
276
+ if type(value) == "table" then
277
+ if value[1] and type(value[1]) == "table" then
278
+ local keypoints = {}
279
+ for _, kp in value do
280
+ table.insert(keypoints, NumberSequenceKeypoint.new(kp.Time or kp[1] or 0, kp.Value or kp[2] or 0, kp.Envelope or kp[3] or 0))
281
+ end
282
+ return NumberSequence.new(keypoints)
283
+ end
284
+ if #value == 2 and type(value[1]) == "number" then
285
+ return NumberSequence.new(value[1], value[2])
286
+ end
287
+ end
288
+ if type(value) == "number" then
289
+ return NumberSequence.new(value)
290
+ end
291
+ end
292
+
293
+ -- ═══════════════════════════════════════════
294
+ -- ColorSequence
295
+ -- ═══════════════════════════════════════════
296
+ if expectedType == "ColorSequence" then
297
+ if type(value) == "table" then
298
+ if value[1] and type(value[1]) == "table" then
299
+ local keypoints = {}
300
+ for _, kp in value do
301
+ local color = Color3.new(1, 1, 1)
302
+ if kp.Color then
303
+ if type(kp.Color) == "string" and kp.Color:sub(1,1) == "#" then
304
+ color = Color3.fromHex(kp.Color)
305
+ elseif type(kp.Color) == "table" then
306
+ color = Color3.new(kp.Color[1] or 0, kp.Color[2] or 0, kp.Color[3] or 0)
307
+ end
308
+ end
309
+ table.insert(keypoints, ColorSequenceKeypoint.new(kp.Time or kp[1] or 0, color))
310
+ end
311
+ return ColorSequence.new(keypoints)
312
+ end
313
+ end
314
+ end
315
+
316
+ -- ═══════════════════════════════════════════
317
+ -- Font
318
+ -- ═══════════════════════════════════════════
319
+ if expectedType == "Font" then
320
+ if type(value) == "table" then
321
+ local family = value.Family or value.family or "rbxasset://fonts/families/SourceSansPro.json"
322
+ local weight = Enum.FontWeight.Regular
323
+ local style = Enum.FontStyle.Normal
324
+ if value.Weight or value.weight then
325
+ pcall(function() weight = Enum.FontWeight[value.Weight or value.weight] end)
326
+ end
327
+ if value.Style or value.style then
328
+ pcall(function() style = Enum.FontStyle[value.Style or value.style] end)
329
+ end
330
+ return Font.new(family, weight, style)
331
+ end
332
+ if type(value) == "string" then
333
+ return Font.new(value)
334
+ end
335
+ end
336
+
337
+ -- ═══════════════════════════════════════════
338
+ -- EnumItem (any enum property)
339
+ -- ═══════════════════════════════════════════
340
+ if type(value) == "string" then
341
+ local currentVal = nil
342
+ pcall(function() currentVal = instance[key] end)
343
+ if typeof(currentVal) == "EnumItem" then
344
+ local enumType = tostring(currentVal.EnumType)
345
+ local enumOk, enumVal = pcall(function()
346
+ return Enum[enumType][value]
347
+ end)
348
+ if enumOk then return enumVal end
349
+ end
350
+ end
351
+
352
+ -- ═══════════════════════════════════════════
353
+ -- Fallback: type-agnostic guesses (no expectedType known)
354
+ -- ═══════════════════════════════════════════
355
+ if type(value) == "table" and not expectedType then
356
+ -- UDim2 guess: has XScale/YScale keys
357
+ if value.XScale or value.xScale then
358
+ return UDim2.new(
359
+ value.XScale or value.xScale or 0, value.XOffset or value.xOffset or 0,
360
+ value.YScale or value.yScale or 0, value.YOffset or value.yOffset or 0
361
+ )
362
+ end
363
+ -- Vector3 guess: 3 number elements or X/Y/Z keys
364
+ if (value.X or value.x) and (value.Y or value.y) and (value.Z or value.z) then
365
+ return Vector3.new(value.X or value.x, value.Y or value.y, value.Z or value.z)
366
+ end
367
+ if value[1] and value[2] and value[3] and not value[4] then
368
+ return Vector3.new(value[1], value[2], value[3])
369
+ end
370
+ -- Color3 guess: R/G/B keys
371
+ if value.R or value.r then
372
+ return Color3.new(value.R or value.r or 0, value.G or value.g or 0, value.B or value.b or 0)
373
+ end
374
+ -- Vector2 guess: 2 number elements
375
+ if value[1] and value[2] and not value[3] then
376
+ return Vector2.new(value[1], value[2])
377
+ end
378
+ end
379
+
380
+ -- String-based color on color properties (fallback)
381
+ if type(value) == "string" and (key == "Color" or key == "Color3" or key:match("Color$")) then
382
+ if value:sub(1, 1) == "#" then
383
+ return Color3.fromHex(value)
384
+ end
385
+ local ok, bc = pcall(BrickColor.new, value)
386
+ if ok then return bc.Color end
387
+ end
388
+
389
+ if type(value) == "string" and key == "BrickColor" then
390
+ return BrickColor.new(value)
391
+ end
392
+
393
+ return value
394
+ end
395
+
396
+ function Properties.set(path, propertiesToSet)
397
+ local instance = Explorer.resolveInstance(path)
398
+ if not instance then
399
+ return { success = false, error = "Instance not found: " .. path }
400
+ end
401
+
402
+ local errors = {}
403
+ local set = {}
404
+
405
+ for key, value in propertiesToSet do
406
+ local coerced = coerceValue(instance, key, value)
407
+ local ok, err = pcall(function()
408
+ instance[key] = coerced
409
+ end)
410
+ if ok then
411
+ table.insert(set, key)
412
+ else
413
+ table.insert(errors, key .. ": " .. tostring(err))
414
+ end
415
+ end
416
+
417
+ if #errors > 0 then
418
+ return { success = false, error = table.concat(errors, "; "), set = set }
419
+ end
420
+
421
+ return { success = true, set = set }
422
+ end
423
+
424
+ return Properties
@@ -0,0 +1,30 @@
1
+ --[[
2
+ Message protocol: JSON encoding/decoding with unique ID generation
3
+ ]]
4
+
5
+ local HttpService = game:GetService("HttpService")
6
+
7
+ local Protocol = {}
8
+
9
+ function Protocol.encode(data)
10
+ return HttpService:JSONEncode(data)
11
+ end
12
+
13
+ function Protocol.decode(json)
14
+ return HttpService:JSONDecode(json)
15
+ end
16
+
17
+ function Protocol.generateId()
18
+ return HttpService:GenerateGUID(false)
19
+ end
20
+
21
+ function Protocol.createMessage(msgType, payload)
22
+ return {
23
+ id = Protocol.generateId(),
24
+ type = msgType,
25
+ payload = payload,
26
+ ts = os.clock(),
27
+ }
28
+ end
29
+
30
+ return Protocol
@@ -0,0 +1,137 @@
1
+ --[[
2
+ Reflection module: query Roblox class/property/method/event information
3
+ Uses the new ReflectionService API (2025+):
4
+ - GetClass(className, filter?)
5
+ - GetClasses(filter?)
6
+ - GetPropertiesOfClass(className, filter?)
7
+ - GetMethodsOfClass(className, filter?)
8
+ - GetEventsOfClass(className, filter?)
9
+ ]]
10
+
11
+ local ReflectionService = game:GetService("ReflectionService")
12
+ local Reflection = {}
13
+
14
+ local function safeCall(fn, ...)
15
+ local ok, result = pcall(fn, ...)
16
+ if ok then return result end
17
+ return nil, result
18
+ end
19
+
20
+ function Reflection.getClassInfo(className)
21
+ local classData, classErr = safeCall(function()
22
+ return ReflectionService:GetClass(className)
23
+ end)
24
+
25
+ local properties = {}
26
+ local propData = safeCall(function()
27
+ return ReflectionService:GetPropertiesOfClass(className)
28
+ end)
29
+ if propData then
30
+ for _, prop in propData do
31
+ table.insert(properties, {
32
+ name = prop.Name,
33
+ type = prop.ValueType and tostring(prop.ValueType) or "unknown",
34
+ category = prop.Category or "Other",
35
+ serialized = prop.Serialized,
36
+ owner = prop.Owner,
37
+ })
38
+ end
39
+ end
40
+
41
+ local methods = {}
42
+ local methodData = safeCall(function()
43
+ return ReflectionService:GetMethodsOfClass(className)
44
+ end)
45
+ if methodData then
46
+ for _, method in methodData do
47
+ local params = {}
48
+ if method.Parameters then
49
+ for _, param in method.Parameters do
50
+ table.insert(params, {
51
+ name = param.Name,
52
+ type = param.Type and tostring(param.Type) or "unknown",
53
+ })
54
+ end
55
+ end
56
+ table.insert(methods, {
57
+ name = method.Name,
58
+ parameters = params,
59
+ owner = method.Owner,
60
+ })
61
+ end
62
+ end
63
+
64
+ local events = {}
65
+ local eventData = safeCall(function()
66
+ return ReflectionService:GetEventsOfClass(className)
67
+ end)
68
+ if eventData then
69
+ for _, event in eventData do
70
+ local params = {}
71
+ if event.Parameters then
72
+ for _, param in event.Parameters do
73
+ table.insert(params, {
74
+ name = param.Name,
75
+ type = param.Type and tostring(param.Type) or "unknown",
76
+ })
77
+ end
78
+ end
79
+ table.insert(events, {
80
+ name = event.Name,
81
+ parameters = params,
82
+ owner = event.Owner,
83
+ })
84
+ end
85
+ end
86
+
87
+ local result = {
88
+ className = className,
89
+ properties = properties,
90
+ methods = methods,
91
+ events = events,
92
+ }
93
+
94
+ if classData then
95
+ result.superclass = classData.Superclass
96
+ result.creatable = classData.Creatable
97
+ result.serialized = classData.Serialized
98
+ end
99
+
100
+ if classErr and #properties == 0 then
101
+ result.error = "Could not reflect class: " .. tostring(classErr)
102
+ end
103
+
104
+ return result
105
+ end
106
+
107
+ function Reflection.getPropertyTypes(className)
108
+ local propData = safeCall(function()
109
+ return ReflectionService:GetPropertiesOfClass(className)
110
+ end)
111
+ if not propData then return {} end
112
+
113
+ local typeMap = {}
114
+ for _, prop in propData do
115
+ typeMap[prop.Name] = prop.ValueType and tostring(prop.ValueType) or "unknown"
116
+ end
117
+ return typeMap
118
+ end
119
+
120
+ function Reflection.listClasses()
121
+ local classes = safeCall(function()
122
+ return ReflectionService:GetClasses()
123
+ end)
124
+ if not classes then return {} end
125
+
126
+ local result = {}
127
+ for _, cls in classes do
128
+ table.insert(result, {
129
+ name = cls.Name,
130
+ superclass = cls.Superclass,
131
+ creatable = cls.Creatable,
132
+ })
133
+ end
134
+ return result
135
+ end
136
+
137
+ return Reflection