@adiholick/nvstudio-mcp 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1337 @@
1
+ <roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
2
+ <External>null</External>
3
+ <External>nil</External>
4
+ <Item class="Folder" referent="RBX0497167F99814CA6A2096BB729707905">
5
+ <Properties>
6
+ <BinaryString name="AttributesSerialize"></BinaryString>
7
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
8
+ <bool name="DefinesCapabilities">false</bool>
9
+ <string name="Name">nvstudio_mcp</string>
10
+ <int64 name="SourceAssetId">-1</int64>
11
+ <SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
12
+ </Properties>
13
+ <Item class="Script" referent="RBX749E4D5306E74B7C8E355593066F1D2B">
14
+ <Properties>
15
+ <ProtectedString name="Source"><![CDATA[local HttpService = game:GetService("HttpService")
16
+ local LogService = game:GetService("LogService")
17
+
18
+ local TASK_URL = "http://localhost:3055/api/tasks"
19
+ local RESPONSE_URL = "http://localhost:3055/api/response"
20
+
21
+ -- Generate Unique Studio ID (menghindari bentrok jika 2 Studio terbuka)
22
+ local STUDIO_ID = HttpService:GenerateGUID(false)
23
+
24
+ -- Import UI Manager (Procedural Dashboard)
25
+ local uiModule = script.Parent:FindFirstChild("ui_manager")
26
+ local ui = nil
27
+
28
+ -- Membangun Tombol Toolbar (Toggle)
29
+ local toolbar = plugin:CreateToolbar("NVStudio MCP")
30
+ local toggleBtn = toolbar:CreateButton("Dashboard", "Buka Dashboard NVStudio MCP", "rbxassetid://82696893597722")
31
+
32
+ -- Global State & Context AI
33
+ local ctx = {
34
+ scriptHistory = {},
35
+ recentLogs = {},
36
+ HttpService = HttpService,
37
+ studioId = STUDIO_ID
38
+ }
39
+ local MAX_LOGS = 50
40
+ local isConnected = false
41
+ local autoConnect = plugin:GetSetting("AutoConnect") or false
42
+ local isPolling = false
43
+ local isClickDebounce = false -- Mencegah rapid double-click
44
+
45
+ -- Deklarasi fungsi polling di awal agar bisa dipanggil oleh UI
46
+ local startPolling
47
+ local stopPolling
48
+
49
+ -- Inisialisasi UI jika modul ditemukan
50
+ 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)
94
+ 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)
99
+ end
100
+
101
+ -- Handler Output Studio (Untuk AI dan GUI)
102
+ 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
125
+ end
126
+
127
+ -- Ikat listener output
128
+ LogService.MessageOut:Connect(addSystemLog)
129
+
130
+ -- Memuat semua module skills secara dinamis
131
+ local skills = {}
132
+ 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
136
+ end
137
+
138
+ -- Fungsi Helper Pathing
139
+ 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
151
+ end
152
+
153
+ -- Memproses Task Eksekusi dari AI
154
+ 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
190
+ end
191
+
192
+ -- Menghentikan polling dengan bersih (dipanggil oleh tombol Disconnect)
193
+ local isManualDisconnect = false -- Mencegah auto-reconnect saat user sengaja disconnect
194
+
195
+ function stopPolling()
196
+ isConnected = false
197
+ isManualDisconnect = true
198
+ -- isPolling akan menjadi false secara otomatis saat loop keluar
199
+ end
200
+
201
+ -- Background Polling Loop (Dapat dihentikan oleh user)
202
+ 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)
251
+ end
252
+
253
+ -- ══════════════════════════════════════════════════════════════════════
254
+ -- AUTO-DETECT: Background heartbeat yang SELALU berjalan.
255
+ -- Mendeteksi apakah server MCP aktif setiap 2 detik.
256
+ -- Jika terdeteksi aktif → otomatis connect (tanpa perlu tekan tombol).
257
+ -- Jika terdeteksi mati → otomatis update status.
258
+ -- ══════════════════════════════════════════════════════════════════════
259
+ 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
305
+ end)
306
+ ]]></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
+ </Properties>
318
+ </Item>
319
+ <Item class="ModuleScript" referent="RBXF88A8EA0FA1B449280110AA8E425E2C9">
320
+ <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
+ <BinaryString name="AttributesSerialize"></BinaryString>
349
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
350
+ <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>
358
+ <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
381
+ end
382
+ ]]></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
+ </Properties>
391
+ </Item>
392
+ <Item class="ModuleScript" referent="RBX020FB330A8554FAFA0499639D23AB46D">
393
+ <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
+ <BinaryString name="AttributesSerialize"></BinaryString>
405
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
406
+ <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>
414
+ <Content name="LinkedSource"><null></null></Content>
415
+ <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
421
+ end
422
+ ]]></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
+ </Properties>
431
+ </Item>
432
+ <Item class="ModuleScript" referent="RBXE5B71E01ACBE4AD0B6DC072381E6F57B">
433
+ <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
+ <BinaryString name="AttributesSerialize"></BinaryString>
445
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
446
+ <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>
454
+ <Content name="LinkedSource"><null></null></Content>
455
+ <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
472
+ end
473
+ ]]></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
+ </Properties>
482
+ </Item>
483
+ <Item class="ModuleScript" referent="RBXB2C4DDC5A9A84FC5B6318F52E35AF483">
484
+ <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
+ <BinaryString name="AttributesSerialize"></BinaryString>
502
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
503
+ <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>
511
+ <Content name="LinkedSource"><null></null></Content>
512
+ <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
531
+ end
532
+ ]]></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
+ </Properties>
541
+ </Item>
542
+ <Item class="ModuleScript" referent="RBX543373412A5740FCB6655A9B57FBE9D7">
543
+ <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
+ <BinaryString name="AttributesSerialize"></BinaryString>
564
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
565
+ <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>
573
+ <Content name="LinkedSource"><null></null></Content>
574
+ <ProtectedString name="Source"><![CDATA[local UIManager = {}
575
+ local widget = nil
576
+
577
+ 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
928
+ end
929
+
930
+ -- ═══════════════════════════════════════════
931
+ -- Menambahkan baris log baru ke terminal GUI
932
+ -- ═══════════════════════════════════════════
933
+ 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)
970
+ end
971
+
972
+ -- ═══════════════════════════════════════════
973
+ -- 3 State Setter (Disconnected, Waiting, Connected)
974
+ -- ═══════════════════════════════════════════
975
+ 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
997
+ end
998
+
999
+ -- Mengubah visual custom checkbox
1000
+ 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
1007
+ end
1008
+
1009
+ -- Sakelar visibilitas panel GUI
1010
+ function UIManager:toggleVisibility()
1011
+ if self.widget then
1012
+ self.widget.Enabled = not self.widget.Enabled
1013
+ end
1014
+ end
1015
+
1016
+ return UIManager
1017
+ ]]></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
+ </Properties>
1110
+ </Item>
1111
+ <Item class="ModuleScript" referent="RBX87CCA7F1F7C047D69399148376B5D38E">
1112
+ <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
+ <BinaryString name="AttributesSerialize"></BinaryString>
1148
+ <SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
1149
+ <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>
1194
+ <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>
1253
+ <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>
1260
+ <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) }
1322
+ end
1323
+ ]]></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
+ </Properties>
1332
+ </Item>
1333
+ </Item>
1334
+ <SharedStrings>
1335
+ <SharedString md5="yuZpQdnvvUBOTYh1jqZ2cA=="></SharedString>
1336
+ </SharedStrings>
1337
+ </roblox>