Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nnaridz/RbxGenie/llms.txt

Use this file to discover all available pages before exploring further.

The Roblox Studio plugin is the execution engine of RbxGenie, running inside Roblox Studio to poll for commands, execute tools against the DataModel, and report results back to the daemon.

Plugin Structure

Location: plugin/
plugin/
├── init.server.lua          # Entry point, UI, connection loop
├── Bridge.lua               # HTTP polling and result submission
├── Executor.lua             # Tool dispatcher
├── UI.lua                   # Widget UI with activity feed
├── PathResolver.lua         # Path string → Instance resolver
├── ValueSerializer.lua      # JSON ↔ Roblox types serializer
└── tools/
    ├── InstanceTools.lua    # Tree queries, search, info
    ├── PropertyTools.lua    # Get/set properties
    ├── ObjectTools.lua      # Create, delete, duplicate
    ├── ScriptTools.lua      # Script source operations
    ├── AttributeTools.lua   # Attribute operations
    ├── TagTools.lua         # CollectionService tags
    ├── SelectionTools.lua   # Selection queries
    ├── ExecuteTools.lua     # Luau execution
    ├── PlaytestTools.lua    # Play mode control
    └── InsertModelTools.lua # Marketplace model insertion

Core Modules

init.server.lua

Entry point and connection management. Location: plugin/init.server.lua
1

Widget Creation

Creates a dock widget with the RbxGenie UI
local widgetInfo = DockWidgetPluginGuiInfo.new(
  Enum.InitialDockState.Right,
  false, false, 280, 420, 200, 150
)
local widget = plugin:CreateDockWidgetPluginGui("RbxGenie", widgetInfo)
2

Toolbar Button

Adds a toolbar button to toggle the widget
local toolbar = plugin:CreateToolbar("RbxGenie")
local toggleBtn = toolbar:CreateButton("RbxGenie", "Toggle RbxGenie panel", "rbxassetid://...")
3

Health Polling

Polls the daemon’s /health endpoint every 3 seconds
local HEALTH_URL = "http://127.0.0.1:7766/health"
local HEALTH_POLL = 3
4

Auto-Reconnect

Restores connection state across play mode transitions
local saved = plugin:GetSetting("RbxGenieAutoConnect")
if saved then setConnect(true) end

Bridge.lua

HTTP communication and command loop. Location: plugin/Bridge.lua

Configuration

local DAEMON_URL = "http://127.0.0.1:7766"
local POLL_ENDPOINT = DAEMON_URL .. "/poll"
local RESULT_ENDPOINT = DAEMON_URL .. "/result"

local BACKOFF_INITIAL = 2
local BACKOFF_MAX = 30
local BACKOFF_FACTOR = 1.5

Main Loop

Location: plugin/Bridge.lua:76-129
function Bridge.startLoop()
  local backoff = BACKOFF_INITIAL

  while true do
    local ok, body = httpGet(POLL_ENDPOINT)

    if not ok then
      log("[Bridge] Poll failed: " .. body .. " — retry in " .. math.round(backoff) .. "s")
      task.wait(backoff)
      backoff = math.min(backoff * BACKOFF_FACTOR + math.random() * 0.5, BACKOFF_MAX)
      continue
    end

    backoff = BACKOFF_INITIAL

    local decoded = HttpService:JSONDecode(body)

    if not decoded or not decoded.hasCommand then
      task.wait(0.05)
      continue
    end

    local id = decoded.id
    local tool = decoded.tool
    local args = decoded.args

    log("[Bridge] Received: " .. tool .. " (" .. id:sub(1, 8) .. ")")
    if Bridge.onCommandStart then Bridge.onCommandStart(tool, args) end

    local recording = nil
    if not READ_ONLY_TOOLS[tool] then
      recording = ChangeHistoryService:TryBeginRecording("RbxGenie: " .. tool)
    end

    local t0 = os.clock()
    local result, err = Executor.dispatch(tool, args)
    local elapsedMs = math.round((os.clock() - t0) * 1000)

    if recording then
      ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
    end

    if Bridge.onCommandEnd then Bridge.onCommandEnd(tool, err == nil, elapsedMs) end

    if err then
      postResult(id, nil, err)
    else
      postResult(id, result, nil)
    end

    task.wait(0.1)
  end
end
The bridge implements exponential backoff on poll failures, starting at 2 seconds and capping at 30 seconds, with jitter to prevent thundering herd.

Read-Only Tools

Tools that don’t modify the DataModel are not recorded for undo: Location: plugin/Bridge.lua:13-24
local READ_ONLY_TOOLS = {
  get_file_tree = true, search_files = true, get_place_info = true,
  get_services = true, search_objects = true, get_instance_properties = true,
  get_instance_children = true, search_by_property = true, get_class_info = true,
  get_project_structure = true, summarize_game = true,
  mass_get_property = true,
  get_script_source = true,
  get_attribute = true, get_attributes = true,
  get_tags = true, get_tagged = true,
  get_selection = true,
  get_console_output = true, get_studio_mode = true,
}

Executor.lua

Tool dispatcher that routes commands to appropriate tool modules. Location: plugin/Executor.lua
local DISPATCH: { [string]: (args: any) -> any } = {
  -- Instance / Info
  summarize_game = InstanceTools.summarize_game,
  get_file_tree = InstanceTools.get_file_tree,
  search_files = InstanceTools.search_files,
  get_place_info = InstanceTools.get_place_info,
  -- ... 50+ tool mappings
}

function Executor.dispatch(tool: string, args: any): (any, string?)
  local handler = DISPATCH[tool]
  if not handler then
    return nil, "Unknown tool: " .. tostring(tool)
  end
  local ok, result = pcall(handler, args)
  if not ok then
    return nil, "Tool error [" .. tool .. "]: " .. tostring(result)
  end
  return result, nil
end
All tool calls are wrapped in pcall to catch Lua errors and return them as structured error messages.

Utility Modules

PathResolver.lua

Resolves dot-separated path strings to Instance references. Location: plugin/PathResolver.lua
function PathResolver.resolve(path: string): (Instance?, string?)
  if not path or path == "" then
    return nil, "Empty path"
  end

  local parts = path:split(".")
  local root = ROOTS[parts[1]]  -- e.g., "Workspace", "ReplicatedStorage"

  if not root then
    -- Try game:GetService fallback
    local ok, svc = pcall(function() return game:GetService(parts[1]) end)
    if ok and svc then
      root = svc
    else
      return nil, "Unknown root service: " .. parts[1]
    end
  end

  local current: Instance = root
  for i = 2, #parts do
    local child = current:FindFirstChild(parts[i])
    if not child then
      return nil, ("Path not found at segment [%d] '%s' in '%s'"):format(i, parts[i], path)
    end
    current = child
  end

  return current, nil
end
Example paths:
  • Workspace.Model.Part
  • ReplicatedStorage.Modules.Utility
  • ServerScriptService.GameController
Paths are case-sensitive and use FindFirstChild, so they match the exact instance names in the DataModel.

ValueSerializer.lua

Converts between JSON-serializable values and Roblox data types. Location: plugin/ValueSerializer.lua

fromJSON()

Converts JSON representation to Roblox types:
function ValueSerializer.fromJSON(v: any): any
  if type(v) == "table" and v.type then
    if v.type == "Vector3" then
      return Vector3.new(v.value[1], v.value[2], v.value[3])
    elseif v.type == "Color3" then
      return Color3.new(v.value[1], v.value[2], v.value[3])
    elseif v.type == "CFrame" then
      return CFrame.new(unpack(v.value))  -- 12 components
    elseif v.type == "Enum" then
      return toEnum(tostring(v.value))  -- e.g., "Enum.Material.Plastic"
    -- ... other types
    end
  end
  return v  -- Pass through primitives
end
JSON format examples:
{ "type": "Vector3", "value": [10, 20, 30] }

toJSON()

Converts Roblox types to JSON representation:
function ValueSerializer.toJSON(v: any): any
  local t = typeof(v)
  if t == "Vector3" then
    return { type = "Vector3", value = {v.X, v.Y, v.Z} }
  elseif t == "Color3" then
    return { type = "Color3", value = {v.R, v.G, v.B} }
  elseif t == "EnumItem" then
    return { type = "Enum", value = tostring(v) }
  -- ... other types
  else
    return v  -- Pass through primitives
  end
end

UI.lua

Builds the widget interface with status indicators and activity feed. Location: plugin/UI.lua UI Components:

Status Strip

Connection status dot, “Online”/“Disconnected” label, Start/Stop button

Active Panel

Current executing tool with elapsed time display

Activity Feed

Scrolling log of completed commands with timestamps and results

Stat Bar

Bottom bar showing daemon URL and connection state
API:
local ui = UI.build(widget)

ui.setStatus(connected: boolean)           -- Update connection status
ui.setActive(tool: string?, detail: string?) -- Show/hide active tool
ui.addEntry(tool: string, ok: boolean, elapsedMs: number?, isSystem: boolean?) -- Log entry
ui.clearLog()                              -- Clear activity feed
ui.setConnectButton(running: boolean)      -- Update Start/Stop button
ui.onConnectToggle = function(running: boolean) end  -- Connection callback

Tool Modules

InstanceTools

Query and navigate the DataModel tree. Location: plugin/tools/InstanceTools.lua Key functions:
  • get_file_tree() — Recursive tree with depth/limit controls
  • search_files() — Case-insensitive name pattern search
  • get_place_info() — PlaceId, GameId, version info
  • get_services() — List all top-level services
  • search_objects() — Multi-criteria instance search
  • get_instance_properties() — All readable properties
  • get_instance_children() — Direct children list
  • summarize_game() — High-level project structure
Example:
function InstanceTools.get_place_info(_args: {}): any
  return {
    placeId = game.PlaceId,
    gameId = game.GameId,
    placeName = game.Name,
    placeVersion = game.PlaceVersion,
    creatorId = game.CreatorId,
    creatorType = tostring(game.CreatorType),
  }
end

PropertyTools

Get and set instance properties. Location: plugin/tools/PropertyTools.lua Key functions:
  • set_property() — Set a single property
  • mass_set_property() — Set property on multiple instances
  • mass_get_property() — Get property from multiple instances
  • set_calculated_property() — Use expression (e.g., "Size.X * 2")
  • set_relative_property() — Relative adjustment (e.g., delta)
Example:
function PropertyTools.set_property(args: { path: string, property: string, value: any }): any
  local inst, err = PathResolver.resolve(args.path)
  if not inst then return { error = err } end

  local value = ValueSerializer.fromJSON(args.value)
  inst[args.property] = value

  return "Property set"
end

ScriptTools

Read and modify script source code. Location: plugin/tools/ScriptTools.lua Key functions:
  • get_script_source() — Return full source as string
  • set_script_source() — Replace entire script source
  • edit_script_lines() — Replace line range
  • insert_script_lines() — Insert after line number
  • delete_script_lines() — Delete line range
Example:
function ScriptTools.edit_script_lines(args: {
  path: string,
  startLine: number,
  endLine: number,
  newText: string
}): any
  local inst, err = PathResolver.resolve(args.path)
  if not inst then return { error = err } end
  if not inst:IsA("LuaSourceContainer") then
    return { error = "Instance is not a script" }
  end

  local lines = inst.Source:split("\n")
  -- Replace lines[startLine..endLine] with newText
  -- (implementation omitted)
  inst.Source = table.concat(lines, "\n")

  return "Lines edited"
end

ExecuteTools

Execute arbitrary Luau code in edit mode. Location: plugin/tools/ExecuteTools.lua
The execute_luau tool runs arbitrary code with full Studio API access. Use with caution.
Key functions:
  • execute_luau() — Execute code, capture output and result
  • get_console_output() — Retrieve LogService messages
  • clear_console_output() — Clear message buffer
Example:
function ExecuteTools.execute_luau(args: { code: string }): any
  local outputBuffer = {}
  
  -- Redirect print() to buffer
  local oldPrint = print
  print = function(...)
    table.insert(outputBuffer, table.concat({...}, " "))
  end

  local fn, loadErr = loadstring(args.code)
  if not fn then
    print = oldPrint
    return { error = "Syntax error: " .. loadErr, output = outputBuffer }
  end

  local ok, result = pcall(fn)
  print = oldPrint

  if not ok then
    return { error = tostring(result), output = outputBuffer }
  end

  return {
    result = ValueSerializer.toJSON(result),
    output = outputBuffer
  }
end

PlaytestTools

Control Studio play modes and run integration tests. Location: plugin/tools/PlaytestTools.lua Key functions:
  • start_play() — Enter play mode
  • stop_play() — Exit play mode
  • run_server() — Enter server mode
  • get_studio_mode() — Current mode (“edit”/“play”/“server”)
  • run_script_in_play_mode() — Inject test script, run, capture logs
Example:
function PlaytestTools.run_script_in_play_mode(args: {
  code: string,
  timeout: number?,
  mode: string?
}): any
  local RunService = game:GetService("RunService")
  local timeout = args.timeout or 10
  local mode = args.mode or "play"

  -- 1. Create test script in ServerScriptService
  -- 2. Start play/server mode
  -- 3. Wait for script to complete or timeout
  -- 4. Capture LogService output
  -- 5. Stop play mode
  -- 6. Return { logs, errors, duration }
end
The run_script_in_play_mode tool is designed for automated testing, allowing AI to write and execute integration tests in play mode.

Error Handling

Path Resolution Errors

local inst, err = PathResolver.resolve(args.path)
if not inst then return { error = err } end
Returns structured errors:
{ "error": "Path not found at segment [2] 'Part' in 'Workspace.Part'" }

Property Access Errors

local ok, result = pcall(function()
  inst[args.property] = value
end)
if not ok then
  return { error = "Property error: " .. tostring(result) }
end

Type Conversion Errors

ValueSerializer returns the original value if conversion fails:
function ValueSerializer.fromJSON(v: any): any
  if type(v) == "table" and v.type then
    -- Attempt conversion
  end
  return v  -- Fallback to original
end

Performance Optimizations

Tree Truncation

Location: plugin/tools/InstanceTools.lua:8-40
local function instanceToTable(
  inst: Instance,
  depth: number,
  maxDepth: number,
  counter: { n: number },
  maxNodes: number
): { [string]: any }?
  if counter.n >= maxNodes then
    return nil
  end
  counter.n += 1
  -- ... build tree
  if counter.n >= maxNodes then
    t.truncated = true
    break
  end
  return t
end
The get_file_tree tool caps at 200 nodes by default to prevent massive responses. Use scoped paths or lower depth for large games.

Batch Operations

Tools like mass_set_property and mass_create_objects operate on multiple instances in a single command:
function PropertyTools.mass_set_property(args: {
  paths: { string },
  property: string,
  value: any
}): any
  local results = {}
  for _, path in ipairs(args.paths) do
    local inst, err = PathResolver.resolve(path)
    if inst then
      inst[args.property] = ValueSerializer.fromJSON(args.value)
      table.insert(results, { path = path, ok = true })
    else
      table.insert(results, { path = path, ok = false, error = err })
    end
  end
  return results
end

Next Steps

MCP Server

Learn how AI assistants connect via the MCP protocol

Daemon

Understand the HTTP server and bridge queue