@adiholick/nvstudio-mcp 1.1.0 → 2.0.1

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.
@@ -10,8 +10,374 @@
10
10
  <int64 name="SourceAssetId">-1</int64>
11
11
  <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
12
12
  </Properties>
13
- <Item class="Script" referent="RBX749E4D5306E74B7C8E355593066F1D2B">
13
+ <Item class="ModuleScript" referent="RBX_create_instance">
14
14
  <Properties>
15
+ <BinaryString name="AttributesSerialize"></BinaryString>
16
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
17
+ <bool name="DefinesCapabilities">false</bool>
18
+ <bool name="Disabled">false</bool>
19
+ <Content name="LinkedSource"><null></null></Content>
20
+ <string name="Name">create_instance</string>
21
+ <int64 name="SourceAssetId">-1</int64>
22
+ <SharedString name="Tags"></SharedString>
23
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
24
+ local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
25
+ if not success or not config.ClassName then
26
+ return { status = "error", error = "Format data JSON tidak valid atau ClassName hilang." }
27
+ end
28
+
29
+ local newSuccess, newInst = pcall(function()
30
+ local inst = Instance.new(config.ClassName)
31
+ inst.Parent = targetInstance
32
+
33
+ if config.Properties then
34
+ for prop, val in pairs(config.Properties) do
35
+ pcall(function() inst[prop] = val end)
36
+ end
37
+ end
38
+ return inst
39
+ end)
40
+
41
+ if newSuccess then
42
+ return { status = "success", result = "Instance '"..config.ClassName.."' berhasil dibuat di " .. targetPath }
43
+ else
44
+ return { status = "error", error = "Gagal membuat Instance. Pastikan ClassName valid." }
45
+ end
46
+ end
47
+ ]]></ProtectedString>
48
+ </Properties>
49
+ </Item>
50
+ <Item class="ModuleScript" referent="RBX_delete_instance">
51
+ <Properties>
52
+ <BinaryString name="AttributesSerialize"></BinaryString>
53
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
54
+ <bool name="DefinesCapabilities">false</bool>
55
+ <bool name="Disabled">false</bool>
56
+ <Content name="LinkedSource"><null></null></Content>
57
+ <string name="Name">delete_instance</string>
58
+ <int64 name="SourceAssetId">-1</int64>
59
+ <SharedString name="Tags"></SharedString>
60
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
61
+ -- Guardrail: Cegah penghapusan Service utama
62
+ local protectedServices = {
63
+ "Workspace", "Players", "Lighting", "MaterialService", "NetworkClient",
64
+ "ReplicatedFirst", "ReplicatedStorage", "ServerScriptService", "ServerStorage",
65
+ "StarterGui", "StarterPack", "StarterPlayer", "Teams", "SoundService", "Chat",
66
+ "TextChatService", "VoiceChatService"
67
+ }
68
+
69
+ if targetInstance.Parent == game or targetInstance.Parent == nil then
70
+ for _, serviceName in ipairs(protectedServices) do
71
+ if targetInstance.Name == serviceName or targetInstance.ClassName == serviceName then
72
+ return { status = "error", error = "GUARDRAIL BLOCKED: Anda tidak diizinkan untuk menghapus layanan tingkat atas (" .. targetInstance.Name .. ")." }
73
+ end
74
+ end
75
+ end
76
+
77
+ local name = targetInstance.Name
78
+ local className = targetInstance.ClassName
79
+ local parentName = targetInstance.Parent and targetInstance.Parent.Name or "nil"
80
+
81
+ local success, err = pcall(function()
82
+ targetInstance:Destroy()
83
+ end)
84
+
85
+ if success then
86
+ return { status = "success", result = "Instance '" .. name .. "' (" .. className .. ") berhasil dihapus dari " .. parentName .. "." }
87
+ else
88
+ return { status = "error", error = "Gagal menghapus instance: " .. tostring(err) }
89
+ end
90
+ end
91
+ ]]></ProtectedString>
92
+ </Properties>
93
+ </Item>
94
+ <Item class="ModuleScript" referent="RBX_execute_luau">
95
+ <Properties>
96
+ <BinaryString name="AttributesSerialize"></BinaryString>
97
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
98
+ <bool name="DefinesCapabilities">false</bool>
99
+ <bool name="Disabled">false</bool>
100
+ <Content name="LinkedSource"><null></null></Content>
101
+ <string name="Name">execute_luau</string>
102
+ <int64 name="SourceAssetId">-1</int64>
103
+ <SharedString name="Tags"></SharedString>
104
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
105
+ local code = data
106
+ if not code or code == "" then
107
+ return { status = "error", error = "Kode yang dieksekusi tidak boleh kosong." }
108
+ end
109
+
110
+ local HttpService = game:GetService("HttpService")
111
+
112
+ -- Bungkus code ke dalam fungsi return
113
+ local wrappedCode = "return function()\n" .. code .. "\nend"
114
+
115
+ local moduleScript = Instance.new("ModuleScript")
116
+ moduleScript.Name = "AI_Execute_Temp"
117
+ moduleScript.Source = wrappedCode
118
+
119
+ -- Taruh di ReplicatedStorage sementara agar bisa di-require
120
+ moduleScript.Parent = game:GetService("ReplicatedStorage")
121
+
122
+ local resultData = nil
123
+ local execError = nil
124
+ local isFinished = false
125
+
126
+ -- Eksekusi asinkron dengan batas waktu (timeout) 5 detik
127
+ task.spawn(function()
128
+ local reqSuccess, func = pcall(function() return require(moduleScript) end)
129
+ if reqSuccess and type(func) == "function" then
130
+ local execSuccess, res = pcall(func)
131
+ if execSuccess then
132
+ resultData = res
133
+ else
134
+ execError = "Runtime Error: " .. tostring(res)
135
+ end
136
+ else
137
+ execError = "Syntax/Require Error: " .. tostring(func)
138
+ end
139
+ isFinished = true
140
+ end)
141
+
142
+ -- Tunggu hingga 5 detik
143
+ local timeWaited = 0
144
+ while not isFinished and timeWaited < 5 do
145
+ timeWaited = timeWaited + task.wait()
146
+ end
147
+
148
+ -- Pembersihan (Wajib dihancurkan)
149
+ pcall(function() moduleScript:Destroy() end)
150
+
151
+ if not isFinished then
152
+ return { status = "error", error = "Timeout 5 detik terlampaui. Kemungkinan ada infinite loop tanpa task.wait()." }
153
+ end
154
+
155
+ if execError then
156
+ return { status = "error", error = execError }
157
+ end
158
+
159
+ -- Cegah return nil error
160
+ if resultData == nil then
161
+ return { status = "success", result = "Eksekusi berhasil (Tidak ada nilai kembalian)." }
162
+ end
163
+
164
+ -- JSON Encode untuk mencegah raw Instance return crash
165
+ local serializeSuccess, jsonRes = pcall(function()
166
+ return HttpService:JSONEncode(resultData)
167
+ end)
168
+
169
+ if serializeSuccess then
170
+ return { status = "success", result = HttpService:JSONDecode(jsonRes) } -- decode kembali agar format aslinya diteruskan oleh router
171
+ else
172
+ return { status = "error", error = "Gagal menserialisasi hasil (Dilarang mengembalikan raw Roblox Instance): " .. tostring(jsonRes) }
173
+ end
174
+ end
175
+ ]]></ProtectedString>
176
+ </Properties>
177
+ </Item>
178
+ <Item class="ModuleScript" referent="RBX_generate_terrain">
179
+ <Properties>
180
+ <BinaryString name="AttributesSerialize"></BinaryString>
181
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
182
+ <bool name="DefinesCapabilities">false</bool>
183
+ <bool name="Disabled">false</bool>
184
+ <Content name="LinkedSource"><null></null></Content>
185
+ <string name="Name">generate_terrain</string>
186
+ <int64 name="SourceAssetId">-1</int64>
187
+ <SharedString name="Tags"></SharedString>
188
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
189
+ local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
190
+ if not success or not config.Size or not config.Position or not config.Material then
191
+ return { status = "error", error = "Format JSON tidak valid. Membutuhkan Size, Position, dan Material." }
192
+ end
193
+
194
+ local fillSuccess, err = pcall(function()
195
+ local terrain = game.Workspace.Terrain
196
+ local materialEnum = Enum.Material[config.Material] or Enum.Material.Grass
197
+
198
+ local size = Vector3.new(config.Size[1], config.Size[2], config.Size[3])
199
+ local position = Vector3.new(config.Position[1], config.Position[2], config.Position[3])
200
+
201
+ local region = Region3.new(position - (size/2), position + (size/2))
202
+ terrain:FillRegion(region:ExpandToGrid(4), 4, materialEnum)
203
+ end)
204
+
205
+ if fillSuccess then
206
+ return { status = "success", result = "Terrain (" .. config.Material .. ") berhasil di-generate." }
207
+ else
208
+ return { status = "error", error = "Gagal men-generate Terrain. Pastikan tipe data array angka valid: " .. tostring(err) }
209
+ end
210
+ end
211
+ ]]></ProtectedString>
212
+ </Properties>
213
+ </Item>
214
+ <Item class="ModuleScript" referent="RBX_get_children">
215
+ <Properties>
216
+ <BinaryString name="AttributesSerialize"></BinaryString>
217
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
218
+ <bool name="DefinesCapabilities">false</bool>
219
+ <bool name="Disabled">false</bool>
220
+ <Content name="LinkedSource"><null></null></Content>
221
+ <string name="Name">get_children</string>
222
+ <int64 name="SourceAssetId">-1</int64>
223
+ <SharedString name="Tags"></SharedString>
224
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
225
+ local children = {}
226
+ for _, child in ipairs(targetInstance:GetChildren()) do
227
+ table.insert(children, child.Name .. " (" .. child.ClassName .. ")")
228
+ end
229
+ return { status = "success", result = children }
230
+ end
231
+ ]]></ProtectedString>
232
+ </Properties>
233
+ </Item>
234
+ <Item class="ModuleScript" referent="RBX_get_logs">
235
+ <Properties>
236
+ <BinaryString name="AttributesSerialize"></BinaryString>
237
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
238
+ <bool name="DefinesCapabilities">false</bool>
239
+ <bool name="Disabled">false</bool>
240
+ <Content name="LinkedSource"><null></null></Content>
241
+ <string name="Name">get_logs</string>
242
+ <int64 name="SourceAssetId">-1</int64>
243
+ <SharedString name="Tags"></SharedString>
244
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
245
+ if #ctx.recentLogs == 0 then
246
+ return { status = "success", result = {"Console kosong. Tidak ada error atau log terbaru."} }
247
+ else
248
+ return { status = "success", result = ctx.recentLogs }
249
+ end
250
+ end
251
+ ]]></ProtectedString>
252
+ </Properties>
253
+ </Item>
254
+ <Item class="ModuleScript" referent="RBX_get_properties">
255
+ <Properties>
256
+ <BinaryString name="AttributesSerialize"></BinaryString>
257
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
258
+ <bool name="DefinesCapabilities">false</bool>
259
+ <bool name="Disabled">false</bool>
260
+ <Content name="LinkedSource"><null></null></Content>
261
+ <string name="Name">get_properties</string>
262
+ <int64 name="SourceAssetId">-1</int64>
263
+ <SharedString name="Tags"></SharedString>
264
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
265
+ local CollectionService = game:GetService("CollectionService")
266
+
267
+ local props = {
268
+ Name = targetInstance.Name,
269
+ ClassName = targetInstance.ClassName,
270
+ Parent = targetInstance.Parent and targetInstance.Parent.Name or "nil",
271
+ }
272
+
273
+ -- Ambil Properties Umum secara aman via pcall
274
+ local function tryProp(propName)
275
+ local success, val = pcall(function() return targetInstance[propName] end)
276
+ if success and val ~= nil then
277
+ if typeof(val) == "Vector3" then
278
+ props[propName] = {math.round(val.X*100)/100, math.round(val.Y*100)/100, math.round(val.Z*100)/100}
279
+ elseif typeof(val) == "Color3" then
280
+ props[propName] = {math.round(val.R * 255), math.round(val.G * 255), math.round(val.B * 255)}
281
+ elseif typeof(val) == "CFrame" then
282
+ props[propName] = tostring(val)
283
+ elseif typeof(val) == "EnumItem" then
284
+ props[propName] = val.Name
285
+ elseif typeof(val) == "UDim2" then
286
+ props[propName] = {val.X.Scale, val.X.Offset, val.Y.Scale, val.Y.Offset}
287
+ elseif typeof(val) == "Instance" then
288
+ props[propName] = val:GetFullName()
289
+ else
290
+ props[propName] = val
291
+ end
292
+ end
293
+ end
294
+
295
+ local commonProps = {
296
+ "Anchored", "CanCollide", "Transparency", "Reflectance",
297
+ "Position", "Size", "Color", "Material", "Text", "TextColor3",
298
+ "BackgroundTransparency", "BackgroundColor3", "Visible", "Enabled",
299
+ "Volume", "Playing", "Looped", "Value"
300
+ }
301
+
302
+ for _, prop in ipairs(commonProps) do
303
+ tryProp(prop)
304
+ end
305
+
306
+ -- Attributes & Tags
307
+ pcall(function()
308
+ props.Attributes = targetInstance:GetAttributes()
309
+ end)
310
+ pcall(function()
311
+ props.Tags = CollectionService:GetTags(targetInstance)
312
+ end)
313
+
314
+ return { status = "success", result = props }
315
+ end
316
+ ]]></ProtectedString>
317
+ </Properties>
318
+ </Item>
319
+ <Item class="ModuleScript" referent="RBX_get_script_source">
320
+ <Properties>
321
+ <BinaryString name="AttributesSerialize"></BinaryString>
322
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
323
+ <bool name="DefinesCapabilities">false</bool>
324
+ <bool name="Disabled">false</bool>
325
+ <Content name="LinkedSource"><null></null></Content>
326
+ <string name="Name">get_script_source</string>
327
+ <int64 name="SourceAssetId">-1</int64>
328
+ <SharedString name="Tags"></SharedString>
329
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
330
+ if targetInstance:IsA("LuaSourceContainer") then
331
+ return { status = "success", result = targetInstance.Source }
332
+ else
333
+ return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. "). Kamu WAJIB menggunakan get_children terlebih dahulu sebelum menarik source." }
334
+ end
335
+ end
336
+ ]]></ProtectedString>
337
+ </Properties>
338
+ </Item>
339
+ <Item class="ModuleScript" referent="RBX_insert_asset">
340
+ <Properties>
341
+ <BinaryString name="AttributesSerialize"></BinaryString>
342
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
343
+ <bool name="DefinesCapabilities">false</bool>
344
+ <bool name="Disabled">false</bool>
345
+ <Content name="LinkedSource"><null></null></Content>
346
+ <string name="Name">insert_asset</string>
347
+ <int64 name="SourceAssetId">-1</int64>
348
+ <SharedString name="Tags"></SharedString>
349
+ <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
350
+ local InsertService = game:GetService("InsertService")
351
+ local assetId = tonumber(data)
352
+ if not assetId then
353
+ return { status = "error", error = "Asset ID harus berupa angka yang valid." }
354
+ end
355
+
356
+ local success, model = pcall(function()
357
+ return InsertService:LoadAsset(assetId)
358
+ end)
359
+
360
+ if success and model then
361
+ model.Parent = game.Workspace
362
+ return { status = "success", result = "Aset berhasil dimasukkan ke Workspace." }
363
+ else
364
+ return { status = "error", error = "Gagal memuat aset. Pastikan Asset ID benar dan akun/plugin Anda memiliki izin (Ownership/Public)." }
365
+ end
366
+ end
367
+ ]]></ProtectedString>
368
+ </Properties>
369
+ </Item>
370
+ <Item class="Script" referent="RBX_Main_server">
371
+ <Properties>
372
+ <BinaryString name="AttributesSerialize"></BinaryString>
373
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
374
+ <bool name="DefinesCapabilities">false</bool>
375
+ <bool name="Disabled">false</bool>
376
+ <Content name="LinkedSource"><null></null></Content>
377
+ <string name="Name">Main.server</string>
378
+ <int64 name="SourceAssetId">-1</int64>
379
+ <SharedString name="Tags"></SharedString>
380
+ <token name="RunContext">0</token>
15
381
  <ProtectedString name="Source"><![CDATA[local HttpService = game:GetService("HttpService")
16
382
  local LogService = game:GetService("LogService")
17
383
 
@@ -31,10 +397,10 @@ local toggleBtn = toolbar:CreateButton("Dashboard", "Buka Dashboard NVStudio MCP
31
397
 
32
398
  -- Global State & Context AI
33
399
  local ctx = {
34
- scriptHistory = {},
35
- recentLogs = {},
36
- HttpService = HttpService,
37
- studioId = STUDIO_ID
400
+ scriptHistory = {},
401
+ recentLogs = {},
402
+ HttpService = HttpService,
403
+ studioId = STUDIO_ID
38
404
  }
39
405
  local MAX_LOGS = 50
40
406
  local isConnected = false
@@ -48,80 +414,80 @@ local stopPolling
48
414
 
49
415
  -- Inisialisasi UI jika modul ditemukan
50
416
  if uiModule then
51
- local UIManager = require(uiModule)
52
- ui = UIManager.init(plugin, STUDIO_ID)
53
-
54
- -- Sync Initial State ke UI
55
- ui:setAutoConnectState(autoConnect)
56
- ui:setStatus("disconnected")
57
-
58
- -- Event Toggle GUI Panel
59
- toggleBtn.Click:Connect(function()
60
- ui:toggleVisibility()
61
- end)
62
-
63
- -- Event Tombol Connect (dengan debounce untuk mencegah double-click)
64
- ui.connectBtn.MouseButton1Click:Connect(function()
65
- if isClickDebounce then return end
66
- isClickDebounce = true
67
-
68
- if not isConnected then
69
- -- CONNECT (manual) — reset manual disconnect flag agar auto-detect bisa bekerja
70
- isManualDisconnect = false
71
- isConnected = true
72
- ui:setStatus("waiting")
73
- ui:addLog("[System] Koneksi manual dimulai. Mencari AI Server (Waiting)...", Color3.fromRGB(255, 220, 100))
74
- startPolling()
75
- else
76
- -- DISCONNECT — hentikan polling dan tandai manual disconnect
77
- stopPolling()
78
- ui:setStatus("disconnected")
79
- ui:addLog("[System] Koneksi dihentikan oleh pengguna.", Color3.fromRGB(255, 100, 100))
80
- end
81
-
82
- task.delay(0.3, function()
83
- isClickDebounce = false
84
- end)
85
- end)
86
-
87
- -- Event Custom Checkbox (Auto-Connect)
88
- ui.autoConnectBtn.MouseButton1Click:Connect(function()
89
- autoConnect = not autoConnect
90
- plugin:SetSetting("AutoConnect", autoConnect)
91
- ui:setAutoConnectState(autoConnect)
92
- ui:addLog("[System] Preferensi Auto-Connect disimpan: " .. tostring(autoConnect), Color3.fromRGB(200, 200, 255))
93
- end)
417
+ local UIManager = require(uiModule)
418
+ ui = UIManager.init(plugin, STUDIO_ID)
419
+
420
+ -- Sync Initial State ke UI
421
+ ui:setAutoConnectState(autoConnect)
422
+ ui:setStatus("disconnected")
423
+
424
+ -- Event Toggle GUI Panel
425
+ toggleBtn.Click:Connect(function()
426
+ ui:toggleVisibility()
427
+ end)
428
+
429
+ -- Event Tombol Connect (dengan debounce untuk mencegah double-click)
430
+ ui.connectBtn.MouseButton1Click:Connect(function()
431
+ if isClickDebounce then return end
432
+ isClickDebounce = true
433
+
434
+ if not isConnected then
435
+ -- CONNECT (manual) — reset manual disconnect flag agar auto-detect bisa bekerja
436
+ isManualDisconnect = false
437
+ isConnected = true
438
+ ui:setStatus("waiting")
439
+ ui:addLog("[System] Koneksi manual dimulai. Mencari AI Server (Waiting)...", Color3.fromRGB(255, 220, 100))
440
+ startPolling()
441
+ else
442
+ -- DISCONNECT — hentikan polling dan tandai manual disconnect
443
+ stopPolling()
444
+ ui:setStatus("disconnected")
445
+ ui:addLog("[System] Koneksi dihentikan oleh pengguna.", Color3.fromRGB(255, 100, 100))
446
+ end
447
+
448
+ task.delay(0.3, function()
449
+ isClickDebounce = false
450
+ end)
451
+ end)
452
+
453
+ -- Event Custom Checkbox (Auto-Connect)
454
+ ui.autoConnectBtn.MouseButton1Click:Connect(function()
455
+ autoConnect = not autoConnect
456
+ plugin:SetSetting("AutoConnect", autoConnect)
457
+ ui:setAutoConnectState(autoConnect)
458
+ ui:addLog("[System] Preferensi Auto-Connect disimpan: " .. tostring(autoConnect), Color3.fromRGB(200, 200, 255))
459
+ end)
94
460
  else
95
- -- Fallback aman jika pengguna lupa meng-copy ui_manager.lua
96
- toggleBtn.Click:Connect(function()
97
- print("[NVStudio MCP] ERROR: Modul 'ui_manager' tidak ditemukan di dalam Folder. GUI Gagal dimuat!")
98
- end)
461
+ -- Fallback aman jika pengguna lupa meng-copy ui_manager.lua
462
+ toggleBtn.Click:Connect(function()
463
+ print("[NVStudio MCP] ERROR: Modul 'ui_manager' tidak ditemukan di dalam Folder. GUI Gagal dimuat!")
464
+ end)
99
465
  end
100
466
 
101
467
  -- Handler Output Studio (Untuk AI dan GUI)
102
468
  local function addSystemLog(msg, msgType)
103
- local color = Color3.fromRGB(200, 200, 200)
104
- local prefix = "[INFO] "
105
- if msgType == Enum.MessageType.MessageError then
106
- prefix = "[ERROR] "
107
- color = Color3.fromRGB(255, 100, 100)
108
- elseif msgType == Enum.MessageType.MessageWarning then
109
- prefix = "[WARN] "
110
- color = Color3.fromRGB(255, 200, 100)
111
- end
112
-
113
- local fullMsg = prefix .. msg
114
-
115
- -- 1. Simpan di memori untuk disedot AI
116
- table.insert(ctx.recentLogs, fullMsg)
117
- if #ctx.recentLogs > MAX_LOGS then
118
- table.remove(ctx.recentLogs, 1)
119
- end
120
-
121
- -- 2. Cetak ke Terminal GUI Plugin
122
- if ui then
123
- ui:addLog(fullMsg, color)
124
- end
469
+ local color = Color3.fromRGB(200, 200, 200)
470
+ local prefix = "[INFO] "
471
+ if msgType == Enum.MessageType.MessageError then
472
+ prefix = "[ERROR] "
473
+ color = Color3.fromRGB(255, 100, 100)
474
+ elseif msgType == Enum.MessageType.MessageWarning then
475
+ prefix = "[WARN] "
476
+ color = Color3.fromRGB(255, 200, 100)
477
+ end
478
+
479
+ local fullMsg = prefix .. msg
480
+
481
+ -- 1. Simpan di memori untuk disedot AI
482
+ table.insert(ctx.recentLogs, fullMsg)
483
+ if #ctx.recentLogs > MAX_LOGS then
484
+ table.remove(ctx.recentLogs, 1)
485
+ end
486
+
487
+ -- 2. Cetak ke Terminal GUI Plugin
488
+ if ui then
489
+ ui:addLog(fullMsg, color)
490
+ end
125
491
  end
126
492
 
127
493
  -- Ikat listener output
@@ -130,124 +496,135 @@ LogService.MessageOut:Connect(addSystemLog)
130
496
  -- Memuat semua module skills secara dinamis
131
497
  local skills = {}
132
498
  for _, module in ipairs(script.Parent:GetChildren()) do
133
- if module:IsA("ModuleScript") and module.Name ~= "ui_manager" then
134
- skills[module.Name] = require(module)
135
- end
499
+ if module:IsA("ModuleScript") and module.Name ~= "ui_manager" then
500
+ skills[module.Name] = require(module)
501
+ end
136
502
  end
137
503
 
138
504
  -- Fungsi Helper Pathing
139
505
  local function getInstanceFromPath(pathString)
140
- local parts = string.split(pathString, ".")
141
- local current = game
142
-
143
- if parts[1] == "game" then table.remove(parts, 1) end
144
-
145
- for _, part in ipairs(parts) do
146
- local found = current:FindFirstChild(part)
147
- if not found then return nil end
148
- current = found
149
- end
150
- return current
506
+ local parts = string.split(pathString, ".")
507
+ local current = game
508
+
509
+ if parts[1] == "game" then table.remove(parts, 1) end
510
+
511
+ -- Handle common Services directly to ensure reliability
512
+ pcall(function()
513
+ local svc = game:GetService(parts[1])
514
+ if svc then
515
+ current = svc
516
+ table.remove(parts, 1)
517
+ end
518
+ end)
519
+
520
+ for _, part in ipairs(parts) do
521
+ local found = current:FindFirstChild(part)
522
+ if not found then return nil end
523
+ current = found
524
+ end
525
+ return current
151
526
  end
152
527
 
153
528
  -- Memproses Task Eksekusi dari AI
154
529
  local function processTask(taskData)
155
- if not taskData or not taskData.id or not taskData.command then return nil end
156
-
157
- if ui then
158
- ui:addLog("[AI Command] Menerima instruksi: " .. taskData.command, Color3.fromRGB(150, 200, 255))
159
- end
160
-
161
- local targetInstance = getInstanceFromPath(taskData.target)
162
- if not targetInstance then
163
- return {
164
- id = taskData.id,
165
- status = "error",
166
- error = "Target path tidak ditemukan: " .. taskData.target
167
- }
168
- end
169
-
170
- local skillFunc = skills[taskData.command]
171
- if not skillFunc then
172
- return {
173
- id = taskData.id,
174
- status = "error",
175
- error = "Command tidak dikenali oleh Studio: " .. taskData.command
176
- }
177
- end
178
-
179
- -- Eksekusi skill AI
180
- local result = skillFunc(targetInstance, taskData.data, ctx, taskData.target)
181
- result.id = taskData.id
182
-
183
- -- Cetak hasil eksekusi ke Terminal GUI
184
- if ui then
185
- local color = result.status == "success" and Color3.fromRGB(100, 255, 100) or Color3.fromRGB(255, 100, 100)
186
- ui:addLog("[AI Result] " .. tostring(result.result or result.error), color)
187
- end
188
-
189
- return result
530
+ if not taskData or not taskData.id or not taskData.command then return nil end
531
+
532
+ if ui then
533
+ ui:addLog("[AI Command] Menerima instruksi: " .. taskData.command, Color3.fromRGB(150, 200, 255))
534
+ end
535
+
536
+ local targetInstance = getInstanceFromPath(taskData.target)
537
+ if not targetInstance then
538
+ return {
539
+ id = taskData.id,
540
+ status = "error",
541
+ error = "Target path tidak ditemukan: " .. taskData.target
542
+ }
543
+ end
544
+
545
+ local skillFunc = skills[taskData.command]
546
+ if not skillFunc then
547
+ return {
548
+ id = taskData.id,
549
+ status = "error",
550
+ error = "Command tidak dikenali oleh Studio: " .. taskData.command
551
+ }
552
+ end
553
+
554
+ -- Eksekusi skill AI
555
+ local result = skillFunc(targetInstance, taskData.data, ctx, taskData.target)
556
+ result.id = taskData.id
557
+
558
+ -- Cetak hasil eksekusi ke Terminal GUI
559
+ if ui then
560
+ local color = result.status == "success" and Color3.fromRGB(100, 255, 100) or Color3.fromRGB(255, 100, 100)
561
+ ui:addLog("[AI Result] " .. tostring(result.result or result.error), color)
562
+ end
563
+
564
+ return result
190
565
  end
191
566
 
192
567
  -- Menghentikan polling dengan bersih (dipanggil oleh tombol Disconnect)
193
568
  local isManualDisconnect = false -- Mencegah auto-reconnect saat user sengaja disconnect
194
569
 
195
570
  function stopPolling()
196
- isConnected = false
197
- isManualDisconnect = true
198
- -- isPolling akan menjadi false secara otomatis saat loop keluar
571
+ isConnected = false
572
+ isManualDisconnect = true
573
+ -- isPolling akan menjadi false secara otomatis saat loop keluar
199
574
  end
200
575
 
201
576
  -- Background Polling Loop (Dapat dihentikan oleh user)
202
577
  function startPolling()
203
- if isPolling then return end
204
- isPolling = true
205
-
206
- task.spawn(function()
207
- while isConnected do
208
- local success, response = pcall(function()
209
- -- Tambahkan cache-buster agar Studio tidak me-cache response HTTP
210
- local cacheBuster = "?t=" .. tostring(os.clock())
211
- return HttpService:GetAsync(TASK_URL .. cacheBuster)
212
- end)
213
-
214
- -- PENTING: Cek ulang isConnected SETELAH HTTP request selesai.
215
- -- Jika user menekan Disconnect saat request sedang berjalan,
216
- -- kita TIDAK boleh menimpa status UI "disconnected" yang sudah di-set.
217
- if not isConnected then
218
- break
219
- end
220
-
221
- if success and response then
222
- -- JIKA PING SUKSES, Node.js menyala (IDE / Agen Sedang Terbuka!)
223
- if ui then ui:setStatus("connected") end
224
-
225
- local decodeSuccess, taskData = pcall(function()
226
- return HttpService:JSONDecode(response)
227
- end)
228
-
229
- if decodeSuccess and taskData and taskData.id then
230
- local resultData = processTask(taskData)
231
- if resultData then
232
- pcall(function()
233
- local jsonResult = HttpService:JSONEncode(resultData)
234
- HttpService:PostAsync(RESPONSE_URL, jsonResult, Enum.HttpContentType.ApplicationJson)
235
- end)
236
- end
237
- end
238
- else
239
- -- Fetch gagal (Node.js server mati karena IDE/Antigravity/Cursor ditutup)
240
- if ui then ui:setStatus("waiting") end
241
- end
242
-
243
- -- Cek lagi sebelum sleep (agar disconnect lebih responsif)
244
- if not isConnected then
245
- break
246
- end
247
- task.wait(1)
248
- end
249
- isPolling = false
250
- end)
578
+ if isPolling then return end
579
+ isPolling = true
580
+
581
+ task.spawn(function()
582
+ while isConnected do
583
+ local success, response = pcall(function()
584
+ -- Tambahkan cache-buster agar Studio tidak me-cache response HTTP
585
+ local cacheBuster = "?t=" .. tostring(os.clock())
586
+ return HttpService:GetAsync(TASK_URL .. cacheBuster)
587
+ end)
588
+
589
+ -- PENTING: Cek ulang isConnected SETELAH HTTP request selesai.
590
+ if not isConnected then
591
+ break
592
+ end
593
+
594
+ if success and response then
595
+ -- JIKA PING SUKSES, Node.js menyala (IDE / Agen Sedang Terbuka!)
596
+ if ui then ui:setStatus("connected") end
597
+
598
+ local decodeSuccess, taskData = pcall(function()
599
+ return HttpService:JSONDecode(response)
600
+ end)
601
+
602
+ if decodeSuccess and taskData and taskData.id then
603
+ local resultData = processTask(taskData)
604
+ if resultData then
605
+ pcall(function()
606
+ local jsonResult = HttpService:JSONEncode(resultData)
607
+ HttpService:PostAsync(RESPONSE_URL, jsonResult, Enum.HttpContentType.ApplicationJson)
608
+ end)
609
+ end
610
+ end
611
+
612
+ -- Fast-polling (long-polling yield di server Node.js)
613
+ -- 50ms untuk safety mencegah Studio Engine freeze jika Node.js membalas instant
614
+ task.wait(0.05)
615
+ else
616
+ -- Fetch gagal (Node.js server mati karena IDE/Antigravity/Cursor ditutup)
617
+ if ui then ui:setStatus("waiting") end
618
+ task.wait(1)
619
+ end
620
+
621
+ -- Cek lagi sebelum sleep (agar disconnect lebih responsif)
622
+ if not isConnected then
623
+ break
624
+ end
625
+ end
626
+ isPolling = false
627
+ end)
251
628
  end
252
629
 
253
630
  -- ══════════════════════════════════════════════════════════════════════
@@ -257,1081 +634,787 @@ end
257
634
  -- Jika terdeteksi mati → otomatis update status.
258
635
  -- ══════════════════════════════════════════════════════════════════════
259
636
  task.spawn(function()
260
- task.wait(1) -- Tunggu sebentar agar UI siap
261
-
262
- if ui then
263
- ui:addLog("[System] Plugin termuat. Auto-detect server MCP aktif...", Color3.fromRGB(200, 200, 200))
264
- end
265
-
266
- while true do
267
- -- Skip heartbeat jika polling loop sudah aktif (ia sudah menangani semuanya)
268
- if not isPolling then
269
- local success, errorMsg = pcall(function()
270
- local cacheBuster = "?t=" .. tostring(os.clock())
271
- return HttpService:GetAsync(TASK_URL .. cacheBuster)
272
- end)
273
-
274
- if success then
275
- -- Server terdeteksi aktif!
276
- if not isConnected and not isManualDisconnect then
277
- isConnected = true
278
- if ui then
279
- ui:setStatus("connected")
280
- ui:addLog("[System] Server MCP terdeteksi! Auto-connected.", Color3.fromRGB(100, 255, 100))
281
- end
282
- startPolling()
283
- end
284
- else
285
- -- Server tidak aktif
286
- if isConnected then
287
- -- Server baru saja mati (sebelumnya connected) — polling loop juga akan exit
288
- isConnected = false
289
- if ui then
290
- ui:setStatus("waiting")
291
- ui:addLog("[System] Server MCP terputus. Menunggu reconnect...", Color3.fromRGB(255, 200, 100))
292
- end
293
- elseif not isManualDisconnect then
294
- if ui then
295
- ui:setStatus("waiting")
296
- -- HANYA print error jika belum terkoneksi, untuk debugging
297
- ui:addLog("[Debug] Deteksi gagal: " .. tostring(errorMsg), Color3.fromRGB(150, 150, 150))
298
- end
299
- end
300
- end
301
- end
302
-
303
- task.wait(2)
304
- end
637
+ task.wait(1) -- Tunggu sebentar agar UI siap
638
+
639
+ if ui then
640
+ ui:addLog("[System] Plugin termuat. Auto-detect server MCP aktif...", Color3.fromRGB(200, 200, 200))
641
+ end
642
+
643
+ while true do
644
+ -- Skip heartbeat jika polling loop sudah aktif (ia sudah menangani semuanya)
645
+ if not isPolling then
646
+ local success, errorMsg = pcall(function()
647
+ local cacheBuster = "?t=" .. tostring(os.clock())
648
+ return HttpService:GetAsync(TASK_URL .. cacheBuster)
649
+ end)
650
+
651
+ if success then
652
+ -- Server terdeteksi aktif!
653
+ if not isConnected and not isManualDisconnect then
654
+ isConnected = true
655
+ if ui then
656
+ ui:setStatus("connected")
657
+ ui:addLog("[System] Server MCP terdeteksi! Auto-connected.", Color3.fromRGB(100, 255, 100))
658
+ end
659
+ startPolling()
660
+ end
661
+ else
662
+ -- Server tidak aktif
663
+ if isConnected then
664
+ -- Server baru saja mati (sebelumnya connected) — polling loop juga akan exit
665
+ isConnected = false
666
+ if ui then
667
+ ui:setStatus("waiting")
668
+ ui:addLog("[System] Server MCP terputus. Menunggu reconnect...", Color3.fromRGB(255, 200, 100))
669
+ end
670
+ elseif not isManualDisconnect then
671
+ if ui then
672
+ ui:setStatus("waiting")
673
+ -- HANYA print error jika belum terkoneksi, untuk debugging
674
+ ui:addLog("[Debug] Deteksi gagal: " .. tostring(errorMsg), Color3.fromRGB(150, 150, 150))
675
+ end
676
+ end
677
+ end
678
+ end
679
+
680
+ task.wait(2)
681
+ end
305
682
  end)
306
683
  ]]></ProtectedString>
307
- <bool name="Disabled">false</bool>
308
- <Content name="LinkedSource"><null></null></Content>
309
- <token name="RunContext">0</token>
310
- <string name="ScriptGuid">{260D8035-CDB9-4D28-83B4-92903E74BA82}</string>
311
- <BinaryString name="AttributesSerialize"></BinaryString>
312
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
313
- <bool name="DefinesCapabilities">false</bool>
314
- <string name="Name">Main</string>
315
- <int64 name="SourceAssetId">-1</int64>
316
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
317
684
  </Properties>
318
685
  </Item>
319
- <Item class="ModuleScript" referent="RBXF88A8EA0FA1B449280110AA8E425E2C9">
686
+ <Item class="ModuleScript" referent="RBX_patch_script_source">
320
687
  <Properties>
321
- <Content name="LinkedSource"><null></null></Content>
322
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
323
- local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
324
- if not success or not config.ClassName then
325
- return { status = "error", error = "Format data JSON tidak valid atau ClassName hilang." }
326
- end
327
-
328
- local newSuccess, newInst = pcall(function()
329
- local inst = Instance.new(config.ClassName)
330
- inst.Parent = targetInstance
331
-
332
- if config.Properties then
333
- for prop, val in pairs(config.Properties) do
334
- pcall(function() inst[prop] = val end)
335
- end
336
- end
337
- return inst
338
- end)
339
-
340
- if newSuccess then
341
- return { status = "success", result = "Instance '"..config.ClassName.."' berhasil dibuat di " .. targetPath }
342
- else
343
- return { status = "error", error = "Gagal membuat Instance. Pastikan ClassName valid." }
344
- end
345
- end
346
- ]]></ProtectedString>
347
- <string name="ScriptGuid">{77371DDE-9628-42F5-BFF7-C6B802AB9AC0}</string>
348
688
  <BinaryString name="AttributesSerialize"></BinaryString>
349
689
  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
350
690
  <bool name="DefinesCapabilities">false</bool>
351
- <string name="Name">create_instance</string>
352
- <int64 name="SourceAssetId">-1</int64>
353
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
354
- </Properties>
355
- </Item>
356
- <Item class="ModuleScript" referent="RBXA056230ABABC48FFBC66487DC4B83FB2">
357
- <Properties>
691
+ <bool name="Disabled">false</bool>
358
692
  <Content name="LinkedSource"><null></null></Content>
359
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
360
- local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
361
- if not success or not config.Size or not config.Position or not config.Material then
362
- return { status = "error", error = "Format JSON tidak valid. Membutuhkan Size, Position, dan Material." }
363
- end
364
-
365
- local fillSuccess, err = pcall(function()
366
- local terrain = game.Workspace.Terrain
367
- local materialEnum = Enum.Material[config.Material] or Enum.Material.Grass
368
-
369
- local size = Vector3.new(config.Size[1], config.Size[2], config.Size[3])
370
- local position = Vector3.new(config.Position[1], config.Position[2], config.Position[3])
371
-
372
- local region = Region3.new(position - (size/2), position + (size/2))
373
- terrain:FillRegion(region:ExpandToGrid(4), 4, materialEnum)
374
- end)
375
-
376
- if fillSuccess then
377
- return { status = "success", result = "Terrain (" .. config.Material .. ") berhasil di-generate." }
378
- else
379
- return { status = "error", error = "Gagal men-generate Terrain. Pastikan tipe data array angka valid: " .. tostring(err) }
380
- end
693
+ <string name="Name">patch_script_source</string>
694
+ <int64 name="SourceAssetId">-1</int64>
695
+ <SharedString name="Tags"></SharedString>
696
+ <ProtectedString name="Source"><![CDATA[local HttpService = game:GetService("HttpService")
697
+
698
+ return function(targetInstance, data, ctx, targetPath)
699
+ if not targetInstance:IsA("LuaSourceContainer") then
700
+ return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
701
+ end
702
+
703
+ local payload = nil
704
+ pcall(function()
705
+ payload = HttpService:JSONDecode(data)
706
+ end)
707
+
708
+ if not payload then
709
+ return { status = "error", error = "Payload data harus berupa JSON valid." }
710
+ end
711
+
712
+ local currentSource = targetInstance.Source
713
+ local newSource = currentSource
714
+ local affectedLines = 0
715
+
716
+ -- Mode 1: Search & Replace Exact Chunk
717
+ if payload.target_content and payload.replacement_content then
718
+ local targetContent = payload.target_content
719
+ local replacementContent = payload.replacement_content
720
+ local allowMultiple = payload.allow_multiple or false
721
+
722
+ -- Escape magic characters for string.gsub if needed, or use string.find manually.
723
+ -- string.gsub doesn't do plain text replacement directly without escaping.
724
+ -- We'll write a plain text replace function.
725
+
726
+ local function plainReplace(str, pattern, repl, max)
727
+ local res = ""
728
+ local startIdx = 1
729
+ local count = 0
730
+ while true do
731
+ if max and count >= max then break end
732
+ local findStart, findEnd = string.find(str, pattern, startIdx, true) -- true for plain match
733
+ if not findStart then break end
734
+
735
+ res = res .. string.sub(str, startIdx, findStart - 1) .. repl
736
+ startIdx = findEnd + 1
737
+ count = count + 1
738
+ end
739
+ res = res .. string.sub(str, startIdx)
740
+ return res, count
741
+ end
742
+
743
+ local _, occurrences = plainReplace(currentSource, targetContent, replacementContent)
744
+
745
+ if occurrences == 0 then
746
+ return { status = "error", error = "target_content tidak ditemukan di dalam script." }
747
+ elseif occurrences > 1 and not allowMultiple then
748
+ return { status = "error", error = "target_content ditemukan " .. occurrences .. " kali. Tolong sertakan baris konteks lebih banyak agar unik, atau set allow_multiple = true." }
749
+ end
750
+
751
+ newSource, affectedLines = plainReplace(currentSource, targetContent, replacementContent)
752
+ affectedLines = affectedLines -- not exact line count, but occurrences count
753
+
754
+ -- Mode 2: Line Range Replace
755
+ elseif payload.start_line and payload.end_line and payload.replacement_content then
756
+ local lines = string.split(currentSource, "\n")
757
+ local startLine = tonumber(payload.start_line)
758
+ local endLine = tonumber(payload.end_line)
759
+
760
+ if not startLine or not endLine or startLine < 1 or endLine > #lines or startLine > endLine then
761
+ return { status = "error", error = "Range baris tidak valid. Jumlah baris script: " .. #lines }
762
+ end
763
+
764
+ local newLines = {}
765
+ for i = 1, startLine - 1 do
766
+ table.insert(newLines, lines[i])
767
+ end
768
+
769
+ table.insert(newLines, payload.replacement_content)
770
+
771
+ for i = endLine + 1, #lines do
772
+ table.insert(newLines, lines[i])
773
+ end
774
+
775
+ newSource = table.concat(newLines, "\n")
776
+ affectedLines = (endLine - startLine) + 1
777
+ else
778
+ return { status = "error", error = "Payload tidak lengkap. Butuh (target_content & replacement_content) atau (start_line, end_line & replacement_content)." }
779
+ end
780
+
781
+ -- Simpan backup snapshot sebelum mengaplikasikan patch
782
+ ctx.scriptHistory[targetPath] = currentSource
783
+
784
+ -- Terapkan perubahan
785
+ targetInstance.Source = newSource
786
+
787
+ return {
788
+ status = "success",
789
+ result = "Patch berhasil diterapkan pada '" .. targetInstance.Name .. "'. Perubahan diaplikasikan.",
790
+ affected_lines = affectedLines
791
+ }
381
792
  end
382
793
  ]]></ProtectedString>
383
- <string name="ScriptGuid">{5D7B3DEA-8687-47A3-97CF-A969DAAF075B}</string>
384
- <BinaryString name="AttributesSerialize"></BinaryString>
385
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
386
- <bool name="DefinesCapabilities">false</bool>
387
- <string name="Name">generate_terrain</string>
388
- <int64 name="SourceAssetId">-1</int64>
389
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
390
794
  </Properties>
391
795
  </Item>
392
- <Item class="ModuleScript" referent="RBX020FB330A8554FAFA0499639D23AB46D">
796
+ <Item class="ModuleScript" referent="RBX_rollback_script">
393
797
  <Properties>
394
- <Content name="LinkedSource"><null></null></Content>
395
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
396
- local children = {}
397
- for _, child in ipairs(targetInstance:GetChildren()) do
398
- table.insert(children, child.Name .. " (" .. child.ClassName .. ")")
399
- end
400
- return { status = "success", result = children }
401
- end
402
- ]]></ProtectedString>
403
- <string name="ScriptGuid">{52133AC5-2F28-4C9D-A861-C726088A0450}</string>
404
798
  <BinaryString name="AttributesSerialize"></BinaryString>
405
799
  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
406
800
  <bool name="DefinesCapabilities">false</bool>
407
- <string name="Name">get_children</string>
408
- <int64 name="SourceAssetId">-1</int64>
409
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
410
- </Properties>
411
- </Item>
412
- <Item class="ModuleScript" referent="RBX625EEDF3B809470F94D6CA72BD468ED6">
413
- <Properties>
801
+ <bool name="Disabled">false</bool>
414
802
  <Content name="LinkedSource"><null></null></Content>
803
+ <string name="Name">rollback_script</string>
804
+ <int64 name="SourceAssetId">-1</int64>
805
+ <SharedString name="Tags"></SharedString>
415
806
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
416
- if #ctx.recentLogs == 0 then
417
- return { status = "success", result = {"Console kosong. Tidak ada error atau log terbaru."} }
418
- else
419
- return { status = "success", result = ctx.recentLogs }
420
- end
807
+ if targetInstance:IsA("LuaSourceContainer") then
808
+ local prevSource = ctx.scriptHistory[targetPath]
809
+ if prevSource then
810
+ targetInstance.Source = prevSource
811
+ return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil di-rollback ke versi sebelumnya." }
812
+ else
813
+ return { status = "error", error = "Tidak ada riwayat backup sebelumnya di sesi Studio ini untuk: " .. targetPath }
814
+ end
815
+ else
816
+ return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
817
+ end
421
818
  end
422
819
  ]]></ProtectedString>
423
- <string name="ScriptGuid">{5A11F6E8-8B4E-4561-AF76-62C40C81F0D7}</string>
424
- <BinaryString name="AttributesSerialize"></BinaryString>
425
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
426
- <bool name="DefinesCapabilities">false</bool>
427
- <string name="Name">get_logs</string>
428
- <int64 name="SourceAssetId">-1</int64>
429
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
430
820
  </Properties>
431
821
  </Item>
432
- <Item class="ModuleScript" referent="RBXE5B71E01ACBE4AD0B6DC072381E6F57B">
822
+ <Item class="ModuleScript" referent="RBX_script_grep">
433
823
  <Properties>
434
- <Content name="LinkedSource"><null></null></Content>
435
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
436
- if targetInstance:IsA("LuaSourceContainer") then
437
- return { status = "success", result = targetInstance.Source }
438
- else
439
- return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. "). Kamu WAJIB menggunakan get_children terlebih dahulu sebelum menarik source." }
440
- end
441
- end
442
- ]]></ProtectedString>
443
- <string name="ScriptGuid">{D6848696-9F37-490E-9ADF-634A868F8B0A}</string>
444
824
  <BinaryString name="AttributesSerialize"></BinaryString>
445
825
  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
446
826
  <bool name="DefinesCapabilities">false</bool>
447
- <string name="Name">get_script_source</string>
448
- <int64 name="SourceAssetId">-1</int64>
449
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
450
- </Properties>
451
- </Item>
452
- <Item class="ModuleScript" referent="RBX8A29B2C0C1804D6A9A8C3DEBD337010E">
453
- <Properties>
827
+ <bool name="Disabled">false</bool>
454
828
  <Content name="LinkedSource"><null></null></Content>
829
+ <string name="Name">script_grep</string>
830
+ <int64 name="SourceAssetId">-1</int64>
831
+ <SharedString name="Tags"></SharedString>
455
832
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
456
- local InsertService = game:GetService("InsertService")
457
- local assetId = tonumber(data)
458
- if not assetId then
459
- return { status = "error", error = "Asset ID harus berupa angka yang valid." }
460
- end
461
-
462
- local success, model = pcall(function()
463
- return InsertService:LoadAsset(assetId)
464
- end)
465
-
466
- if success and model then
467
- model.Parent = game.Workspace
468
- return { status = "success", result = "Aset berhasil dimasukkan ke Workspace." }
469
- else
470
- return { status = "error", error = "Gagal memuat aset. Pastikan Asset ID benar dan akun/plugin Anda memiliki izin (Ownership/Public)." }
471
- end
833
+ local query = tostring(data)
834
+ if not query or query == "" then
835
+ return { status = "error", error = "Query pencarian tidak boleh kosong." }
836
+ end
837
+
838
+ local matches = {}
839
+ local matchCount = 0
840
+ local MAX_MATCHES = 50
841
+
842
+ local searchableServices = {
843
+ game:GetService("Workspace"),
844
+ game:GetService("ServerScriptService"),
845
+ game:GetService("ReplicatedStorage"),
846
+ game:GetService("StarterPlayer"),
847
+ game:GetService("StarterGui")
848
+ }
849
+
850
+ local function searchInScript(scriptObj)
851
+ local success, source = pcall(function() return scriptObj.Source end)
852
+ if not success or not source then return end
853
+
854
+ local lines = string.split(source, "\n")
855
+ for i, line in ipairs(lines) do
856
+ -- Coba cari dengan plain text dulu, jika gagal coba pattern
857
+ local found = string.find(line, query, 1, true)
858
+ if not found then
859
+ pcall(function() found = string.find(line, query) end)
860
+ end
861
+
862
+ if found then
863
+ table.insert(matches, {
864
+ script = scriptObj:GetFullName(),
865
+ line = i,
866
+ content = string.match(line, "^%s*(.-)%s*$") or line
867
+ })
868
+ matchCount = matchCount + 1
869
+ if matchCount >= MAX_MATCHES then
870
+ return true
871
+ end
872
+ end
873
+ end
874
+ return false
875
+ end
876
+
877
+ for _, service in ipairs(searchableServices) do
878
+ pcall(function()
879
+ local descendants = service:GetDescendants()
880
+ for _, desc in ipairs(descendants) do
881
+ if desc:IsA("LuaSourceContainer") then
882
+ if searchInScript(desc) then
883
+ break
884
+ end
885
+ end
886
+ end
887
+ end)
888
+ if matchCount >= MAX_MATCHES then
889
+ break
890
+ end
891
+ end
892
+
893
+ return { status = "success", matches = matches, count = matchCount, limitReached = (matchCount >= MAX_MATCHES) }
472
894
  end
473
895
  ]]></ProtectedString>
474
- <string name="ScriptGuid">{7C5C84D5-FCBE-403F-9679-BF4A782C4A82}</string>
475
- <BinaryString name="AttributesSerialize"></BinaryString>
476
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
477
- <bool name="DefinesCapabilities">false</bool>
478
- <string name="Name">insert_asset</string>
479
- <int64 name="SourceAssetId">-1</int64>
480
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
481
896
  </Properties>
482
897
  </Item>
483
- <Item class="ModuleScript" referent="RBXB2C4DDC5A9A84FC5B6318F52E35AF483">
898
+ <Item class="ModuleScript" referent="RBX_search_instance">
484
899
  <Properties>
485
- <Content name="LinkedSource"><null></null></Content>
486
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
487
- if targetInstance:IsA("LuaSourceContainer") then
488
- local prevSource = ctx.scriptHistory[targetPath]
489
- if prevSource then
490
- targetInstance.Source = prevSource
491
- return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil di-rollback ke versi sebelumnya." }
492
- else
493
- return { status = "error", error = "Tidak ada riwayat backup sebelumnya di sesi Studio ini untuk: " .. targetPath }
494
- end
495
- else
496
- return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
497
- end
498
- end
499
- ]]></ProtectedString>
500
- <string name="ScriptGuid">{C7FD1B40-5FDA-4700-AE2A-201A6D1E282E}</string>
501
900
  <BinaryString name="AttributesSerialize"></BinaryString>
502
901
  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
503
902
  <bool name="DefinesCapabilities">false</bool>
504
- <string name="Name">rollback_script</string>
505
- <int64 name="SourceAssetId">-1</int64>
506
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
507
- </Properties>
508
- </Item>
509
- <Item class="ModuleScript" referent="RBX4A79E3095F604AFEB115D7444BDB20B3">
510
- <Properties>
903
+ <bool name="Disabled">false</bool>
511
904
  <Content name="LinkedSource"><null></null></Content>
905
+ <string name="Name">search_instance</string>
906
+ <int64 name="SourceAssetId">-1</int64>
907
+ <SharedString name="Tags"></SharedString>
512
908
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
513
- local searchName = tostring(data)
514
- local results = {}
515
-
516
- local searchableServices = {game:GetService("Workspace"), game:GetService("ReplicatedStorage"), game:GetService("ServerScriptService"), game:GetService("StarterGui")}
517
-
518
- for _, service in ipairs(searchableServices) do
519
- for _, desc in ipairs(service:GetDescendants()) do
520
- if desc.Name == searchName then
521
- table.insert(results, desc:GetFullName())
522
- end
523
- end
524
- end
525
-
526
- if #results > 0 then
527
- return { status = "success", result = results }
528
- else
529
- return { status = "success", result = "Tidak ditemukan instance dengan nama: " .. searchName }
530
- end
909
+ local searchName = tostring(data)
910
+ local results = {}
911
+
912
+ local searchableServices = {game:GetService("Workspace"), game:GetService("ReplicatedStorage"), game:GetService("ServerScriptService"), game:GetService("StarterGui")}
913
+
914
+ for _, service in ipairs(searchableServices) do
915
+ for _, desc in ipairs(service:GetDescendants()) do
916
+ if desc.Name == searchName then
917
+ table.insert(results, desc:GetFullName())
918
+ end
919
+ end
920
+ end
921
+
922
+ if #results > 0 then
923
+ return { status = "success", result = results }
924
+ else
925
+ return { status = "success", result = "Tidak ditemukan instance dengan nama: " .. searchName }
926
+ end
531
927
  end
532
928
  ]]></ProtectedString>
533
- <string name="ScriptGuid">{2A73242D-0CFB-4307-861B-CDAAC6A0B3AD}</string>
534
- <BinaryString name="AttributesSerialize"></BinaryString>
535
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
536
- <bool name="DefinesCapabilities">false</bool>
537
- <string name="Name">search_instance</string>
538
- <int64 name="SourceAssetId">-1</int64>
539
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
540
929
  </Properties>
541
930
  </Item>
542
- <Item class="ModuleScript" referent="RBX543373412A5740FCB6655A9B57FBE9D7">
931
+ <Item class="ModuleScript" referent="RBX_ui_manager">
543
932
  <Properties>
544
- <Content name="LinkedSource"><null></null></Content>
545
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
546
- if targetInstance:IsA("LuaSourceContainer") then
547
- local newCode = data or ""
548
-
549
- -- Catatan: Validasi AST menggunakan loadstring() sengaja DIHAPUS.
550
- -- Karena secara default Roblox Studio mematikan LoadStringEnabled, menggunakannya
551
- -- hanya akan membuat AI gagal terus menerus untuk menulis script.
552
- -- Error syntax akan ditangkap secara native oleh Roblox Output.
553
-
554
- ctx.scriptHistory[targetPath] = targetInstance.Source
555
- targetInstance.Source = newCode
556
- return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil diperbarui." }
557
- else
558
- return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
559
- end
560
- end
561
- ]]></ProtectedString>
562
- <string name="ScriptGuid">{B2F31812-C6C6-4AF9-8320-95D1E9069768}</string>
563
933
  <BinaryString name="AttributesSerialize"></BinaryString>
564
934
  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
565
935
  <bool name="DefinesCapabilities">false</bool>
566
- <string name="Name">update_script_source</string>
567
- <int64 name="SourceAssetId">-1</int64>
568
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
569
- </Properties>
570
- </Item>
571
- <Item class="ModuleScript" referent="RBX67FD07EDDECD4543B003A3628AF30B03">
572
- <Properties>
936
+ <bool name="Disabled">false</bool>
573
937
  <Content name="LinkedSource"><null></null></Content>
938
+ <string name="Name">ui_manager</string>
939
+ <int64 name="SourceAssetId">-1</int64>
940
+ <SharedString name="Tags"></SharedString>
574
941
  <ProtectedString name="Source"><![CDATA[local UIManager = {}
575
942
  local widget = nil
576
943
 
577
944
  function UIManager.init(pluginInstance, studioId)
578
- -- Membuat Widget Panel yang bisa di-dock (menempel)
579
- local widgetInfo = DockWidgetPluginGuiInfo.new(
580
- Enum.InitialDockState.Right,
581
- false, -- Initially hidden until toolbar button is clicked
582
- false, -- Override enabled
583
- 380, -- Default width
584
- 520, -- Default height
585
- 280, -- Min width
586
- 320 -- Min height
587
- )
588
-
589
- widget = pluginInstance:CreateDockWidgetPluginGui("NVStudioMCP_GUI_V3", widgetInfo)
590
- widget.Title = "NVStudio MCP Dashboard"
591
-
592
- -- ═══════════════════════════════════════════
593
- -- ROOT FRAME (Non-scrolling, fills entire widget)
594
- -- ═══════════════════════════════════════════
595
- local rootFrame = Instance.new("Frame")
596
- rootFrame.Size = UDim2.new(1, 0, 1, 0)
597
- rootFrame.BackgroundColor3 = Color3.fromRGB(22, 22, 26)
598
- rootFrame.BorderSizePixel = 0
599
- rootFrame.Parent = widget
600
-
601
- local rootPadding = Instance.new("UIPadding")
602
- rootPadding.PaddingTop = UDim.new(0, 8)
603
- rootPadding.PaddingLeft = UDim.new(0, 10)
604
- rootPadding.PaddingRight = UDim.new(0, 10)
605
- rootPadding.PaddingBottom = UDim.new(0, 8)
606
- rootPadding.Parent = rootFrame
607
-
608
- local rootLayout = Instance.new("UIListLayout")
609
- rootLayout.SortOrder = Enum.SortOrder.LayoutOrder
610
- rootLayout.Padding = UDim.new(0, 6)
611
- rootLayout.Parent = rootFrame
612
-
613
- -- ═══════════════════════════════════════════
614
- -- SECTION 1: HEADER CARD
615
- -- ═══════════════════════════════════════════
616
- local headerFrame = Instance.new("Frame")
617
- headerFrame.AutomaticSize = Enum.AutomaticSize.Y
618
- headerFrame.Size = UDim2.new(1, 0, 0, 0)
619
- headerFrame.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
620
- headerFrame.BorderSizePixel = 0
621
- headerFrame.LayoutOrder = 1
622
- headerFrame.Parent = rootFrame
623
-
624
- local hCorner = Instance.new("UICorner")
625
- hCorner.CornerRadius = UDim.new(0, 6)
626
- hCorner.Parent = headerFrame
627
-
628
- local hPadding = Instance.new("UIPadding")
629
- hPadding.PaddingTop = UDim.new(0, 10)
630
- hPadding.PaddingLeft = UDim.new(0, 12)
631
- hPadding.PaddingRight = UDim.new(0, 12)
632
- hPadding.PaddingBottom = UDim.new(0, 10)
633
- hPadding.Parent = headerFrame
634
-
635
- local hLayout = Instance.new("UIListLayout")
636
- hLayout.SortOrder = Enum.SortOrder.LayoutOrder
637
- hLayout.Padding = UDim.new(0, 2)
638
- hLayout.Parent = headerFrame
639
-
640
- -- Title Row (Name + Status side by side)
641
- local titleRow = Instance.new("Frame")
642
- titleRow.AutomaticSize = Enum.AutomaticSize.Y
643
- titleRow.Size = UDim2.new(1, 0, 0, 0)
644
- titleRow.BackgroundTransparency = 1
645
- titleRow.LayoutOrder = 1
646
- titleRow.Parent = headerFrame
647
-
648
- local titleLabel = Instance.new("TextLabel")
649
- titleLabel.Text = "NVStudio MCP"
650
- titleLabel.Font = Enum.Font.GothamBold
651
- titleLabel.TextSize = 18
652
- titleLabel.TextColor3 = Color3.fromRGB(240, 240, 245)
653
- titleLabel.BackgroundTransparency = 1
654
- titleLabel.Size = UDim2.new(0.6, 0, 0, 22)
655
- titleLabel.TextXAlignment = Enum.TextXAlignment.Left
656
- titleLabel.Parent = titleRow
657
-
658
- local statusLabel = Instance.new("TextLabel")
659
- statusLabel.Text = "● Disconnected"
660
- statusLabel.Font = Enum.Font.GothamBold
661
- statusLabel.TextSize = 12
662
- statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
663
- statusLabel.BackgroundTransparency = 1
664
- statusLabel.Size = UDim2.new(0.4, 0, 0, 22)
665
- statusLabel.Position = UDim2.new(0.6, 0, 0, 0)
666
- statusLabel.TextXAlignment = Enum.TextXAlignment.Right
667
- statusLabel.Parent = titleRow
668
-
669
- -- Version
670
- local versionLabel = Instance.new("TextLabel")
671
- versionLabel.Text = "v1.2.0 · Hybrid Agent"
672
- versionLabel.Font = Enum.Font.Gotham
673
- versionLabel.TextSize = 11
674
- versionLabel.TextColor3 = Color3.fromRGB(120, 120, 130)
675
- versionLabel.BackgroundTransparency = 1
676
- versionLabel.Size = UDim2.new(1, 0, 0, 14)
677
- versionLabel.TextXAlignment = Enum.TextXAlignment.Left
678
- versionLabel.LayoutOrder = 2
679
- versionLabel.Parent = headerFrame
680
-
681
- -- Thin separator
682
- local sep1 = Instance.new("Frame")
683
- sep1.Size = UDim2.new(1, 0, 0, 1)
684
- sep1.BackgroundColor3 = Color3.fromRGB(50, 50, 58)
685
- sep1.BorderSizePixel = 0
686
- sep1.LayoutOrder = 3
687
- sep1.Parent = headerFrame
688
-
689
- -- Place info
690
- local placeLabel = Instance.new("TextLabel")
691
- placeLabel.Text = "📁 " .. (game.Name ~= "" and game.Name or "Untitled Baseplate")
692
- placeLabel.Font = Enum.Font.Gotham
693
- placeLabel.TextSize = 11
694
- placeLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
695
- placeLabel.BackgroundTransparency = 1
696
- placeLabel.Size = UDim2.new(1, 0, 0, 16)
697
- placeLabel.TextXAlignment = Enum.TextXAlignment.Left
698
- placeLabel.LayoutOrder = 4
699
- placeLabel.Parent = headerFrame
700
-
701
- -- Studio ID
702
- local idLabel = Instance.new("TextLabel")
703
- idLabel.Text = "🔗 " .. string.sub(tostring(studioId), 1, 18) .. "..."
704
- idLabel.Font = Enum.Font.Code
705
- idLabel.TextSize = 10
706
- idLabel.TextColor3 = Color3.fromRGB(90, 180, 230)
707
- idLabel.BackgroundTransparency = 1
708
- idLabel.Size = UDim2.new(1, 0, 0, 14)
709
- idLabel.TextXAlignment = Enum.TextXAlignment.Left
710
- idLabel.LayoutOrder = 5
711
- idLabel.Parent = headerFrame
712
-
713
- -- Server URL
714
- local serverLabel = Instance.new("TextLabel")
715
- serverLabel.Text = "🌐 localhost:3055"
716
- serverLabel.Font = Enum.Font.Code
717
- serverLabel.TextSize = 10
718
- serverLabel.TextColor3 = Color3.fromRGB(140, 140, 150)
719
- serverLabel.BackgroundTransparency = 1
720
- serverLabel.Size = UDim2.new(1, 0, 0, 14)
721
- serverLabel.TextXAlignment = Enum.TextXAlignment.Left
722
- serverLabel.LayoutOrder = 6
723
- serverLabel.Parent = headerFrame
724
-
725
- -- ═══════════════════════════════════════════
726
- -- SECTION 2: CONTROLS ROW
727
- -- ═══════════════════════════════════════════
728
- local controlsFrame = Instance.new("Frame")
729
- controlsFrame.Size = UDim2.new(1, 0, 0, 32)
730
- controlsFrame.BackgroundTransparency = 1
731
- controlsFrame.LayoutOrder = 2
732
- controlsFrame.Parent = rootFrame
733
-
734
- -- Connect / Disconnect Button
735
- local connectBtn = Instance.new("TextButton")
736
- connectBtn.Size = UDim2.new(0.52, -4, 1, 0)
737
- connectBtn.Position = UDim2.new(0, 0, 0, 0)
738
- connectBtn.Text = " Connect"
739
- connectBtn.Font = Enum.Font.GothamBold
740
- connectBtn.TextSize = 13
741
- connectBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
742
- connectBtn.BackgroundColor3 = Color3.fromRGB(40, 160, 80)
743
- connectBtn.BorderSizePixel = 0
744
- connectBtn.AutoButtonColor = false
745
- connectBtn.ZIndex = 2
746
- connectBtn.Parent = controlsFrame
747
-
748
- local cCorner = Instance.new("UICorner")
749
- cCorner.CornerRadius = UDim.new(0, 5)
750
- cCorner.Parent = connectBtn
751
-
752
- -- Hover Effects for Connect Button (menggunakan stored base color, bukan text check)
753
- local _btnBaseColor = Color3.fromRGB(40, 160, 80)
754
- local _isHovering = false
755
-
756
- connectBtn.MouseEnter:Connect(function()
757
- _isHovering = true
758
- connectBtn.BackgroundColor3 = Color3.fromRGB(
759
- math.min(255, math.floor(_btnBaseColor.R * 255 + 25)),
760
- math.min(255, math.floor(_btnBaseColor.G * 255 + 25)),
761
- math.min(255, math.floor(_btnBaseColor.B * 255 + 25))
762
- )
763
- end)
764
- connectBtn.MouseLeave:Connect(function()
765
- _isHovering = false
766
- connectBtn.BackgroundColor3 = _btnBaseColor
767
- end)
768
-
769
- -- Auto-Connect Checkbox Container
770
- local autoConnectContainer = Instance.new("Frame")
771
- autoConnectContainer.Size = UDim2.new(0.48, -4, 1, 0)
772
- autoConnectContainer.Position = UDim2.new(0.52, 4, 0, 0)
773
- autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
774
- autoConnectContainer.BorderSizePixel = 0
775
- autoConnectContainer.Parent = controlsFrame
776
-
777
- local acCorner = Instance.new("UICorner")
778
- acCorner.CornerRadius = UDim.new(0, 5)
779
- acCorner.Parent = autoConnectContainer
780
-
781
- -- Checkbox visual (outer box)
782
- local checkboxOuter = Instance.new("Frame")
783
- checkboxOuter.Size = UDim2.new(0, 16, 0, 16)
784
- checkboxOuter.Position = UDim2.new(0, 10, 0.5, -8)
785
- checkboxOuter.BackgroundColor3 = Color3.fromRGB(55, 55, 65)
786
- checkboxOuter.BorderSizePixel = 0
787
- checkboxOuter.Parent = autoConnectContainer
788
-
789
- local cbCorner = Instance.new("UICorner")
790
- cbCorner.CornerRadius = UDim.new(0, 3)
791
- cbCorner.Parent = checkboxOuter
792
-
793
- local cbStroke = Instance.new("UIStroke")
794
- cbStroke.Color = Color3.fromRGB(80, 80, 95)
795
- cbStroke.Thickness = 1
796
- cbStroke.Parent = checkboxOuter
797
-
798
- -- Checkbox inner (check mark)
799
- local checkboxInner = Instance.new("Frame")
800
- checkboxInner.Size = UDim2.new(0, 10, 0, 10)
801
- checkboxInner.Position = UDim2.new(0.5, -5, 0.5, -5)
802
- checkboxInner.BackgroundColor3 = Color3.fromRGB(80, 170, 255)
803
- checkboxInner.Visible = false
804
- checkboxInner.Parent = checkboxOuter
805
-
806
- local cbiCorner = Instance.new("UICorner")
807
- cbiCorner.CornerRadius = UDim.new(0, 2)
808
- cbiCorner.Parent = checkboxInner
809
-
810
- -- Label
811
- local autoConnectLabel = Instance.new("TextLabel")
812
- autoConnectLabel.Text = "Auto-Connect"
813
- autoConnectLabel.Font = Enum.Font.Gotham
814
- autoConnectLabel.TextSize = 11
815
- autoConnectLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
816
- autoConnectLabel.BackgroundTransparency = 1
817
- autoConnectLabel.Position = UDim2.new(0, 32, 0, 0)
818
- autoConnectLabel.Size = UDim2.new(1, -36, 1, 0)
819
- autoConnectLabel.TextXAlignment = Enum.TextXAlignment.Left
820
- autoConnectLabel.Parent = autoConnectContainer
821
-
822
- -- Invisible overlay button for checkbox hit area
823
- local autoConnectBtn = Instance.new("TextButton")
824
- autoConnectBtn.Size = UDim2.new(1, 0, 1, 0)
825
- autoConnectBtn.BackgroundTransparency = 1
826
- autoConnectBtn.Text = ""
827
- autoConnectBtn.ZIndex = 3 -- Di atas elemen visual agar klik selalu terdeteksi
828
- autoConnectBtn.Parent = autoConnectContainer
829
-
830
- -- Hover effect for checkbox container
831
- autoConnectBtn.MouseEnter:Connect(function()
832
- autoConnectContainer.BackgroundColor3 = Color3.fromRGB(42, 42, 50)
833
- end)
834
- autoConnectBtn.MouseLeave:Connect(function()
835
- autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
836
- end)
837
-
838
- -- ═══════════════════════════════════════════
839
- -- SECTION 3: TERMINAL LOG (fills remaining space)
840
- -- ═══════════════════════════════════════════
841
- -- Terminal Label
842
- local termLabel = Instance.new("TextLabel")
843
- termLabel.Text = "TERMINAL"
844
- termLabel.Font = Enum.Font.GothamBold
845
- termLabel.TextSize = 10
846
- termLabel.TextColor3 = Color3.fromRGB(90, 90, 100)
847
- termLabel.BackgroundTransparency = 1
848
- termLabel.Size = UDim2.new(1, 0, 0, 14)
849
- termLabel.TextXAlignment = Enum.TextXAlignment.Left
850
- termLabel.LayoutOrder = 3
851
- termLabel.Parent = rootFrame
852
-
853
- -- Terminal container: Fills ALL remaining vertical space
854
- local terminalContainer = Instance.new("Frame")
855
- terminalContainer.BackgroundColor3 = Color3.fromRGB(14, 14, 18)
856
- terminalContainer.BorderSizePixel = 0
857
- terminalContainer.LayoutOrder = 4
858
- terminalContainer.ClipsDescendants = true
859
- terminalContainer.Parent = rootFrame
860
-
861
- local termCorner = Instance.new("UICorner")
862
- termCorner.CornerRadius = UDim.new(0, 5)
863
- termCorner.Parent = terminalContainer
864
-
865
- local termStroke = Instance.new("UIStroke")
866
- termStroke.Color = Color3.fromRGB(40, 40, 48)
867
- termStroke.Thickness = 1
868
- termStroke.Parent = terminalContainer
869
-
870
- -- Scrolling log area inside terminal
871
- local scrollingFrame = Instance.new("ScrollingFrame")
872
- scrollingFrame.Size = UDim2.new(1, -8, 1, -4)
873
- scrollingFrame.Position = UDim2.new(0, 4, 0, 2)
874
- scrollingFrame.BackgroundTransparency = 1
875
- scrollingFrame.BorderSizePixel = 0
876
- scrollingFrame.ScrollBarThickness = 4
877
- scrollingFrame.ScrollBarImageColor3 = Color3.fromRGB(70, 70, 85)
878
- scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0)
879
- scrollingFrame.Parent = terminalContainer
880
-
881
- local listLayout = Instance.new("UIListLayout")
882
- listLayout.Padding = UDim.new(0, 1)
883
- listLayout.SortOrder = Enum.SortOrder.LayoutOrder
884
- listLayout.Parent = scrollingFrame
885
-
886
- -- ═══════════════════════════════════════════
887
- -- RESPONSIVITY: Dynamically size terminal to fill remaining space
888
- -- ═══════════════════════════════════════════
889
- local function updateLayout()
890
- -- Calculate how much space header + controls + termLabel use
891
- local usedHeight = rootLayout.AbsoluteContentSize.Y - terminalContainer.AbsoluteSize.Y
892
- local availableHeight = rootFrame.AbsoluteSize.Y - 16 -- rootPadding top+bottom
893
- local termHeight = math.max(100, availableHeight - usedHeight)
894
- terminalContainer.Size = UDim2.new(1, 0, 0, termHeight)
895
- end
896
-
897
- -- Fire on widget resize
898
- widget:GetPropertyChangedSignal("AbsoluteSize"):Connect(updateLayout)
899
- -- Also fire once after initial render
900
- task.defer(updateLayout)
901
-
902
- -- ═══════════════════════════════════════════
903
- -- Store references for external access
904
- -- ═══════════════════════════════════════════
905
- UIManager.widget = widget
906
- UIManager.connectBtn = connectBtn
907
- UIManager.autoConnectBtn = autoConnectBtn
908
- UIManager.checkboxInner = checkboxInner
909
- UIManager.checkboxStroke = cbStroke
910
- UIManager.statusLabel = statusLabel
911
- UIManager.scrollingFrame = scrollingFrame
912
- UIManager.listLayout = listLayout
913
- UIManager.terminalContainer = terminalContainer
914
- UIManager.termStroke = termStroke
915
- UIManager.logsCount = 0
916
-
917
- -- Simpan referensi hover state agar setStatus bisa update base color
918
- UIManager._btnBaseColor = _btnBaseColor
919
- UIManager._isHovering = _isHovering
920
- UIManager._updateBtnBaseColor = function(newColor)
921
- _btnBaseColor = newColor
922
- if not _isHovering then
923
- connectBtn.BackgroundColor3 = newColor
924
- end
925
- end
926
-
927
- return UIManager
945
+ -- Membuat Widget Panel yang bisa di-dock (menempel)
946
+ local widgetInfo = DockWidgetPluginGuiInfo.new(
947
+ Enum.InitialDockState.Right,
948
+ false, -- Initially hidden until toolbar button is clicked
949
+ false, -- Override enabled
950
+ 380, -- Default width
951
+ 520, -- Default height
952
+ 280, -- Min width
953
+ 320 -- Min height
954
+ )
955
+
956
+ widget = pluginInstance:CreateDockWidgetPluginGui("NVStudioMCP_GUI_V3", widgetInfo)
957
+ widget.Title = "NVStudio MCP Dashboard"
958
+
959
+ -- ═══════════════════════════════════════════
960
+ -- ROOT FRAME (Non-scrolling, fills entire widget)
961
+ -- ═══════════════════════════════════════════
962
+ local rootFrame = Instance.new("Frame")
963
+ rootFrame.Size = UDim2.new(1, 0, 1, 0)
964
+ rootFrame.BackgroundColor3 = Color3.fromRGB(22, 22, 26)
965
+ rootFrame.BorderSizePixel = 0
966
+ rootFrame.Parent = widget
967
+
968
+ local rootPadding = Instance.new("UIPadding")
969
+ rootPadding.PaddingTop = UDim.new(0, 8)
970
+ rootPadding.PaddingLeft = UDim.new(0, 10)
971
+ rootPadding.PaddingRight = UDim.new(0, 10)
972
+ rootPadding.PaddingBottom = UDim.new(0, 8)
973
+ rootPadding.Parent = rootFrame
974
+
975
+ local rootLayout = Instance.new("UIListLayout")
976
+ rootLayout.SortOrder = Enum.SortOrder.LayoutOrder
977
+ rootLayout.Padding = UDim.new(0, 6)
978
+ rootLayout.Parent = rootFrame
979
+
980
+ -- ═══════════════════════════════════════════
981
+ -- SECTION 1: HEADER CARD
982
+ -- ═══════════════════════════════════════════
983
+ local headerFrame = Instance.new("Frame")
984
+ headerFrame.AutomaticSize = Enum.AutomaticSize.Y
985
+ headerFrame.Size = UDim2.new(1, 0, 0, 0)
986
+ headerFrame.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
987
+ headerFrame.BorderSizePixel = 0
988
+ headerFrame.LayoutOrder = 1
989
+ headerFrame.Parent = rootFrame
990
+
991
+ local hCorner = Instance.new("UICorner")
992
+ hCorner.CornerRadius = UDim.new(0, 6)
993
+ hCorner.Parent = headerFrame
994
+
995
+ local hPadding = Instance.new("UIPadding")
996
+ hPadding.PaddingTop = UDim.new(0, 10)
997
+ hPadding.PaddingLeft = UDim.new(0, 12)
998
+ hPadding.PaddingRight = UDim.new(0, 12)
999
+ hPadding.PaddingBottom = UDim.new(0, 10)
1000
+ hPadding.Parent = headerFrame
1001
+
1002
+ local hLayout = Instance.new("UIListLayout")
1003
+ hLayout.SortOrder = Enum.SortOrder.LayoutOrder
1004
+ hLayout.Padding = UDim.new(0, 2)
1005
+ hLayout.Parent = headerFrame
1006
+
1007
+ -- Title Row (Name + Status side by side)
1008
+ local titleRow = Instance.new("Frame")
1009
+ titleRow.AutomaticSize = Enum.AutomaticSize.Y
1010
+ titleRow.Size = UDim2.new(1, 0, 0, 0)
1011
+ titleRow.BackgroundTransparency = 1
1012
+ titleRow.LayoutOrder = 1
1013
+ titleRow.Parent = headerFrame
1014
+
1015
+ local titleLabel = Instance.new("TextLabel")
1016
+ titleLabel.Text = "NVStudio MCP"
1017
+ titleLabel.Font = Enum.Font.GothamBold
1018
+ titleLabel.TextSize = 18
1019
+ titleLabel.TextColor3 = Color3.fromRGB(240, 240, 245)
1020
+ titleLabel.BackgroundTransparency = 1
1021
+ titleLabel.Size = UDim2.new(0.6, 0, 0, 22)
1022
+ titleLabel.TextXAlignment = Enum.TextXAlignment.Left
1023
+ titleLabel.Parent = titleRow
1024
+
1025
+ local statusLabel = Instance.new("TextLabel")
1026
+ statusLabel.Text = "● Disconnected"
1027
+ statusLabel.Font = Enum.Font.GothamBold
1028
+ statusLabel.TextSize = 12
1029
+ statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
1030
+ statusLabel.BackgroundTransparency = 1
1031
+ statusLabel.Size = UDim2.new(0.4, 0, 0, 22)
1032
+ statusLabel.Position = UDim2.new(0.6, 0, 0, 0)
1033
+ statusLabel.TextXAlignment = Enum.TextXAlignment.Right
1034
+ statusLabel.Parent = titleRow
1035
+
1036
+ -- Version
1037
+ local versionLabel = Instance.new("TextLabel")
1038
+ versionLabel.Text = "v1.2.0 · Hybrid Agent"
1039
+ versionLabel.Font = Enum.Font.Gotham
1040
+ versionLabel.TextSize = 11
1041
+ versionLabel.TextColor3 = Color3.fromRGB(120, 120, 130)
1042
+ versionLabel.BackgroundTransparency = 1
1043
+ versionLabel.Size = UDim2.new(1, 0, 0, 14)
1044
+ versionLabel.TextXAlignment = Enum.TextXAlignment.Left
1045
+ versionLabel.LayoutOrder = 2
1046
+ versionLabel.Parent = headerFrame
1047
+
1048
+ -- Thin separator
1049
+ local sep1 = Instance.new("Frame")
1050
+ sep1.Size = UDim2.new(1, 0, 0, 1)
1051
+ sep1.BackgroundColor3 = Color3.fromRGB(50, 50, 58)
1052
+ sep1.BorderSizePixel = 0
1053
+ sep1.LayoutOrder = 3
1054
+ sep1.Parent = headerFrame
1055
+
1056
+ -- Place info
1057
+ local placeLabel = Instance.new("TextLabel")
1058
+ placeLabel.Text = "📁 " .. (game.Name ~= "" and game.Name or "Untitled Baseplate")
1059
+ placeLabel.Font = Enum.Font.Gotham
1060
+ placeLabel.TextSize = 11
1061
+ placeLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
1062
+ placeLabel.BackgroundTransparency = 1
1063
+ placeLabel.Size = UDim2.new(1, 0, 0, 16)
1064
+ placeLabel.TextXAlignment = Enum.TextXAlignment.Left
1065
+ placeLabel.LayoutOrder = 4
1066
+ placeLabel.Parent = headerFrame
1067
+
1068
+ -- Studio ID
1069
+ local idLabel = Instance.new("TextLabel")
1070
+ idLabel.Text = "🔗 " .. string.sub(tostring(studioId), 1, 18) .. "..."
1071
+ idLabel.Font = Enum.Font.Code
1072
+ idLabel.TextSize = 10
1073
+ idLabel.TextColor3 = Color3.fromRGB(90, 180, 230)
1074
+ idLabel.BackgroundTransparency = 1
1075
+ idLabel.Size = UDim2.new(1, 0, 0, 14)
1076
+ idLabel.TextXAlignment = Enum.TextXAlignment.Left
1077
+ idLabel.LayoutOrder = 5
1078
+ idLabel.Parent = headerFrame
1079
+
1080
+ -- Server URL
1081
+ local serverLabel = Instance.new("TextLabel")
1082
+ serverLabel.Text = "🌐 localhost:3055"
1083
+ serverLabel.Font = Enum.Font.Code
1084
+ serverLabel.TextSize = 10
1085
+ serverLabel.TextColor3 = Color3.fromRGB(140, 140, 150)
1086
+ serverLabel.BackgroundTransparency = 1
1087
+ serverLabel.Size = UDim2.new(1, 0, 0, 14)
1088
+ serverLabel.TextXAlignment = Enum.TextXAlignment.Left
1089
+ serverLabel.LayoutOrder = 6
1090
+ serverLabel.Parent = headerFrame
1091
+
1092
+ -- ═══════════════════════════════════════════
1093
+ -- SECTION 2: CONTROLS ROW
1094
+ -- ═══════════════════════════════════════════
1095
+ local controlsFrame = Instance.new("Frame")
1096
+ controlsFrame.Size = UDim2.new(1, 0, 0, 32)
1097
+ controlsFrame.BackgroundTransparency = 1
1098
+ controlsFrame.LayoutOrder = 2
1099
+ controlsFrame.Parent = rootFrame
1100
+
1101
+ -- Connect / Disconnect Button
1102
+ local connectBtn = Instance.new("TextButton")
1103
+ connectBtn.Size = UDim2.new(0.52, -4, 1, 0)
1104
+ connectBtn.Position = UDim2.new(0, 0, 0, 0)
1105
+ connectBtn.Text = " Connect"
1106
+ connectBtn.Font = Enum.Font.GothamBold
1107
+ connectBtn.TextSize = 13
1108
+ connectBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
1109
+ connectBtn.BackgroundColor3 = Color3.fromRGB(40, 160, 80)
1110
+ connectBtn.BorderSizePixel = 0
1111
+ connectBtn.AutoButtonColor = false
1112
+ connectBtn.ZIndex = 2
1113
+ connectBtn.Parent = controlsFrame
1114
+
1115
+ local cCorner = Instance.new("UICorner")
1116
+ cCorner.CornerRadius = UDim.new(0, 5)
1117
+ cCorner.Parent = connectBtn
1118
+
1119
+ -- Hover Effects for Connect Button (menggunakan stored base color, bukan text check)
1120
+ local _btnBaseColor = Color3.fromRGB(40, 160, 80)
1121
+ local _isHovering = false
1122
+
1123
+ connectBtn.MouseEnter:Connect(function()
1124
+ _isHovering = true
1125
+ connectBtn.BackgroundColor3 = Color3.fromRGB(
1126
+ math.min(255, math.floor(_btnBaseColor.R * 255 + 25)),
1127
+ math.min(255, math.floor(_btnBaseColor.G * 255 + 25)),
1128
+ math.min(255, math.floor(_btnBaseColor.B * 255 + 25))
1129
+ )
1130
+ end)
1131
+ connectBtn.MouseLeave:Connect(function()
1132
+ _isHovering = false
1133
+ connectBtn.BackgroundColor3 = _btnBaseColor
1134
+ end)
1135
+
1136
+ -- Auto-Connect Checkbox Container
1137
+ local autoConnectContainer = Instance.new("Frame")
1138
+ autoConnectContainer.Size = UDim2.new(0.48, -4, 1, 0)
1139
+ autoConnectContainer.Position = UDim2.new(0.52, 4, 0, 0)
1140
+ autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
1141
+ autoConnectContainer.BorderSizePixel = 0
1142
+ autoConnectContainer.Parent = controlsFrame
1143
+
1144
+ local acCorner = Instance.new("UICorner")
1145
+ acCorner.CornerRadius = UDim.new(0, 5)
1146
+ acCorner.Parent = autoConnectContainer
1147
+
1148
+ -- Checkbox visual (outer box)
1149
+ local checkboxOuter = Instance.new("Frame")
1150
+ checkboxOuter.Size = UDim2.new(0, 16, 0, 16)
1151
+ checkboxOuter.Position = UDim2.new(0, 10, 0.5, -8)
1152
+ checkboxOuter.BackgroundColor3 = Color3.fromRGB(55, 55, 65)
1153
+ checkboxOuter.BorderSizePixel = 0
1154
+ checkboxOuter.Parent = autoConnectContainer
1155
+
1156
+ local cbCorner = Instance.new("UICorner")
1157
+ cbCorner.CornerRadius = UDim.new(0, 3)
1158
+ cbCorner.Parent = checkboxOuter
1159
+
1160
+ local cbStroke = Instance.new("UIStroke")
1161
+ cbStroke.Color = Color3.fromRGB(80, 80, 95)
1162
+ cbStroke.Thickness = 1
1163
+ cbStroke.Parent = checkboxOuter
1164
+
1165
+ -- Checkbox inner (check mark)
1166
+ local checkboxInner = Instance.new("Frame")
1167
+ checkboxInner.Size = UDim2.new(0, 10, 0, 10)
1168
+ checkboxInner.Position = UDim2.new(0.5, -5, 0.5, -5)
1169
+ checkboxInner.BackgroundColor3 = Color3.fromRGB(80, 170, 255)
1170
+ checkboxInner.Visible = false
1171
+ checkboxInner.Parent = checkboxOuter
1172
+
1173
+ local cbiCorner = Instance.new("UICorner")
1174
+ cbiCorner.CornerRadius = UDim.new(0, 2)
1175
+ cbiCorner.Parent = checkboxInner
1176
+
1177
+ -- Label
1178
+ local autoConnectLabel = Instance.new("TextLabel")
1179
+ autoConnectLabel.Text = "Auto-Connect"
1180
+ autoConnectLabel.Font = Enum.Font.Gotham
1181
+ autoConnectLabel.TextSize = 11
1182
+ autoConnectLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
1183
+ autoConnectLabel.BackgroundTransparency = 1
1184
+ autoConnectLabel.Position = UDim2.new(0, 32, 0, 0)
1185
+ autoConnectLabel.Size = UDim2.new(1, -36, 1, 0)
1186
+ autoConnectLabel.TextXAlignment = Enum.TextXAlignment.Left
1187
+ autoConnectLabel.Parent = autoConnectContainer
1188
+
1189
+ -- Invisible overlay button for checkbox hit area
1190
+ local autoConnectBtn = Instance.new("TextButton")
1191
+ autoConnectBtn.Size = UDim2.new(1, 0, 1, 0)
1192
+ autoConnectBtn.BackgroundTransparency = 1
1193
+ autoConnectBtn.Text = ""
1194
+ autoConnectBtn.ZIndex = 3 -- Di atas elemen visual agar klik selalu terdeteksi
1195
+ autoConnectBtn.Parent = autoConnectContainer
1196
+
1197
+ -- Hover effect for checkbox container
1198
+ autoConnectBtn.MouseEnter:Connect(function()
1199
+ autoConnectContainer.BackgroundColor3 = Color3.fromRGB(42, 42, 50)
1200
+ end)
1201
+ autoConnectBtn.MouseLeave:Connect(function()
1202
+ autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
1203
+ end)
1204
+
1205
+ -- ═══════════════════════════════════════════
1206
+ -- SECTION 3: TERMINAL LOG (fills remaining space)
1207
+ -- ═══════════════════════════════════════════
1208
+ -- Terminal Label
1209
+ local termLabel = Instance.new("TextLabel")
1210
+ termLabel.Text = "TERMINAL"
1211
+ termLabel.Font = Enum.Font.GothamBold
1212
+ termLabel.TextSize = 10
1213
+ termLabel.TextColor3 = Color3.fromRGB(90, 90, 100)
1214
+ termLabel.BackgroundTransparency = 1
1215
+ termLabel.Size = UDim2.new(1, 0, 0, 14)
1216
+ termLabel.TextXAlignment = Enum.TextXAlignment.Left
1217
+ termLabel.LayoutOrder = 3
1218
+ termLabel.Parent = rootFrame
1219
+
1220
+ -- Terminal container: Fills ALL remaining vertical space
1221
+ local terminalContainer = Instance.new("Frame")
1222
+ terminalContainer.BackgroundColor3 = Color3.fromRGB(14, 14, 18)
1223
+ terminalContainer.BorderSizePixel = 0
1224
+ terminalContainer.LayoutOrder = 4
1225
+ terminalContainer.ClipsDescendants = true
1226
+ terminalContainer.Parent = rootFrame
1227
+
1228
+ local termCorner = Instance.new("UICorner")
1229
+ termCorner.CornerRadius = UDim.new(0, 5)
1230
+ termCorner.Parent = terminalContainer
1231
+
1232
+ local termStroke = Instance.new("UIStroke")
1233
+ termStroke.Color = Color3.fromRGB(40, 40, 48)
1234
+ termStroke.Thickness = 1
1235
+ termStroke.Parent = terminalContainer
1236
+
1237
+ -- Scrolling log area inside terminal
1238
+ local scrollingFrame = Instance.new("ScrollingFrame")
1239
+ scrollingFrame.Size = UDim2.new(1, -8, 1, -4)
1240
+ scrollingFrame.Position = UDim2.new(0, 4, 0, 2)
1241
+ scrollingFrame.BackgroundTransparency = 1
1242
+ scrollingFrame.BorderSizePixel = 0
1243
+ scrollingFrame.ScrollBarThickness = 4
1244
+ scrollingFrame.ScrollBarImageColor3 = Color3.fromRGB(70, 70, 85)
1245
+ scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0)
1246
+ scrollingFrame.Parent = terminalContainer
1247
+
1248
+ local listLayout = Instance.new("UIListLayout")
1249
+ listLayout.Padding = UDim.new(0, 1)
1250
+ listLayout.SortOrder = Enum.SortOrder.LayoutOrder
1251
+ listLayout.Parent = scrollingFrame
1252
+
1253
+ -- ═══════════════════════════════════════════
1254
+ -- RESPONSIVITY: Dynamically size terminal to fill remaining space
1255
+ -- ═══════════════════════════════════════════
1256
+ local function updateLayout()
1257
+ -- Calculate how much space header + controls + termLabel use
1258
+ local usedHeight = rootLayout.AbsoluteContentSize.Y - terminalContainer.AbsoluteSize.Y
1259
+ local availableHeight = rootFrame.AbsoluteSize.Y - 16 -- rootPadding top+bottom
1260
+ local termHeight = math.max(100, availableHeight - usedHeight)
1261
+ terminalContainer.Size = UDim2.new(1, 0, 0, termHeight)
1262
+ end
1263
+
1264
+ -- Fire on widget resize
1265
+ widget:GetPropertyChangedSignal("AbsoluteSize"):Connect(updateLayout)
1266
+ -- Also fire once after initial render
1267
+ task.defer(updateLayout)
1268
+
1269
+ -- ═══════════════════════════════════════════
1270
+ -- Store references for external access
1271
+ -- ═══════════════════════════════════════════
1272
+ UIManager.widget = widget
1273
+ UIManager.connectBtn = connectBtn
1274
+ UIManager.autoConnectBtn = autoConnectBtn
1275
+ UIManager.checkboxInner = checkboxInner
1276
+ UIManager.checkboxStroke = cbStroke
1277
+ UIManager.statusLabel = statusLabel
1278
+ UIManager.scrollingFrame = scrollingFrame
1279
+ UIManager.listLayout = listLayout
1280
+ UIManager.terminalContainer = terminalContainer
1281
+ UIManager.termStroke = termStroke
1282
+ UIManager.logsCount = 0
1283
+
1284
+ -- Simpan referensi hover state agar setStatus bisa update base color
1285
+ UIManager._btnBaseColor = _btnBaseColor
1286
+ UIManager._isHovering = _isHovering
1287
+ UIManager._updateBtnBaseColor = function(newColor)
1288
+ _btnBaseColor = newColor
1289
+ if not _isHovering then
1290
+ connectBtn.BackgroundColor3 = newColor
1291
+ end
1292
+ end
1293
+
1294
+ return UIManager
928
1295
  end
929
1296
 
930
1297
  -- ═══════════════════════════════════════════
931
1298
  -- Menambahkan baris log baru ke terminal GUI
932
1299
  -- ═══════════════════════════════════════════
933
1300
  function UIManager:addLog(text, color)
934
- local timestamp = os.date("%H:%M:%S")
935
- local displayText = "[" .. timestamp .. "] " .. text
936
-
937
- local logLabel = Instance.new("TextLabel")
938
- logLabel.Text = displayText
939
- logLabel.Font = Enum.Font.Code
940
- logLabel.TextSize = 11
941
- logLabel.TextColor3 = color or Color3.fromRGB(180, 180, 190)
942
- logLabel.BackgroundTransparency = 1
943
- logLabel.TextWrapped = true
944
- logLabel.TextXAlignment = Enum.TextXAlignment.Left
945
- logLabel.RichText = false
946
- logLabel.AutomaticSize = Enum.AutomaticSize.Y
947
- logLabel.Size = UDim2.new(1, -6, 0, 0) -- Width fill, height auto
948
-
949
- local logPadding = Instance.new("UIPadding")
950
- logPadding.PaddingLeft = UDim.new(0, 4)
951
- logPadding.PaddingTop = UDim.new(0, 1)
952
- logPadding.PaddingBottom = UDim.new(0, 1)
953
- logPadding.Parent = logLabel
954
-
955
- logLabel.LayoutOrder = self.logsCount
956
- logLabel.Parent = self.scrollingFrame
957
- self.logsCount = self.logsCount + 1
958
-
959
- -- Limit max logs to prevent memory bloat
960
- if self.logsCount > 150 then
961
- local first = self.scrollingFrame:FindFirstChildWhichIsA("TextLabel")
962
- if first then first:Destroy() end
963
- end
964
-
965
- -- Update canvas size after a frame so AutomaticSize has resolved
966
- task.defer(function()
967
- self.scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, self.listLayout.AbsoluteContentSize.Y + 4)
968
- self.scrollingFrame.CanvasPosition = Vector2.new(0, math.max(0, self.listLayout.AbsoluteContentSize.Y - self.scrollingFrame.AbsoluteSize.Y))
969
- end)
1301
+ local timestamp = os.date("%H:%M:%S")
1302
+ local displayText = "[" .. timestamp .. "] " .. text
1303
+
1304
+ local logLabel = Instance.new("TextLabel")
1305
+ logLabel.Text = displayText
1306
+ logLabel.Font = Enum.Font.Code
1307
+ logLabel.TextSize = 11
1308
+ logLabel.TextColor3 = color or Color3.fromRGB(180, 180, 190)
1309
+ logLabel.BackgroundTransparency = 1
1310
+ logLabel.TextWrapped = true
1311
+ logLabel.TextXAlignment = Enum.TextXAlignment.Left
1312
+ logLabel.RichText = false
1313
+ logLabel.AutomaticSize = Enum.AutomaticSize.Y
1314
+ logLabel.Size = UDim2.new(1, -6, 0, 0) -- Width fill, height auto
1315
+
1316
+ local logPadding = Instance.new("UIPadding")
1317
+ logPadding.PaddingLeft = UDim.new(0, 4)
1318
+ logPadding.PaddingTop = UDim.new(0, 1)
1319
+ logPadding.PaddingBottom = UDim.new(0, 1)
1320
+ logPadding.Parent = logLabel
1321
+
1322
+ logLabel.LayoutOrder = self.logsCount
1323
+ logLabel.Parent = self.scrollingFrame
1324
+ self.logsCount = self.logsCount + 1
1325
+
1326
+ -- Limit max logs to prevent memory bloat
1327
+ if self.logsCount > 150 then
1328
+ local first = self.scrollingFrame:FindFirstChildWhichIsA("TextLabel")
1329
+ if first then first:Destroy() end
1330
+ end
1331
+
1332
+ -- Update canvas size after a frame so AutomaticSize has resolved
1333
+ task.defer(function()
1334
+ self.scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, self.listLayout.AbsoluteContentSize.Y + 4)
1335
+ self.scrollingFrame.CanvasPosition = Vector2.new(0, math.max(0, self.listLayout.AbsoluteContentSize.Y - self.scrollingFrame.AbsoluteSize.Y))
1336
+ end)
970
1337
  end
971
1338
 
972
1339
  -- ═══════════════════════════════════════════
973
1340
  -- 3 State Setter (Disconnected, Waiting, Connected)
974
1341
  -- ═══════════════════════════════════════════
975
1342
  function UIManager:setStatus(stateStr)
976
- if stateStr == "disconnected" then
977
- self.connectBtn.Text = " Connect"
978
- self._updateBtnBaseColor(Color3.fromRGB(40, 160, 80))
979
- self.statusLabel.Text = "● Disconnected"
980
- self.statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
981
- self.termStroke.Color = Color3.fromRGB(40, 40, 48)
982
-
983
- elseif stateStr == "waiting" then
984
- self.connectBtn.Text = " Disconnect"
985
- self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
986
- self.statusLabel.Text = "● Waiting for AI..."
987
- self.statusLabel.TextColor3 = Color3.fromRGB(255, 200, 80)
988
- self.termStroke.Color = Color3.fromRGB(130, 110, 50)
989
-
990
- elseif stateStr == "connected" then
991
- self.connectBtn.Text = " Disconnect"
992
- self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
993
- self.statusLabel.Text = "● Connected"
994
- self.statusLabel.TextColor3 = Color3.fromRGB(100, 230, 120)
995
- self.termStroke.Color = Color3.fromRGB(50, 120, 60)
996
- end
1343
+ if stateStr == "disconnected" then
1344
+ self.connectBtn.Text = " Connect"
1345
+ self._updateBtnBaseColor(Color3.fromRGB(40, 160, 80))
1346
+ self.statusLabel.Text = "● Disconnected"
1347
+ self.statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
1348
+ self.termStroke.Color = Color3.fromRGB(40, 40, 48)
1349
+
1350
+ elseif stateStr == "waiting" then
1351
+ self.connectBtn.Text = " Disconnect"
1352
+ self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
1353
+ self.statusLabel.Text = "● Waiting for AI..."
1354
+ self.statusLabel.TextColor3 = Color3.fromRGB(255, 200, 80)
1355
+ self.termStroke.Color = Color3.fromRGB(130, 110, 50)
1356
+
1357
+ elseif stateStr == "connected" then
1358
+ self.connectBtn.Text = " Disconnect"
1359
+ self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
1360
+ self.statusLabel.Text = "● Connected"
1361
+ self.statusLabel.TextColor3 = Color3.fromRGB(100, 230, 120)
1362
+ self.termStroke.Color = Color3.fromRGB(50, 120, 60)
1363
+ end
997
1364
  end
998
1365
 
999
1366
  -- Mengubah visual custom checkbox
1000
1367
  function UIManager:setAutoConnectState(isAuto)
1001
- self.checkboxInner.Visible = isAuto
1002
- if isAuto then
1003
- self.checkboxStroke.Color = Color3.fromRGB(80, 170, 255)
1004
- else
1005
- self.checkboxStroke.Color = Color3.fromRGB(80, 80, 95)
1006
- end
1368
+ self.checkboxInner.Visible = isAuto
1369
+ if isAuto then
1370
+ self.checkboxStroke.Color = Color3.fromRGB(80, 170, 255)
1371
+ else
1372
+ self.checkboxStroke.Color = Color3.fromRGB(80, 80, 95)
1373
+ end
1007
1374
  end
1008
1375
 
1009
1376
  -- Sakelar visibilitas panel GUI
1010
1377
  function UIManager:toggleVisibility()
1011
- if self.widget then
1012
- self.widget.Enabled = not self.widget.Enabled
1013
- end
1378
+ if self.widget then
1379
+ self.widget.Enabled = not self.widget.Enabled
1380
+ end
1014
1381
  end
1015
1382
 
1016
1383
  return UIManager
1017
1384
  ]]></ProtectedString>
1018
- <string name="ScriptGuid">{8B113EA4-CA11-4BDF-BFA7-98B5E077AC2E}</string>
1019
- <BinaryString name="AttributesSerialize"></BinaryString>
1020
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
1021
- <bool name="DefinesCapabilities">false</bool>
1022
- <string name="Name">ui_manager</string>
1023
- <int64 name="SourceAssetId">-1</int64>
1024
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
1025
- </Properties>
1026
- </Item>
1027
- <Item class="ModuleScript" referent="RBX6852D813E05543C487103F791CF924EF">
1028
- <Properties>
1029
- <Content name="LinkedSource"><null></null></Content>
1030
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
1031
- local code = data
1032
- if not code or code == "" then
1033
- return { status = "error", error = "Kode yang dieksekusi tidak boleh kosong." }
1034
- end
1035
-
1036
- local HttpService = game:GetService("HttpService")
1037
-
1038
- -- Bungkus code ke dalam fungsi return
1039
- local wrappedCode = "return function()\n" .. code .. "\nend"
1040
-
1041
- local moduleScript = Instance.new("ModuleScript")
1042
- moduleScript.Name = "AI_Execute_Temp"
1043
- moduleScript.Source = wrappedCode
1044
-
1045
- -- Taruh di ReplicatedStorage sementara agar bisa di-require
1046
- moduleScript.Parent = game:GetService("ReplicatedStorage")
1047
-
1048
- local resultData = nil
1049
- local execError = nil
1050
- local isFinished = false
1051
-
1052
- -- Eksekusi asinkron dengan batas waktu (timeout) 5 detik
1053
- task.spawn(function()
1054
- local reqSuccess, func = pcall(function() return require(moduleScript) end)
1055
- if reqSuccess and type(func) == "function" then
1056
- local execSuccess, res = pcall(func)
1057
- if execSuccess then
1058
- resultData = res
1059
- else
1060
- execError = "Runtime Error: " .. tostring(res)
1061
- end
1062
- else
1063
- execError = "Syntax/Require Error: " .. tostring(func)
1064
- end
1065
- isFinished = true
1066
- end)
1067
-
1068
- -- Tunggu hingga 5 detik
1069
- local timeWaited = 0
1070
- while not isFinished and timeWaited < 5 do
1071
- timeWaited = timeWaited + task.wait()
1072
- end
1073
-
1074
- -- Pembersihan (Wajib dihancurkan)
1075
- pcall(function() moduleScript:Destroy() end)
1076
-
1077
- if not isFinished then
1078
- return { status = "error", error = "Timeout 5 detik terlampaui. Kemungkinan ada infinite loop tanpa task.wait()." }
1079
- end
1080
-
1081
- if execError then
1082
- return { status = "error", error = execError }
1083
- end
1084
-
1085
- -- Cegah return nil error
1086
- if resultData == nil then
1087
- return { status = "success", result = "Eksekusi berhasil (Tidak ada nilai kembalian)." }
1088
- end
1089
-
1090
- -- JSON Encode untuk mencegah raw Instance return crash
1091
- local serializeSuccess, jsonRes = pcall(function()
1092
- return HttpService:JSONEncode(resultData)
1093
- end)
1094
-
1095
- if serializeSuccess then
1096
- return { status = "success", result = HttpService:JSONDecode(jsonRes) } -- decode kembali agar format aslinya diteruskan oleh router
1097
- else
1098
- return { status = "error", error = "Gagal menserialisasi hasil (Dilarang mengembalikan raw Roblox Instance): " .. tostring(jsonRes) }
1099
- end
1100
- end
1101
- ]]></ProtectedString>
1102
- <string name="ScriptGuid">{E656AA51-AC95-4EED-9C73-CA164FE1FA52}</string>
1103
- <BinaryString name="AttributesSerialize"></BinaryString>
1104
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
1105
- <bool name="DefinesCapabilities">false</bool>
1106
- <string name="Name">execute_luau</string>
1107
- <int64 name="SourceAssetId">-1</int64>
1108
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
1109
1385
  </Properties>
1110
1386
  </Item>
1111
- <Item class="ModuleScript" referent="RBX87CCA7F1F7C047D69399148376B5D38E">
1387
+ <Item class="ModuleScript" referent="RBX_update_script_source">
1112
1388
  <Properties>
1113
- <Content name="LinkedSource"><null></null></Content>
1114
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
1115
- -- Guardrail: Cegah penghapusan Service utama
1116
- local protectedServices = {
1117
- "Workspace", "Players", "Lighting", "MaterialService", "NetworkClient",
1118
- "ReplicatedFirst", "ReplicatedStorage", "ServerScriptService", "ServerStorage",
1119
- "StarterGui", "StarterPack", "StarterPlayer", "Teams", "SoundService", "Chat",
1120
- "TextChatService", "VoiceChatService"
1121
- }
1122
-
1123
- if targetInstance.Parent == game or targetInstance.Parent == nil then
1124
- for _, serviceName in ipairs(protectedServices) do
1125
- if targetInstance.Name == serviceName or targetInstance.ClassName == serviceName then
1126
- return { status = "error", error = "GUARDRAIL BLOCKED: Anda tidak diizinkan untuk menghapus layanan tingkat atas (" .. targetInstance.Name .. ")." }
1127
- end
1128
- end
1129
- end
1130
-
1131
- local name = targetInstance.Name
1132
- local className = targetInstance.ClassName
1133
- local parentName = targetInstance.Parent and targetInstance.Parent.Name or "nil"
1134
-
1135
- local success, err = pcall(function()
1136
- targetInstance:Destroy()
1137
- end)
1138
-
1139
- if success then
1140
- return { status = "success", result = "Instance '" .. name .. "' (" .. className .. ") berhasil dihapus dari " .. parentName .. "." }
1141
- else
1142
- return { status = "error", error = "Gagal menghapus instance: " .. tostring(err) }
1143
- end
1144
- end
1145
- ]]></ProtectedString>
1146
- <string name="ScriptGuid">{EBD3665C-2719-4288-930C-3902EFF4CCB6}</string>
1147
1389
  <BinaryString name="AttributesSerialize"></BinaryString>
1148
1390
  <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
1149
1391
  <bool name="DefinesCapabilities">false</bool>
1150
- <string name="Name">delete_instance</string>
1151
- <int64 name="SourceAssetId">-1</int64>
1152
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
1153
- </Properties>
1154
- </Item>
1155
- <Item class="ModuleScript" referent="RBX57ADCE105E57454897B1666DA1D52563">
1156
- <Properties>
1157
- <Content name="LinkedSource"><null></null></Content>
1158
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
1159
- local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
1160
- if not success or not config.ClassName then
1161
- return { status = "error", error = "Format data JSON tidak valid atau ClassName hilang." }
1162
- end
1163
-
1164
- local newSuccess, newInst = pcall(function()
1165
- local inst = Instance.new(config.ClassName)
1166
- inst.Parent = targetInstance
1167
-
1168
- if config.Properties then
1169
- for prop, val in pairs(config.Properties) do
1170
- pcall(function() inst[prop] = val end)
1171
- end
1172
- end
1173
- return inst
1174
- end)
1175
-
1176
- if newSuccess then
1177
- return { status = "success", result = "Instance '"..config.ClassName.."' berhasil dibuat di " .. targetPath }
1178
- else
1179
- return { status = "error", error = "Gagal membuat Instance. Pastikan ClassName valid." }
1180
- end
1181
- end
1182
- ]]></ProtectedString>
1183
- <string name="ScriptGuid">{4F0529C1-A158-4652-B56B-A8B224EF87F5}</string>
1184
- <BinaryString name="AttributesSerialize"></BinaryString>
1185
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
1186
- <bool name="DefinesCapabilities">false</bool>
1187
- <string name="Name">create_instance</string>
1188
- <int64 name="SourceAssetId">-1</int64>
1189
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
1190
- </Properties>
1191
- </Item>
1192
- <Item class="ModuleScript" referent="RBXCA3AFE3B00224D6686C665EF3D5A6DAA">
1193
- <Properties>
1392
+ <bool name="Disabled">false</bool>
1194
1393
  <Content name="LinkedSource"><null></null></Content>
1195
- <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
1196
- local CollectionService = game:GetService("CollectionService")
1197
-
1198
- local props = {
1199
- Name = targetInstance.Name,
1200
- ClassName = targetInstance.ClassName,
1201
- Parent = targetInstance.Parent and targetInstance.Parent.Name or "nil",
1202
- }
1203
-
1204
- -- Ambil Properties Umum secara aman via pcall
1205
- local function tryProp(propName)
1206
- local success, val = pcall(function() return targetInstance[propName] end)
1207
- if success and val ~= nil then
1208
- if typeof(val) == "Vector3" then
1209
- props[propName] = {math.round(val.X*100)/100, math.round(val.Y*100)/100, math.round(val.Z*100)/100}
1210
- elseif typeof(val) == "Color3" then
1211
- props[propName] = {math.round(val.R * 255), math.round(val.G * 255), math.round(val.B * 255)}
1212
- elseif typeof(val) == "CFrame" then
1213
- props[propName] = tostring(val)
1214
- elseif typeof(val) == "EnumItem" then
1215
- props[propName] = val.Name
1216
- elseif typeof(val) == "UDim2" then
1217
- props[propName] = {val.X.Scale, val.X.Offset, val.Y.Scale, val.Y.Offset}
1218
- elseif typeof(val) == "Instance" then
1219
- props[propName] = val:GetFullName()
1220
- else
1221
- props[propName] = val
1222
- end
1223
- end
1224
- end
1225
-
1226
- local commonProps = {
1227
- "Anchored", "CanCollide", "Transparency", "Reflectance",
1228
- "Position", "Size", "Color", "Material", "Text", "TextColor3",
1229
- "BackgroundTransparency", "BackgroundColor3", "Visible", "Enabled",
1230
- "Volume", "Playing", "Looped", "Value"
1231
- }
1232
-
1233
- for _, prop in ipairs(commonProps) do
1234
- tryProp(prop)
1235
- end
1236
-
1237
- -- Attributes & Tags
1238
- pcall(function()
1239
- props.Attributes = targetInstance:GetAttributes()
1240
- end)
1241
- pcall(function()
1242
- props.Tags = CollectionService:GetTags(targetInstance)
1243
- end)
1244
-
1245
- return { status = "success", result = props }
1246
- end
1247
- ]]></ProtectedString>
1248
- <string name="ScriptGuid">{3B18E6FF-1FFC-4B48-936D-8471CB3820C6}</string>
1249
- <BinaryString name="AttributesSerialize"></BinaryString>
1250
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
1251
- <bool name="DefinesCapabilities">false</bool>
1252
- <string name="Name">get_properties</string>
1394
+ <string name="Name">update_script_source</string>
1253
1395
  <int64 name="SourceAssetId">-1</int64>
1254
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
1255
- </Properties>
1256
- </Item>
1257
- <Item class="ModuleScript" referent="RBXD0E8AE88B6B24ECE9F916D7EFC8F978E">
1258
- <Properties>
1259
- <Content name="LinkedSource"><null></null></Content>
1396
+ <SharedString name="Tags"></SharedString>
1260
1397
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
1261
- local query = tostring(data)
1262
- if not query or query == "" then
1263
- return { status = "error", error = "Query pencarian tidak boleh kosong." }
1264
- end
1265
-
1266
- local matches = {}
1267
- local matchCount = 0
1268
- local MAX_MATCHES = 50
1269
-
1270
- local searchableServices = {
1271
- game:GetService("Workspace"),
1272
- game:GetService("ServerScriptService"),
1273
- game:GetService("ReplicatedStorage"),
1274
- game:GetService("StarterPlayer"),
1275
- game:GetService("StarterGui")
1276
- }
1277
-
1278
- local function searchInScript(scriptObj)
1279
- local success, source = pcall(function() return scriptObj.Source end)
1280
- if not success or not source then return end
1281
-
1282
- local lines = string.split(source, "\n")
1283
- for i, line in ipairs(lines) do
1284
- -- Coba cari dengan plain text dulu, jika gagal coba pattern
1285
- local found = string.find(line, query, 1, true)
1286
- if not found then
1287
- pcall(function() found = string.find(line, query) end)
1288
- end
1289
-
1290
- if found then
1291
- table.insert(matches, {
1292
- script = scriptObj:GetFullName(),
1293
- line = i,
1294
- content = string.match(line, "^%s*(.-)%s*$") or line
1295
- })
1296
- matchCount = matchCount + 1
1297
- if matchCount >= MAX_MATCHES then
1298
- return true
1299
- end
1300
- end
1301
- end
1302
- return false
1303
- end
1304
-
1305
- for _, service in ipairs(searchableServices) do
1306
- pcall(function()
1307
- local descendants = service:GetDescendants()
1308
- for _, desc in ipairs(descendants) do
1309
- if desc:IsA("LuaSourceContainer") then
1310
- if searchInScript(desc) then
1311
- break
1312
- end
1313
- end
1314
- end
1315
- end)
1316
- if matchCount >= MAX_MATCHES then
1317
- break
1318
- end
1319
- end
1320
-
1321
- return { status = "success", matches = matches, count = matchCount, limitReached = (matchCount >= MAX_MATCHES) }
1398
+ if targetInstance:IsA("LuaSourceContainer") then
1399
+ local newCode = data or ""
1400
+
1401
+ -- Catatan: Validasi AST menggunakan loadstring() sengaja DIHAPUS.
1402
+ -- Karena secara default Roblox Studio mematikan LoadStringEnabled, menggunakannya
1403
+ -- hanya akan membuat AI gagal terus menerus untuk menulis script.
1404
+ -- Error syntax akan ditangkap secara native oleh Roblox Output.
1405
+
1406
+ ctx.scriptHistory[targetPath] = targetInstance.Source
1407
+ targetInstance.Source = newCode
1408
+ return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil diperbarui." }
1409
+ else
1410
+ return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
1411
+ end
1322
1412
  end
1323
1413
  ]]></ProtectedString>
1324
- <string name="ScriptGuid">{74D52488-8890-4D93-B5D6-37BBE9F0DF1E}</string>
1325
- <BinaryString name="AttributesSerialize"></BinaryString>
1326
- <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
1327
- <bool name="DefinesCapabilities">false</bool>
1328
- <string name="Name">script_grep</string>
1329
- <int64 name="SourceAssetId">-1</int64>
1330
- <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
1331
1414
  </Properties>
1332
1415
  </Item>
1333
1416
  </Item>
1334
1417
  <SharedStrings>
1335
1418
  <SharedString md5="yuZpQdnvvUBOTYh1jqZ2cA=="></SharedString>
1336
1419
  </SharedStrings>
1337
- </roblox>
1420
+ </roblox>