rbxstudio-mcp 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -11
- package/dist/index.js +150 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/index.d.ts +43 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +124 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/studio-plugin/plugin.luau +730 -52
|
@@ -17,6 +17,30 @@ end)
|
|
|
17
17
|
local outputBuffer = {}
|
|
18
18
|
local MAX_OUTPUT_BUFFER = 1000
|
|
19
19
|
|
|
20
|
+
-- Track action history for enhanced undo/redo
|
|
21
|
+
local actionHistory = {} -- Stack of actions that can be undone
|
|
22
|
+
local redoHistory = {} -- Stack of actions that can be redone
|
|
23
|
+
local MAX_ACTION_HISTORY = 100
|
|
24
|
+
|
|
25
|
+
-- Helper to log an action for undo tracking
|
|
26
|
+
local function logAction(actionType, target, summary, details)
|
|
27
|
+
-- Clear redo history when a new action is performed
|
|
28
|
+
redoHistory = {}
|
|
29
|
+
|
|
30
|
+
table.insert(actionHistory, {
|
|
31
|
+
action = actionType,
|
|
32
|
+
target = target,
|
|
33
|
+
summary = summary,
|
|
34
|
+
details = details or {},
|
|
35
|
+
timestamp = os.time()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
-- Keep history from growing too large
|
|
39
|
+
if #actionHistory > MAX_ACTION_HISTORY then
|
|
40
|
+
table.remove(actionHistory, 1)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
20
44
|
-- Connect to LogService to capture output
|
|
21
45
|
LogService.MessageOut:Connect(function(message, messageType)
|
|
22
46
|
table.insert(outputBuffer, {
|
|
@@ -55,7 +79,7 @@ local screenGui = plugin:CreateDockWidgetPluginGuiAsync(
|
|
|
55
79
|
"MCPServerInterface",
|
|
56
80
|
DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, false, 400, 500, 350, 450)
|
|
57
81
|
)
|
|
58
|
-
screenGui.Title = "MCP Server v1.
|
|
82
|
+
screenGui.Title = "MCP Server v1.12.0"
|
|
59
83
|
|
|
60
84
|
local mainFrame = Instance.new("Frame")
|
|
61
85
|
mainFrame.Size = UDim2.new(1, 0, 1, 0)
|
|
@@ -108,7 +132,7 @@ local versionLabel = Instance.new("TextLabel")
|
|
|
108
132
|
versionLabel.Size = UDim2.new(1, 0, 0, 16)
|
|
109
133
|
versionLabel.Position = UDim2.new(0, 0, 0, 32)
|
|
110
134
|
versionLabel.BackgroundTransparency = 1
|
|
111
|
-
versionLabel.Text = "AI Integration • v1.
|
|
135
|
+
versionLabel.Text = "AI Integration • v1.12.0"
|
|
112
136
|
versionLabel.TextColor3 = Color3.fromRGB(191, 219, 254)
|
|
113
137
|
versionLabel.TextScaled = false
|
|
114
138
|
versionLabel.TextSize = 12
|
|
@@ -739,50 +763,11 @@ local mutationEndpoints = {
|
|
|
739
763
|
["/api/remove-tag"] = "Remove Tag",
|
|
740
764
|
["/api/clone-instance"] = "Clone Instance",
|
|
741
765
|
["/api/move-instance"] = "Move Instance",
|
|
766
|
+
["/api/insert-asset"] = "Insert Asset",
|
|
742
767
|
}
|
|
743
768
|
|
|
744
|
-
-- Endpoint to handler mapping
|
|
745
|
-
local endpointHandlers
|
|
746
|
-
["/api/file-tree"] = handlers.getFileTree,
|
|
747
|
-
["/api/search-files"] = handlers.searchFiles,
|
|
748
|
-
["/api/place-info"] = handlers.getPlaceInfo,
|
|
749
|
-
["/api/services"] = handlers.getServices,
|
|
750
|
-
["/api/search-objects"] = handlers.searchObjects,
|
|
751
|
-
["/api/instance-properties"] = handlers.getInstanceProperties,
|
|
752
|
-
["/api/instance-children"] = handlers.getInstanceChildren,
|
|
753
|
-
["/api/search-by-property"] = handlers.searchByProperty,
|
|
754
|
-
["/api/class-info"] = handlers.getClassInfo,
|
|
755
|
-
["/api/project-structure"] = handlers.getProjectStructure,
|
|
756
|
-
["/api/set-property"] = handlers.setProperty,
|
|
757
|
-
["/api/mass-set-property"] = handlers.massSetProperty,
|
|
758
|
-
["/api/mass-get-property"] = handlers.massGetProperty,
|
|
759
|
-
["/api/create-object"] = handlers.createObject,
|
|
760
|
-
["/api/mass-create-objects"] = handlers.massCreateObjects,
|
|
761
|
-
["/api/mass-create-objects-with-properties"] = handlers.massCreateObjectsWithProperties,
|
|
762
|
-
["/api/delete-object"] = handlers.deleteObject,
|
|
763
|
-
["/api/smart-duplicate"] = handlers.smartDuplicate,
|
|
764
|
-
["/api/mass-duplicate"] = handlers.massDuplicate,
|
|
765
|
-
["/api/set-calculated-property"] = handlers.setCalculatedProperty,
|
|
766
|
-
["/api/set-relative-property"] = handlers.setRelativeProperty,
|
|
767
|
-
["/api/get-script-source"] = handlers.getScriptSource,
|
|
768
|
-
["/api/set-script-source"] = handlers.setScriptSource,
|
|
769
|
-
["/api/edit-script-lines"] = handlers.editScriptLines,
|
|
770
|
-
["/api/insert-script-lines"] = handlers.insertScriptLines,
|
|
771
|
-
["/api/delete-script-lines"] = handlers.deleteScriptLines,
|
|
772
|
-
["/api/get-attribute"] = handlers.getAttribute,
|
|
773
|
-
["/api/set-attribute"] = handlers.setAttribute,
|
|
774
|
-
["/api/get-attributes"] = handlers.getAttributes,
|
|
775
|
-
["/api/delete-attribute"] = handlers.deleteAttribute,
|
|
776
|
-
["/api/get-tags"] = handlers.getTags,
|
|
777
|
-
["/api/add-tag"] = handlers.addTag,
|
|
778
|
-
["/api/remove-tag"] = handlers.removeTag,
|
|
779
|
-
["/api/get-tagged"] = handlers.getTagged,
|
|
780
|
-
["/api/get-selection"] = handlers.getSelection,
|
|
781
|
-
["/api/get-output"] = handlers.getOutput,
|
|
782
|
-
["/api/clone-instance"] = handlers.cloneInstance,
|
|
783
|
-
["/api/move-instance"] = handlers.moveInstance,
|
|
784
|
-
["/api/validate-script"] = handlers.validateScript,
|
|
785
|
-
}
|
|
769
|
+
-- Endpoint to handler mapping (populated after handlers are defined below)
|
|
770
|
+
local endpointHandlers
|
|
786
771
|
|
|
787
772
|
processRequest = function(request)
|
|
788
773
|
local endpoint = request.endpoint
|
|
@@ -1504,6 +1489,11 @@ handlers.setProperty = function(requestData)
|
|
|
1504
1489
|
end)
|
|
1505
1490
|
|
|
1506
1491
|
if success and result ~= false then
|
|
1492
|
+
-- Log for undo tracking
|
|
1493
|
+
logAction("set_property", instancePath, propertyName .. " = " .. tostring(propertyValue), {
|
|
1494
|
+
propertyName = propertyName,
|
|
1495
|
+
newValue = propertyValue
|
|
1496
|
+
})
|
|
1507
1497
|
return {
|
|
1508
1498
|
success = true,
|
|
1509
1499
|
instancePath = instancePath,
|
|
@@ -1554,11 +1544,17 @@ handlers.createObject = function(requestData)
|
|
|
1554
1544
|
end)
|
|
1555
1545
|
|
|
1556
1546
|
if success and newInstance then
|
|
1547
|
+
local newPath = getInstancePath(newInstance)
|
|
1548
|
+
-- Log for undo tracking
|
|
1549
|
+
logAction("create_object", newPath, "Created " .. className .. " '" .. newInstance.Name .. "'", {
|
|
1550
|
+
className = className,
|
|
1551
|
+
parent = parentPath
|
|
1552
|
+
})
|
|
1557
1553
|
return {
|
|
1558
1554
|
success = true,
|
|
1559
1555
|
className = className,
|
|
1560
1556
|
parent = parentPath,
|
|
1561
|
-
instancePath =
|
|
1557
|
+
instancePath = newPath,
|
|
1562
1558
|
name = newInstance.Name,
|
|
1563
1559
|
message = "Object created successfully",
|
|
1564
1560
|
}
|
|
@@ -1587,15 +1583,21 @@ handlers.deleteObject = function(requestData)
|
|
|
1587
1583
|
return { error = "Cannot delete the game instance" }
|
|
1588
1584
|
end
|
|
1589
1585
|
|
|
1586
|
+
local name = instance.Name
|
|
1587
|
+
local className = instance.ClassName
|
|
1588
|
+
|
|
1590
1589
|
local success, result = pcall(function()
|
|
1591
|
-
local name = instance.Name
|
|
1592
|
-
local className = instance.ClassName
|
|
1593
1590
|
instance:Destroy()
|
|
1594
1591
|
ChangeHistoryService:SetWaypoint("Delete " .. className .. " (" .. name .. ")")
|
|
1595
1592
|
return true
|
|
1596
1593
|
end)
|
|
1597
1594
|
|
|
1598
1595
|
if success then
|
|
1596
|
+
-- Log for undo tracking
|
|
1597
|
+
logAction("delete_object", instancePath, "Deleted " .. className .. " '" .. name .. "'", {
|
|
1598
|
+
className = className,
|
|
1599
|
+
name = name
|
|
1600
|
+
})
|
|
1599
1601
|
return {
|
|
1600
1602
|
success = true,
|
|
1601
1603
|
instancePath = instancePath,
|
|
@@ -2498,6 +2500,10 @@ handlers.setScriptSource = function(requestData)
|
|
|
2498
2500
|
end)
|
|
2499
2501
|
|
|
2500
2502
|
if directSuccess then
|
|
2503
|
+
-- Log for undo tracking
|
|
2504
|
+
logAction("set_script_source", instancePath, "Script source replaced (" .. directResult.newSourceLength .. " chars)", {
|
|
2505
|
+
method = "direct"
|
|
2506
|
+
})
|
|
2501
2507
|
return directResult
|
|
2502
2508
|
end
|
|
2503
2509
|
|
|
@@ -2533,6 +2539,10 @@ handlers.setScriptSource = function(requestData)
|
|
|
2533
2539
|
end)
|
|
2534
2540
|
|
|
2535
2541
|
if replaceSuccess then
|
|
2542
|
+
-- Log for undo tracking
|
|
2543
|
+
logAction("set_script_source", replaceResult.instancePath, "Script replaced entirely", {
|
|
2544
|
+
method = "replace"
|
|
2545
|
+
})
|
|
2536
2546
|
return replaceResult
|
|
2537
2547
|
else
|
|
2538
2548
|
return {
|
|
@@ -2775,6 +2785,509 @@ handlers.deleteScriptLines = function(requestData)
|
|
|
2775
2785
|
end
|
|
2776
2786
|
end
|
|
2777
2787
|
|
|
2788
|
+
-- ============================================
|
|
2789
|
+
-- CLAUDE CODE-STYLE SCRIPT EDITING TOOLS
|
|
2790
|
+
-- ============================================
|
|
2791
|
+
|
|
2792
|
+
-- Helper: Count occurrences of a substring
|
|
2793
|
+
local function countOccurrences(source, searchStr)
|
|
2794
|
+
local count = 0
|
|
2795
|
+
local start = 1
|
|
2796
|
+
while true do
|
|
2797
|
+
local pos = string.find(source, searchStr, start, true) -- plain search
|
|
2798
|
+
if pos then
|
|
2799
|
+
count = count + 1
|
|
2800
|
+
start = pos + 1
|
|
2801
|
+
else
|
|
2802
|
+
break
|
|
2803
|
+
end
|
|
2804
|
+
end
|
|
2805
|
+
return count
|
|
2806
|
+
end
|
|
2807
|
+
|
|
2808
|
+
-- Helper: Simple Lua syntax validation (checks for balanced blocks)
|
|
2809
|
+
local function validateLuaSyntax(source)
|
|
2810
|
+
local errors = {}
|
|
2811
|
+
|
|
2812
|
+
-- Check for balanced keywords
|
|
2813
|
+
local blockStarters = { "function", "if", "for", "while", "do", "repeat" }
|
|
2814
|
+
local openBlocks = 0
|
|
2815
|
+
local repeatBlocks = 0
|
|
2816
|
+
|
|
2817
|
+
-- Simple pattern-based checking for common errors
|
|
2818
|
+
for line in string.gmatch(source .. "\n", "([^\n]*)\n") do
|
|
2819
|
+
-- Count block starters
|
|
2820
|
+
if string.match(line, "^%s*function%s") or string.match(line, "=%s*function%s*%(") or string.match(line, "^%s*local%s+function%s") then
|
|
2821
|
+
openBlocks = openBlocks + 1
|
|
2822
|
+
end
|
|
2823
|
+
if string.match(line, "^%s*if%s") and not string.match(line, "%sthen%s+.+%send%s*$") then
|
|
2824
|
+
openBlocks = openBlocks + 1
|
|
2825
|
+
end
|
|
2826
|
+
if string.match(line, "^%s*for%s") and string.match(line, "%sdo%s*$") then
|
|
2827
|
+
openBlocks = openBlocks + 1
|
|
2828
|
+
end
|
|
2829
|
+
if string.match(line, "^%s*while%s") and string.match(line, "%sdo%s*$") then
|
|
2830
|
+
openBlocks = openBlocks + 1
|
|
2831
|
+
end
|
|
2832
|
+
if string.match(line, "^%s*repeat%s*$") then
|
|
2833
|
+
repeatBlocks = repeatBlocks + 1
|
|
2834
|
+
end
|
|
2835
|
+
|
|
2836
|
+
-- Count block enders
|
|
2837
|
+
if string.match(line, "^%s*end%s*$") or string.match(line, "^%s*end[%)%],;%s]") or string.match(line, "%send%s*$") then
|
|
2838
|
+
if not string.match(line, "^%s*%-%-") then -- Not a comment
|
|
2839
|
+
openBlocks = openBlocks - 1
|
|
2840
|
+
end
|
|
2841
|
+
end
|
|
2842
|
+
if string.match(line, "^%s*until%s") then
|
|
2843
|
+
repeatBlocks = repeatBlocks - 1
|
|
2844
|
+
end
|
|
2845
|
+
end
|
|
2846
|
+
|
|
2847
|
+
if openBlocks > 0 then
|
|
2848
|
+
table.insert(errors, "Missing " .. openBlocks .. " 'end' statement(s) - check your function/if/for/while blocks")
|
|
2849
|
+
elseif openBlocks < 0 then
|
|
2850
|
+
table.insert(errors, "Extra " .. math.abs(openBlocks) .. " 'end' statement(s) - you have more 'end' keywords than block openers")
|
|
2851
|
+
end
|
|
2852
|
+
|
|
2853
|
+
if repeatBlocks > 0 then
|
|
2854
|
+
table.insert(errors, "Missing " .. repeatBlocks .. " 'until' statement(s) for repeat blocks")
|
|
2855
|
+
elseif repeatBlocks < 0 then
|
|
2856
|
+
table.insert(errors, "Extra " .. math.abs(repeatBlocks) .. " 'until' statement(s)")
|
|
2857
|
+
end
|
|
2858
|
+
|
|
2859
|
+
-- Check for unclosed strings (simple check)
|
|
2860
|
+
local inString = false
|
|
2861
|
+
local stringChar = nil
|
|
2862
|
+
local lineNum = 0
|
|
2863
|
+
for line in string.gmatch(source .. "\n", "([^\n]*)\n") do
|
|
2864
|
+
lineNum = lineNum + 1
|
|
2865
|
+
-- Skip long strings and comments for now
|
|
2866
|
+
if not string.match(line, "%[%[") and not string.match(line, "%-%-") then
|
|
2867
|
+
for i = 1, #line do
|
|
2868
|
+
local char = string.sub(line, i, i)
|
|
2869
|
+
if not inString then
|
|
2870
|
+
if char == '"' or char == "'" then
|
|
2871
|
+
inString = true
|
|
2872
|
+
stringChar = char
|
|
2873
|
+
end
|
|
2874
|
+
else
|
|
2875
|
+
if char == stringChar and string.sub(line, i-1, i-1) ~= "\\" then
|
|
2876
|
+
inString = false
|
|
2877
|
+
stringChar = nil
|
|
2878
|
+
end
|
|
2879
|
+
end
|
|
2880
|
+
end
|
|
2881
|
+
end
|
|
2882
|
+
end
|
|
2883
|
+
|
|
2884
|
+
return #errors == 0, errors
|
|
2885
|
+
end
|
|
2886
|
+
|
|
2887
|
+
-- edit_script: String-based editing like Claude Code's Edit tool
|
|
2888
|
+
handlers.editScript = function(requestData)
|
|
2889
|
+
local instancePath = requestData.instancePath
|
|
2890
|
+
local oldString = requestData.oldString
|
|
2891
|
+
local newString = requestData.newString
|
|
2892
|
+
local replaceAll = requestData.replaceAll or false
|
|
2893
|
+
local validateAfter = requestData.validateAfter
|
|
2894
|
+
if validateAfter == nil then validateAfter = true end
|
|
2895
|
+
|
|
2896
|
+
if not instancePath then
|
|
2897
|
+
return { error = "Instance path is required" }
|
|
2898
|
+
end
|
|
2899
|
+
if not oldString or oldString == "" then
|
|
2900
|
+
return { error = "old_string is required and cannot be empty" }
|
|
2901
|
+
end
|
|
2902
|
+
if newString == nil then
|
|
2903
|
+
return { error = "new_string is required" }
|
|
2904
|
+
end
|
|
2905
|
+
if oldString == newString then
|
|
2906
|
+
return { error = "old_string and new_string must be different" }
|
|
2907
|
+
end
|
|
2908
|
+
|
|
2909
|
+
local instance = getInstanceByPath(instancePath)
|
|
2910
|
+
if not instance then
|
|
2911
|
+
return { error = "Instance not found: " .. instancePath }
|
|
2912
|
+
end
|
|
2913
|
+
|
|
2914
|
+
if not instance:IsA("LuaSourceContainer") then
|
|
2915
|
+
return { error = "Instance is not a script: " .. instance.ClassName }
|
|
2916
|
+
end
|
|
2917
|
+
|
|
2918
|
+
local success, result = pcall(function()
|
|
2919
|
+
local source = instance.Source
|
|
2920
|
+
|
|
2921
|
+
-- Normalize escape sequences that may have been double-escaped
|
|
2922
|
+
local searchStr = oldString:gsub("\\n", "\n"):gsub("\\t", "\t"):gsub("\\r", "\r"):gsub("\\\\", "\\")
|
|
2923
|
+
local replaceStr = newString:gsub("\\n", "\n"):gsub("\\t", "\t"):gsub("\\r", "\r"):gsub("\\\\", "\\")
|
|
2924
|
+
|
|
2925
|
+
-- Count occurrences
|
|
2926
|
+
local occurrences = countOccurrences(source, searchStr)
|
|
2927
|
+
|
|
2928
|
+
if occurrences == 0 then
|
|
2929
|
+
return {
|
|
2930
|
+
success = false,
|
|
2931
|
+
error = "old_string not found in script. Make sure the text matches exactly, including whitespace and indentation.",
|
|
2932
|
+
hint = "Tip: Use get_script_source first to see the exact content, then copy the text precisely."
|
|
2933
|
+
}
|
|
2934
|
+
end
|
|
2935
|
+
|
|
2936
|
+
if occurrences > 1 and not replaceAll then
|
|
2937
|
+
return {
|
|
2938
|
+
success = false,
|
|
2939
|
+
error = "old_string appears " .. occurrences .. " times in the script. Either provide more context to make it unique, or set replace_all=true to replace all occurrences.",
|
|
2940
|
+
occurrences = occurrences
|
|
2941
|
+
}
|
|
2942
|
+
end
|
|
2943
|
+
|
|
2944
|
+
-- Perform the replacement
|
|
2945
|
+
local newSource
|
|
2946
|
+
if replaceAll then
|
|
2947
|
+
newSource = string.gsub(source, searchStr:gsub("([^%w])", "%%%1"), replaceStr) -- Escape pattern chars
|
|
2948
|
+
else
|
|
2949
|
+
-- Replace just the first occurrence
|
|
2950
|
+
local pos = string.find(source, searchStr, 1, true)
|
|
2951
|
+
newSource = string.sub(source, 1, pos - 1) .. replaceStr .. string.sub(source, pos + #searchStr)
|
|
2952
|
+
end
|
|
2953
|
+
|
|
2954
|
+
-- Validate syntax if requested
|
|
2955
|
+
if validateAfter then
|
|
2956
|
+
local isValid, syntaxErrors = validateLuaSyntax(newSource)
|
|
2957
|
+
if not isValid then
|
|
2958
|
+
return {
|
|
2959
|
+
success = false,
|
|
2960
|
+
error = "Edit would create invalid Lua syntax",
|
|
2961
|
+
syntaxErrors = syntaxErrors,
|
|
2962
|
+
hint = "The edit was NOT applied. Fix the syntax issues in new_string and try again."
|
|
2963
|
+
}
|
|
2964
|
+
end
|
|
2965
|
+
end
|
|
2966
|
+
|
|
2967
|
+
-- Apply the change
|
|
2968
|
+
ScriptEditorService:UpdateSourceAsync(instance, function(oldContent)
|
|
2969
|
+
return newSource
|
|
2970
|
+
end)
|
|
2971
|
+
|
|
2972
|
+
ChangeHistoryService:SetWaypoint("Edit script: " .. instance.Name)
|
|
2973
|
+
|
|
2974
|
+
-- Prepare summary for logging
|
|
2975
|
+
local shortOld = #searchStr > 30 and (string.sub(searchStr, 1, 30) .. "...") or searchStr
|
|
2976
|
+
local shortNew = #replaceStr > 30 and (string.sub(replaceStr, 1, 30) .. "...") or replaceStr
|
|
2977
|
+
|
|
2978
|
+
return {
|
|
2979
|
+
success = true,
|
|
2980
|
+
instancePath = instancePath,
|
|
2981
|
+
replacements = replaceAll and occurrences or 1,
|
|
2982
|
+
oldLength = #source,
|
|
2983
|
+
newLength = #newSource,
|
|
2984
|
+
validated = validateAfter,
|
|
2985
|
+
message = replaceAll
|
|
2986
|
+
and ("Replaced " .. occurrences .. " occurrence(s) successfully")
|
|
2987
|
+
or "Edit applied successfully",
|
|
2988
|
+
_logSummary = shortOld:gsub("\n", "\\n") .. " → " .. shortNew:gsub("\n", "\\n")
|
|
2989
|
+
}
|
|
2990
|
+
end)
|
|
2991
|
+
|
|
2992
|
+
if success then
|
|
2993
|
+
if result.success then
|
|
2994
|
+
-- Log for undo tracking
|
|
2995
|
+
logAction("edit_script", instancePath, result._logSummary or "Script edited", {
|
|
2996
|
+
replacements = result.replacements
|
|
2997
|
+
})
|
|
2998
|
+
result._logSummary = nil -- Remove internal field
|
|
2999
|
+
end
|
|
3000
|
+
return result
|
|
3001
|
+
else
|
|
3002
|
+
return { error = "Failed to edit script: " .. tostring(result) }
|
|
3003
|
+
end
|
|
3004
|
+
end
|
|
3005
|
+
|
|
3006
|
+
-- search_script: Search for patterns within a script (like grep)
|
|
3007
|
+
handlers.searchScript = function(requestData)
|
|
3008
|
+
local instancePath = requestData.instancePath
|
|
3009
|
+
local pattern = requestData.pattern
|
|
3010
|
+
local useRegex = requestData.useRegex or false
|
|
3011
|
+
local contextLines = requestData.contextLines or 0
|
|
3012
|
+
|
|
3013
|
+
if not instancePath or not pattern then
|
|
3014
|
+
return { error = "Instance path and pattern are required" }
|
|
3015
|
+
end
|
|
3016
|
+
|
|
3017
|
+
local instance = getInstanceByPath(instancePath)
|
|
3018
|
+
if not instance then
|
|
3019
|
+
return { error = "Instance not found: " .. instancePath }
|
|
3020
|
+
end
|
|
3021
|
+
|
|
3022
|
+
if not instance:IsA("LuaSourceContainer") then
|
|
3023
|
+
return { error = "Instance is not a script: " .. instance.ClassName }
|
|
3024
|
+
end
|
|
3025
|
+
|
|
3026
|
+
local success, result = pcall(function()
|
|
3027
|
+
local source = instance.Source
|
|
3028
|
+
local lines, _ = splitLines(source)
|
|
3029
|
+
local matches = {}
|
|
3030
|
+
|
|
3031
|
+
for lineNum, line in ipairs(lines) do
|
|
3032
|
+
local found = false
|
|
3033
|
+
if useRegex then
|
|
3034
|
+
found = string.match(line, pattern) ~= nil
|
|
3035
|
+
else
|
|
3036
|
+
found = string.find(line, pattern, 1, true) ~= nil
|
|
3037
|
+
end
|
|
3038
|
+
|
|
3039
|
+
if found then
|
|
3040
|
+
local match = {
|
|
3041
|
+
lineNumber = lineNum,
|
|
3042
|
+
content = line,
|
|
3043
|
+
context = {}
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
-- Add context lines
|
|
3047
|
+
if contextLines > 0 then
|
|
3048
|
+
for i = math.max(1, lineNum - contextLines), lineNum - 1 do
|
|
3049
|
+
table.insert(match.context, { lineNumber = i, content = lines[i], type = "before" })
|
|
3050
|
+
end
|
|
3051
|
+
for i = lineNum + 1, math.min(#lines, lineNum + contextLines) do
|
|
3052
|
+
table.insert(match.context, { lineNumber = i, content = lines[i], type = "after" })
|
|
3053
|
+
end
|
|
3054
|
+
end
|
|
3055
|
+
|
|
3056
|
+
table.insert(matches, match)
|
|
3057
|
+
end
|
|
3058
|
+
end
|
|
3059
|
+
|
|
3060
|
+
return {
|
|
3061
|
+
instancePath = instancePath,
|
|
3062
|
+
pattern = pattern,
|
|
3063
|
+
useRegex = useRegex,
|
|
3064
|
+
matches = matches,
|
|
3065
|
+
matchCount = #matches,
|
|
3066
|
+
totalLines = #lines
|
|
3067
|
+
}
|
|
3068
|
+
end)
|
|
3069
|
+
|
|
3070
|
+
if success then
|
|
3071
|
+
return result
|
|
3072
|
+
else
|
|
3073
|
+
return { error = "Failed to search script: " .. tostring(result) }
|
|
3074
|
+
end
|
|
3075
|
+
end
|
|
3076
|
+
|
|
3077
|
+
-- get_script_function: Extract a specific function by name
|
|
3078
|
+
handlers.getScriptFunction = function(requestData)
|
|
3079
|
+
local instancePath = requestData.instancePath
|
|
3080
|
+
local functionName = requestData.functionName
|
|
3081
|
+
|
|
3082
|
+
if not instancePath or not functionName then
|
|
3083
|
+
return { error = "Instance path and function name are required" }
|
|
3084
|
+
end
|
|
3085
|
+
|
|
3086
|
+
local instance = getInstanceByPath(instancePath)
|
|
3087
|
+
if not instance then
|
|
3088
|
+
return { error = "Instance not found: " .. instancePath }
|
|
3089
|
+
end
|
|
3090
|
+
|
|
3091
|
+
if not instance:IsA("LuaSourceContainer") then
|
|
3092
|
+
return { error = "Instance is not a script: " .. instance.ClassName }
|
|
3093
|
+
end
|
|
3094
|
+
|
|
3095
|
+
local success, result = pcall(function()
|
|
3096
|
+
local source = instance.Source
|
|
3097
|
+
local lines, _ = splitLines(source)
|
|
3098
|
+
|
|
3099
|
+
local functionStart = nil
|
|
3100
|
+
local functionEnd = nil
|
|
3101
|
+
local depth = 0
|
|
3102
|
+
local inFunction = false
|
|
3103
|
+
|
|
3104
|
+
-- Patterns to match function definitions
|
|
3105
|
+
local patterns = {
|
|
3106
|
+
"^%s*function%s+" .. functionName .. "%s*%(", -- function name(
|
|
3107
|
+
"^%s*local%s+function%s+" .. functionName .. "%s*%(", -- local function name(
|
|
3108
|
+
functionName .. "%s*=%s*function%s*%(", -- name = function(
|
|
3109
|
+
"local%s+" .. functionName .. "%s*=%s*function%s*%(" -- local name = function(
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
for lineNum, line in ipairs(lines) do
|
|
3113
|
+
-- Check if this line starts the function we're looking for
|
|
3114
|
+
if not inFunction then
|
|
3115
|
+
for _, pat in ipairs(patterns) do
|
|
3116
|
+
if string.match(line, pat) then
|
|
3117
|
+
functionStart = lineNum
|
|
3118
|
+
inFunction = true
|
|
3119
|
+
depth = 1
|
|
3120
|
+
-- Check if function also ends on this line (one-liner)
|
|
3121
|
+
if string.match(line, "%send%s*$") or string.match(line, "%send%s*[%)%];,]") then
|
|
3122
|
+
functionEnd = lineNum
|
|
3123
|
+
inFunction = false
|
|
3124
|
+
end
|
|
3125
|
+
break
|
|
3126
|
+
end
|
|
3127
|
+
end
|
|
3128
|
+
else
|
|
3129
|
+
-- We're inside the function, track depth
|
|
3130
|
+
-- Count block openers
|
|
3131
|
+
if string.match(line, "^%s*function%s") or string.match(line, "=%s*function%s*%(") then
|
|
3132
|
+
depth = depth + 1
|
|
3133
|
+
end
|
|
3134
|
+
if string.match(line, "^%s*if%s") and string.match(line, "%sthen%s*$") then
|
|
3135
|
+
depth = depth + 1
|
|
3136
|
+
end
|
|
3137
|
+
if string.match(line, "^%s*for%s") and string.match(line, "%sdo%s*$") then
|
|
3138
|
+
depth = depth + 1
|
|
3139
|
+
end
|
|
3140
|
+
if string.match(line, "^%s*while%s") and string.match(line, "%sdo%s*$") then
|
|
3141
|
+
depth = depth + 1
|
|
3142
|
+
end
|
|
3143
|
+
if string.match(line, "^%s*do%s*$") then
|
|
3144
|
+
depth = depth + 1
|
|
3145
|
+
end
|
|
3146
|
+
|
|
3147
|
+
-- Count block closers
|
|
3148
|
+
if string.match(line, "^%s*end%s*$") or string.match(line, "^%s*end[%)%],;%s]") or string.match(line, "%send%s*$") then
|
|
3149
|
+
depth = depth - 1
|
|
3150
|
+
if depth == 0 then
|
|
3151
|
+
functionEnd = lineNum
|
|
3152
|
+
break
|
|
3153
|
+
end
|
|
3154
|
+
end
|
|
3155
|
+
end
|
|
3156
|
+
end
|
|
3157
|
+
|
|
3158
|
+
if not functionStart then
|
|
3159
|
+
return {
|
|
3160
|
+
success = false,
|
|
3161
|
+
error = "Function '" .. functionName .. "' not found in script",
|
|
3162
|
+
hint = "Check the function name spelling. Use search_script to find available functions."
|
|
3163
|
+
}
|
|
3164
|
+
end
|
|
3165
|
+
|
|
3166
|
+
if not functionEnd then
|
|
3167
|
+
return {
|
|
3168
|
+
success = false,
|
|
3169
|
+
error = "Could not find the end of function '" .. functionName .. "' - the function may be malformed",
|
|
3170
|
+
startLine = functionStart
|
|
3171
|
+
}
|
|
3172
|
+
end
|
|
3173
|
+
|
|
3174
|
+
-- Extract the function source
|
|
3175
|
+
local functionLines = {}
|
|
3176
|
+
for i = functionStart, functionEnd do
|
|
3177
|
+
table.insert(functionLines, lines[i])
|
|
3178
|
+
end
|
|
3179
|
+
|
|
3180
|
+
local functionSource = table.concat(functionLines, "\n")
|
|
3181
|
+
|
|
3182
|
+
-- Create numbered version
|
|
3183
|
+
local numberedLines = {}
|
|
3184
|
+
for i = functionStart, functionEnd do
|
|
3185
|
+
table.insert(numberedLines, string.format("%4d: %s", i, lines[i]))
|
|
3186
|
+
end
|
|
3187
|
+
local numberedSource = table.concat(numberedLines, "\n")
|
|
3188
|
+
|
|
3189
|
+
return {
|
|
3190
|
+
success = true,
|
|
3191
|
+
instancePath = instancePath,
|
|
3192
|
+
functionName = functionName,
|
|
3193
|
+
startLine = functionStart,
|
|
3194
|
+
endLine = functionEnd,
|
|
3195
|
+
lineCount = functionEnd - functionStart + 1,
|
|
3196
|
+
source = functionSource,
|
|
3197
|
+
numberedSource = numberedSource
|
|
3198
|
+
}
|
|
3199
|
+
end)
|
|
3200
|
+
|
|
3201
|
+
if success then
|
|
3202
|
+
return result
|
|
3203
|
+
else
|
|
3204
|
+
return { error = "Failed to get function: " .. tostring(result) }
|
|
3205
|
+
end
|
|
3206
|
+
end
|
|
3207
|
+
|
|
3208
|
+
-- find_and_replace_in_scripts: Batch replace across multiple scripts
|
|
3209
|
+
handlers.findAndReplaceInScripts = function(requestData)
|
|
3210
|
+
local paths = requestData.paths
|
|
3211
|
+
local oldString = requestData.oldString
|
|
3212
|
+
local newString = requestData.newString
|
|
3213
|
+
local validateAfter = requestData.validateAfter
|
|
3214
|
+
if validateAfter == nil then validateAfter = true end
|
|
3215
|
+
|
|
3216
|
+
if not paths or #paths == 0 then
|
|
3217
|
+
return { error = "Paths array is required" }
|
|
3218
|
+
end
|
|
3219
|
+
if not oldString or oldString == "" then
|
|
3220
|
+
return { error = "old_string is required" }
|
|
3221
|
+
end
|
|
3222
|
+
if newString == nil then
|
|
3223
|
+
return { error = "new_string is required" }
|
|
3224
|
+
end
|
|
3225
|
+
|
|
3226
|
+
local results = {}
|
|
3227
|
+
local successCount = 0
|
|
3228
|
+
local failCount = 0
|
|
3229
|
+
local skippedCount = 0
|
|
3230
|
+
|
|
3231
|
+
for _, path in ipairs(paths) do
|
|
3232
|
+
local instance = getInstanceByPath(path)
|
|
3233
|
+
if not instance then
|
|
3234
|
+
table.insert(results, { path = path, success = false, error = "Instance not found" })
|
|
3235
|
+
failCount = failCount + 1
|
|
3236
|
+
elseif not instance:IsA("LuaSourceContainer") then
|
|
3237
|
+
table.insert(results, { path = path, success = false, error = "Not a script" })
|
|
3238
|
+
failCount = failCount + 1
|
|
3239
|
+
else
|
|
3240
|
+
local source = instance.Source
|
|
3241
|
+
local searchStr = oldString:gsub("\\n", "\n"):gsub("\\t", "\t"):gsub("\\r", "\r"):gsub("\\\\", "\\")
|
|
3242
|
+
local replaceStr = newString:gsub("\\n", "\n"):gsub("\\t", "\t"):gsub("\\r", "\r"):gsub("\\\\", "\\")
|
|
3243
|
+
|
|
3244
|
+
local occurrences = countOccurrences(source, searchStr)
|
|
3245
|
+
|
|
3246
|
+
if occurrences == 0 then
|
|
3247
|
+
table.insert(results, { path = path, success = true, skipped = true, reason = "Pattern not found" })
|
|
3248
|
+
skippedCount = skippedCount + 1
|
|
3249
|
+
else
|
|
3250
|
+
local newSource = string.gsub(source, searchStr:gsub("([^%w])", "%%%1"), replaceStr)
|
|
3251
|
+
|
|
3252
|
+
if validateAfter then
|
|
3253
|
+
local isValid, syntaxErrors = validateLuaSyntax(newSource)
|
|
3254
|
+
if not isValid then
|
|
3255
|
+
table.insert(results, { path = path, success = false, error = "Would create invalid syntax", syntaxErrors = syntaxErrors })
|
|
3256
|
+
failCount = failCount + 1
|
|
3257
|
+
else
|
|
3258
|
+
pcall(function()
|
|
3259
|
+
ScriptEditorService:UpdateSourceAsync(instance, function() return newSource end)
|
|
3260
|
+
end)
|
|
3261
|
+
table.insert(results, { path = path, success = true, replacements = occurrences })
|
|
3262
|
+
successCount = successCount + 1
|
|
3263
|
+
end
|
|
3264
|
+
else
|
|
3265
|
+
pcall(function()
|
|
3266
|
+
ScriptEditorService:UpdateSourceAsync(instance, function() return newSource end)
|
|
3267
|
+
end)
|
|
3268
|
+
table.insert(results, { path = path, success = true, replacements = occurrences })
|
|
3269
|
+
successCount = successCount + 1
|
|
3270
|
+
end
|
|
3271
|
+
end
|
|
3272
|
+
end
|
|
3273
|
+
end
|
|
3274
|
+
|
|
3275
|
+
if successCount > 0 then
|
|
3276
|
+
ChangeHistoryService:SetWaypoint("Batch replace in " .. successCount .. " scripts")
|
|
3277
|
+
end
|
|
3278
|
+
|
|
3279
|
+
return {
|
|
3280
|
+
success = failCount == 0,
|
|
3281
|
+
results = results,
|
|
3282
|
+
summary = {
|
|
3283
|
+
total = #paths,
|
|
3284
|
+
successful = successCount,
|
|
3285
|
+
failed = failCount,
|
|
3286
|
+
skipped = skippedCount
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
end
|
|
3290
|
+
|
|
2778
3291
|
-- Attribute Tools: Get a single attribute
|
|
2779
3292
|
handlers.getAttribute = function(requestData)
|
|
2780
3293
|
local instancePath = requestData.instancePath
|
|
@@ -3451,15 +3964,31 @@ handlers.validateScript = function(requestData)
|
|
|
3451
3964
|
end
|
|
3452
3965
|
|
|
3453
3966
|
-- ============================================
|
|
3454
|
-
-- UNDO/REDO HANDLERS
|
|
3967
|
+
-- UNDO/REDO HANDLERS (Enhanced with action tracking)
|
|
3455
3968
|
-- ============================================
|
|
3456
3969
|
|
|
3457
3970
|
handlers.undo = function(requestData)
|
|
3458
3971
|
local success, result = pcall(function()
|
|
3972
|
+
-- Pop the last action from history
|
|
3973
|
+
local lastAction = nil
|
|
3974
|
+
if #actionHistory > 0 then
|
|
3975
|
+
lastAction = table.remove(actionHistory)
|
|
3976
|
+
-- Move it to redo history
|
|
3977
|
+
table.insert(redoHistory, lastAction)
|
|
3978
|
+
end
|
|
3979
|
+
|
|
3459
3980
|
ChangeHistoryService:Undo()
|
|
3981
|
+
|
|
3460
3982
|
return {
|
|
3461
3983
|
success = true,
|
|
3462
|
-
|
|
3984
|
+
undone = lastAction and (lastAction.action .. " → " .. lastAction.target .. " (" .. lastAction.summary .. ")") or "Unknown action (possibly manual change)",
|
|
3985
|
+
action = lastAction and lastAction.action or nil,
|
|
3986
|
+
target = lastAction and lastAction.target or nil,
|
|
3987
|
+
summary = lastAction and lastAction.summary or nil,
|
|
3988
|
+
details = lastAction and lastAction.details or nil,
|
|
3989
|
+
remaining_undos = #actionHistory,
|
|
3990
|
+
available_redos = #redoHistory,
|
|
3991
|
+
message = lastAction and ("Undone: " .. lastAction.summary) or "Undo executed"
|
|
3463
3992
|
}
|
|
3464
3993
|
end)
|
|
3465
3994
|
|
|
@@ -3468,17 +3997,35 @@ handlers.undo = function(requestData)
|
|
|
3468
3997
|
else
|
|
3469
3998
|
return {
|
|
3470
3999
|
success = false,
|
|
3471
|
-
error = "Failed to undo: " .. tostring(result)
|
|
4000
|
+
error = "Failed to undo: " .. tostring(result),
|
|
4001
|
+
remaining_undos = #actionHistory,
|
|
4002
|
+
available_redos = #redoHistory
|
|
3472
4003
|
}
|
|
3473
4004
|
end
|
|
3474
4005
|
end
|
|
3475
4006
|
|
|
3476
4007
|
handlers.redo = function(requestData)
|
|
3477
4008
|
local success, result = pcall(function()
|
|
4009
|
+
-- Pop the last undone action from redo history
|
|
4010
|
+
local redoneAction = nil
|
|
4011
|
+
if #redoHistory > 0 then
|
|
4012
|
+
redoneAction = table.remove(redoHistory)
|
|
4013
|
+
-- Move it back to action history
|
|
4014
|
+
table.insert(actionHistory, redoneAction)
|
|
4015
|
+
end
|
|
4016
|
+
|
|
3478
4017
|
ChangeHistoryService:Redo()
|
|
4018
|
+
|
|
3479
4019
|
return {
|
|
3480
4020
|
success = true,
|
|
3481
|
-
|
|
4021
|
+
redone = redoneAction and (redoneAction.action .. " → " .. redoneAction.target .. " (" .. redoneAction.summary .. ")") or "Unknown action",
|
|
4022
|
+
action = redoneAction and redoneAction.action or nil,
|
|
4023
|
+
target = redoneAction and redoneAction.target or nil,
|
|
4024
|
+
summary = redoneAction and redoneAction.summary or nil,
|
|
4025
|
+
details = redoneAction and redoneAction.details or nil,
|
|
4026
|
+
remaining_undos = #actionHistory,
|
|
4027
|
+
available_redos = #redoHistory,
|
|
4028
|
+
message = redoneAction and ("Redone: " .. redoneAction.summary) or "Redo executed"
|
|
3482
4029
|
}
|
|
3483
4030
|
end)
|
|
3484
4031
|
|
|
@@ -3487,11 +4034,142 @@ handlers.redo = function(requestData)
|
|
|
3487
4034
|
else
|
|
3488
4035
|
return {
|
|
3489
4036
|
success = false,
|
|
3490
|
-
error = "Failed to redo: " .. tostring(result)
|
|
4037
|
+
error = "Failed to redo: " .. tostring(result),
|
|
4038
|
+
remaining_undos = #actionHistory,
|
|
4039
|
+
available_redos = #redoHistory
|
|
3491
4040
|
}
|
|
3492
4041
|
end
|
|
3493
4042
|
end
|
|
3494
4043
|
|
|
4044
|
+
-- ============================================
|
|
4045
|
+
-- INSERT ASSET HANDLER (Creator Store)
|
|
4046
|
+
-- ============================================
|
|
4047
|
+
|
|
4048
|
+
handlers.insertAsset = function(requestData)
|
|
4049
|
+
local assetId = requestData.assetId
|
|
4050
|
+
local folderName = requestData.folderName or "AIReferences"
|
|
4051
|
+
local targetParent = requestData.targetParent or "game.Workspace"
|
|
4052
|
+
|
|
4053
|
+
if not assetId then
|
|
4054
|
+
return { error = "Asset ID is required" }
|
|
4055
|
+
end
|
|
4056
|
+
|
|
4057
|
+
-- Convert to number if string
|
|
4058
|
+
assetId = tonumber(assetId)
|
|
4059
|
+
if not assetId then
|
|
4060
|
+
return { error = "Asset ID must be a valid number" }
|
|
4061
|
+
end
|
|
4062
|
+
|
|
4063
|
+
local success, result = pcall(function()
|
|
4064
|
+
-- Get or create the reference folder
|
|
4065
|
+
local parentInstance = getInstanceByPath(targetParent)
|
|
4066
|
+
if not parentInstance then
|
|
4067
|
+
parentInstance = game.Workspace
|
|
4068
|
+
end
|
|
4069
|
+
|
|
4070
|
+
local folder = parentInstance:FindFirstChild(folderName)
|
|
4071
|
+
if not folder then
|
|
4072
|
+
folder = Instance.new("Folder")
|
|
4073
|
+
folder.Name = folderName
|
|
4074
|
+
folder.Parent = parentInstance
|
|
4075
|
+
end
|
|
4076
|
+
|
|
4077
|
+
-- Use game:GetObjects to load the asset (works with any free asset in plugins)
|
|
4078
|
+
local assetUrl = "rbxassetid://" .. tostring(assetId)
|
|
4079
|
+
local objects = game:GetObjects(assetUrl)
|
|
4080
|
+
|
|
4081
|
+
if not objects or #objects == 0 then
|
|
4082
|
+
return {
|
|
4083
|
+
success = false,
|
|
4084
|
+
error = "Failed to load asset - it may be private, unavailable, or not a model asset"
|
|
4085
|
+
}
|
|
4086
|
+
end
|
|
4087
|
+
|
|
4088
|
+
-- Parent all loaded objects to the folder
|
|
4089
|
+
local insertedPaths = {}
|
|
4090
|
+
for i, obj in ipairs(objects) do
|
|
4091
|
+
-- Name the object with the asset ID for easy identification
|
|
4092
|
+
if #objects == 1 then
|
|
4093
|
+
obj.Name = "Asset_" .. tostring(assetId)
|
|
4094
|
+
else
|
|
4095
|
+
obj.Name = "Asset_" .. tostring(assetId) .. "_" .. tostring(i)
|
|
4096
|
+
end
|
|
4097
|
+
obj.Parent = folder
|
|
4098
|
+
table.insert(insertedPaths, getInstancePath(obj))
|
|
4099
|
+
end
|
|
4100
|
+
|
|
4101
|
+
return {
|
|
4102
|
+
success = true,
|
|
4103
|
+
assetId = assetId,
|
|
4104
|
+
folderPath = getInstancePath(folder),
|
|
4105
|
+
insertedObjects = insertedPaths,
|
|
4106
|
+
objectCount = #objects,
|
|
4107
|
+
message = "Successfully loaded " .. #objects .. " object(s) from asset " .. tostring(assetId)
|
|
4108
|
+
}
|
|
4109
|
+
end)
|
|
4110
|
+
|
|
4111
|
+
if success then
|
|
4112
|
+
return result
|
|
4113
|
+
else
|
|
4114
|
+
return {
|
|
4115
|
+
success = false,
|
|
4116
|
+
error = "Failed to insert asset: " .. tostring(result),
|
|
4117
|
+
assetId = assetId
|
|
4118
|
+
}
|
|
4119
|
+
end
|
|
4120
|
+
end
|
|
4121
|
+
|
|
4122
|
+
-- Now that all handlers are defined, populate the endpoint mapping table
|
|
4123
|
+
endpointHandlers = {
|
|
4124
|
+
["/api/file-tree"] = handlers.getFileTree,
|
|
4125
|
+
["/api/search-files"] = handlers.searchFiles,
|
|
4126
|
+
["/api/place-info"] = handlers.getPlaceInfo,
|
|
4127
|
+
["/api/services"] = handlers.getServices,
|
|
4128
|
+
["/api/search-objects"] = handlers.searchObjects,
|
|
4129
|
+
["/api/instance-properties"] = handlers.getInstanceProperties,
|
|
4130
|
+
["/api/instance-children"] = handlers.getInstanceChildren,
|
|
4131
|
+
["/api/search-by-property"] = handlers.searchByProperty,
|
|
4132
|
+
["/api/class-info"] = handlers.getClassInfo,
|
|
4133
|
+
["/api/project-structure"] = handlers.getProjectStructure,
|
|
4134
|
+
["/api/set-property"] = handlers.setProperty,
|
|
4135
|
+
["/api/mass-set-property"] = handlers.massSetProperty,
|
|
4136
|
+
["/api/mass-get-property"] = handlers.massGetProperty,
|
|
4137
|
+
["/api/create-object"] = handlers.createObject,
|
|
4138
|
+
["/api/mass-create-objects"] = handlers.massCreateObjects,
|
|
4139
|
+
["/api/mass-create-objects-with-properties"] = handlers.massCreateObjectsWithProperties,
|
|
4140
|
+
["/api/delete-object"] = handlers.deleteObject,
|
|
4141
|
+
["/api/smart-duplicate"] = handlers.smartDuplicate,
|
|
4142
|
+
["/api/mass-duplicate"] = handlers.massDuplicate,
|
|
4143
|
+
["/api/set-calculated-property"] = handlers.setCalculatedProperty,
|
|
4144
|
+
["/api/set-relative-property"] = handlers.setRelativeProperty,
|
|
4145
|
+
["/api/get-script-source"] = handlers.getScriptSource,
|
|
4146
|
+
["/api/set-script-source"] = handlers.setScriptSource,
|
|
4147
|
+
["/api/edit-script-lines"] = handlers.editScriptLines,
|
|
4148
|
+
["/api/insert-script-lines"] = handlers.insertScriptLines,
|
|
4149
|
+
["/api/delete-script-lines"] = handlers.deleteScriptLines,
|
|
4150
|
+
-- Claude Code-style script editing tools
|
|
4151
|
+
["/api/edit-script"] = handlers.editScript,
|
|
4152
|
+
["/api/search-script"] = handlers.searchScript,
|
|
4153
|
+
["/api/get-script-function"] = handlers.getScriptFunction,
|
|
4154
|
+
["/api/find-and-replace-in-scripts"] = handlers.findAndReplaceInScripts,
|
|
4155
|
+
["/api/get-attribute"] = handlers.getAttribute,
|
|
4156
|
+
["/api/set-attribute"] = handlers.setAttribute,
|
|
4157
|
+
["/api/get-attributes"] = handlers.getAttributes,
|
|
4158
|
+
["/api/delete-attribute"] = handlers.deleteAttribute,
|
|
4159
|
+
["/api/get-tags"] = handlers.getTags,
|
|
4160
|
+
["/api/add-tag"] = handlers.addTag,
|
|
4161
|
+
["/api/remove-tag"] = handlers.removeTag,
|
|
4162
|
+
["/api/get-tagged"] = handlers.getTagged,
|
|
4163
|
+
["/api/get-selection"] = handlers.getSelection,
|
|
4164
|
+
["/api/get-output"] = handlers.getOutput,
|
|
4165
|
+
["/api/clone-instance"] = handlers.cloneInstance,
|
|
4166
|
+
["/api/move-instance"] = handlers.moveInstance,
|
|
4167
|
+
["/api/validate-script"] = handlers.validateScript,
|
|
4168
|
+
["/api/insert-asset"] = handlers.insertAsset,
|
|
4169
|
+
["/api/undo"] = handlers.undo,
|
|
4170
|
+
["/api/redo"] = handlers.redo,
|
|
4171
|
+
}
|
|
4172
|
+
|
|
3495
4173
|
local function updateUIState()
|
|
3496
4174
|
if pluginState.isActive then
|
|
3497
4175
|
statusLabel.Text = "Connecting..."
|