-- rebind: min_sdk=3.0.0 -- rebind: name=Remote Access -- rebind: version=1.1.0 -- rebind: author=Rebind -- rebind: description=JSON over WebSocket — drive HID, read Screen/Input/Window/Clipboard, and subscribe to live event streams from any external program. -- rebind: instance=single -- rebind: tick_rate=1000 -- rebind: permission=net -- -- Reference remote-control server. Run this script in Rebind and any conforming -- client — the official TypeScript / Python / Rust libraries, the browser demo, -- or your own code — can drive a real keyboard and mouse over a WebSocket. -- -- THE PROTOCOL (everything is one JSON object per message) -- request: { "t": "", ...args, "id": } -- response: { "id": , ...result } (only when the request had an id) -- error: { "id": , "error": { "code", "message" } } -- push: { "t": "mouse" | "window" | "input", ... } (after `subscribe`) -- -- A command with no `id` is fire-and-forget (e.g. an HID write). Add an `id` to -- get a correlated reply (e.g. reading a pixel). This is the whole contract; the -- SDK clients are thin wrappers over it, and you can speak it from any language. -- -- Edit the command table below to extend the surface — every change is local and -- needs no SDK update. -- ── Config ─────────────────────────────────────────────────────────────────── -- AUTH_TOKEN "" = open (fine on localhost, risky on a LAN). When set, a -- client must send { t = "auth", token = "..." } before any -- other command succeeds. -- ALLOW_LUA_EXEC false = the `lua.exec` escape hatch is refused. Leave it off -- unless you fully trust every client — it runs arbitrary Lua. local AUTH_TOKEN = "" local ALLOW_LUA_EXEC = false local cfg = UI.Schema({ port = UI.Slider( 19561, { min = 1024, max = 65535, label = "WebSocket port" } ), }) local PROTOCOL_VERSION = "1.1.0" -- ── Connection state ───────────────────────────────────────────────────────── local server = nil local authed = {} -- client_id -> true once the token is accepted local subscribers = {} -- client_id -> { client, mouse=bool, window=bool, input=bool } local last_mouse_x, last_mouse_y, last_window_title = nil, nil, nil -- ── Wire helpers ───────────────────────────────────────────────────────────── local function send(client, obj) local ok, encoded = pcall(JSON.Stringify, obj) if ok then client:Send(encoded) else Log.Warn("remote_access: encode failed: " .. tostring(encoded)) end end -- reply only when the request carried an id (otherwise it was fire-and-forget) local function reply(client, req, payload) if req.id == nil then return end payload.id = req.id send(client, payload) end local function err(client, req, code, message) if req.id == nil then Log.Warn( ("remote_access: client %d (%s): %s"):format(client.id, code, message) ) return end send(client, { id = req.id, error = { code = code, message = message } }) end local function authorized(client, req) if AUTH_TOKEN == "" or authed[client.id] then return true end err( client, req, "unauthenticated", 'send { t = "auth", token = "..." } first' ) return false end -- ── Commands ───────────────────────────────────────────────────────────────── -- Each handler receives (client, req) and replies when req.id is present. -- HID writes are one-shot; reads return a result. local handlers = {} -- handshake & auth handlers["hello"] = function(client, req) reply( client, req, { protocol = PROTOCOL_VERSION, auth_required = AUTH_TOKEN ~= "" } ) end handlers["auth"] = function(client, req) if AUTH_TOKEN == "" then authed[client.id] = true reply(client, req, { ok = true, note = "no token required" }) elseif req.token == AUTH_TOKEN then authed[client.id] = true reply(client, req, { ok = true }) else err(client, req, "bad_token", "token does not match") end end handlers["ping"] = function(client, req) reply(client, req, { pong = true, time_ms = System.Time() }) end -- HID output (fire-and-forget) handlers["hid.down"] = function(c, r) if authorized(c, r) then HID.Down(r.code) end end handlers["hid.up"] = function(c, r) if authorized(c, r) then HID.Up(r.code) end end handlers["hid.press"] = function(c, r) if authorized(c, r) then HID.Press(r.code, r.hold_ms or 20) end end handlers["hid.type"] = function(c, r) if authorized(c, r) then HID.Type(r.text or "") end end handlers["hid.move"] = function(c, r) if authorized(c, r) then HID.Move(r.dx or 0, r.dy or 0) end end handlers["hid.move_to"] = function(c, r) if authorized(c, r) then HID.MoveTo(r.x or 0, r.y or 0) end end handlers["hid.scroll"] = function(c, r) if authorized(c, r) then HID.Scroll(r.delta or 0) end end -- Screen reads (Windows + macOS) handlers["screen.pixel"] = function(client, req) if not authorized(client, req) then return end local ok, hex = pcall(Screen.GetPixelColor, req.x, req.y) if not ok then err(client, req, "screen_error", tostring(hex)) return end -- GetPixelColor returns "RRGGBB"; expand to numeric channels for the client reply(client, req, { r = tonumber(hex:sub(1, 2), 16) or 0, g = tonumber(hex:sub(3, 4), 16) or 0, b = tonumber(hex:sub(5, 6), 16) or 0, }) end handlers["screen.resolution"] = function(client, req) if not authorized(client, req) then return end local w, h = System.Screen() reply(client, req, { width = w, height = h }) end handlers["screen.displays"] = function(client, req) if not authorized(client, req) then return end reply(client, req, { displays = Screen.List() }) end -- screen.capture passes Screen.Capture through verbatim: a native-res base64 PNG -- plus display geometry and cursor for mapping image coords back to the screen. -- req.display (1-based) and req.region ({x,y,w,h}) are optional. handlers["screen.capture"] = function(client, req) if not authorized(client, req) then return end local ok, result = pcall(Screen.Capture, { display = req.display, region = req.region }) if not ok then err(client, req, "capture_error", tostring(result)) return end reply(client, req, result) end -- System reads handlers["system.mouse"] = function(c, r) if authorized(c, r) then local x, y = System.Mouse() reply(c, r, { x = x, y = y }) end end handlers["system.window"] = function(c, r) if authorized(c, r) then reply(c, r, { window = System.Window() }) end end handlers["system.time"] = function(c, r) if authorized(c, r) then reply(c, r, { time_ms = System.Time() }) end end -- Input state handlers["input.keys"] = function(c, r) if authorized(c, r) then reply(c, r, { keys = Input.GetActiveKeys() }) end end handlers["input.is_down"] = function(c, r) if authorized(c, r) then reply(c, r, { down = Input.IsDown(r.code) }) end end handlers["input.modifiers"] = function(c, r) if authorized(c, r) then reply(c, r, { modifiers = Input.GetModifiers() }) end end -- Clipboard (Windows + macOS) handlers["clipboard.get"] = function(c, r) if authorized(c, r) then reply(c, r, { text = Clipboard.Get() or "" }) end end handlers["clipboard.set"] = function(c, r) if authorized(c, r) then Clipboard.Set(r.text or "") reply(c, r, { ok = true }) end end -- Window manipulation (full on Windows, partial on macOS) handlers["window.list"] = function(c, r) if authorized(c, r) then reply(c, r, { windows = Window.List(r.filter) }) end end handlers["window.find"] = function(c, r) if authorized(c, r) then reply(c, r, { handle = Window.Find(r.title or "") }) end end handlers["window.activate"] = function(c, r) if authorized(c, r) then Window.Activate(r.handle) reply(c, r, { ok = true }) end end handlers["window.move"] = function(c, r) if authorized(c, r) then Window.Move(r.handle, r.x, r.y, r.width, r.height) reply(c, r, { ok = true }) end end -- Live event streams: subscribe to "mouse", "window", and/or "input"; the server -- pushes a frame from OnTick whenever that state changes. handlers["subscribe"] = function(client, req) if not authorized(client, req) then return end local sub = subscribers[client.id] or { client = client } sub.client = client for _, name in ipairs(req.events or {}) do sub[name] = true end subscribers[client.id] = sub reply(client, req, { ok = true, subscribed = req.events or {} }) end handlers["unsubscribe"] = function(client, req) if not authorized(client, req) then return end local sub = subscribers[client.id] if sub then for _, name in ipairs(req.events or {}) do sub[name] = nil end end reply(client, req, { ok = true }) end -- Escape hatch: run arbitrary Lua in this script's sandbox. Off by default. handlers["lua.exec"] = function(client, req) if not authorized(client, req) then return end if not ALLOW_LUA_EXEC then err(client, req, "disabled", "lua.exec is disabled in config") return end local fn, parse_err = loadstring(req.source or "") if not fn then err(client, req, "parse_error", tostring(parse_err)) return end local ok, result = pcall(fn) if ok then reply(client, req, { result = result }) else err(client, req, "runtime_error", tostring(result)) end end -- ── Server lifecycle ───────────────────────────────────────────────────────── function OnStart() server = Net.WSListen(cfg.port, { OnConnect = function(client) Log.Info(("Remote Access: client %d connected"):format(client.id)) -- announce the protocol up front so clients need no round-trip to verify send(client, { t = "hello", protocol = PROTOCOL_VERSION, auth_required = AUTH_TOKEN ~= "", }) end, OnMessage = function(client, payload, is_binary) if is_binary then err( client, { id = nil }, "no_binary", "binary frames are not supported" ) return end local ok, req = pcall(JSON.Parse, payload) if not ok or type(req) ~= "table" then err(client, { id = nil }, "bad_json", "could not parse a JSON object") return end local handler = handlers[req.t or ""] if not handler then err( client, req, "unknown_command", "unknown command: " .. tostring(req.t) ) return end local ran, e = pcall(handler, client, req) if not ran then err(client, req, "handler_error", tostring(e)) end end, OnClose = function(client) Log.Info(("Remote Access: client %d disconnected"):format(client.id)) subscribers[client.id] = nil authed[client.id] = nil end, }) Log.Info( ("Remote Access listening on ws://0.0.0.0:%d (auth=%s)"):format( cfg.port, AUTH_TOKEN ~= "" and "required" or "open" ) ) UI.Notify(("Remote Access: ws://0.0.0.0:%d"):format(cfg.port), "success") end function OnStop() if server then server:Stop() server = nil end subscribers, authed = {}, {} end -- Push subscribed streams once per tick, only when the underlying state changed. -- A closed client's :Send() is a silent no-op, so OnClose handles all cleanup. function OnTick() if not server or next(subscribers) == nil then return end local mouse_msg, window_msg, input_msg = nil, nil, nil local x, y = System.Mouse() if x ~= last_mouse_x or y ~= last_mouse_y then last_mouse_x, last_mouse_y = x, y local ok, m = pcall(JSON.Stringify, { t = "mouse", x = x, y = y }) if ok then mouse_msg = m end end local win = System.Window() if win.title ~= last_window_title then last_window_title = win.title local ok, m = pcall(JSON.Stringify, { t = "window", window = win }) if ok then window_msg = m end end local want_input = false for _, sub in pairs(subscribers) do if sub.input then want_input = true break end end if want_input then local ok, m = pcall(JSON.Stringify, { t = "input", keys = Input.GetActiveKeys(), modifiers = Input.GetModifiers(), }) if ok then input_msg = m end end for _, sub in pairs(subscribers) do if mouse_msg and sub.mouse then sub.client:Send(mouse_msg) end if window_msg and sub.window then sub.client:Send(window_msg) end if input_msg and sub.input then sub.client:Send(input_msg) end end end