@rebind.gg/mcp-server 0.2.3 → 0.3.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 CHANGED
@@ -5,7 +5,9 @@ Stdio MCP server that turns [Rebind](https://rebind.gg) into a computer-use tool
5
5
  **Requirements**
6
6
 
7
7
  - [Bun](https://bun.sh) >= 1.1 (the server runs via `bunx`)
8
- - The [Rebind app](https://rebind.gg/download) running the free [Remote Control](https://www.rebind.gg/marketplace/script_AAHteeDcYPGOT7GfXZ_OK) package (`rebind install @rebind/remote-control`)
8
+ - The [Rebind app](https://rebind.gg/download) running the free [Remote Control](https://www.rebind.gg/marketplace/script_AAHteeDcYPGOT7GfXZ_OK) package (`rebind install @rebind/remote-control --allow`)
9
+
10
+ The first time the server sends a command, Remote Control asks on that computer whether to allow **Rebind MCP server**. Allow it once; the name is remembered. Tool calls fail with `awaiting_approval` until you answer. Remote Control accepts connections from its own computer only unless **Local only** is turned off in its Options, which an agent on another computer or inside WSL needs. `run_lua`, `focus_window`, `position_window` and `read_page` also need **Allow custom Luau** turned on there; the server reads that setting at startup.
9
11
 
10
12
  ## Install
11
13
 
@@ -41,7 +43,7 @@ Any MCP client takes the stdio block directly:
41
43
  bunx @rebind.gg/mcp-server --selftest
42
44
  ```
43
45
 
44
- Connects, takes a capture, and reports the display — the fastest way to catch the #1 first-run failure (relay not loaded).
46
+ Connects, waits up to a minute for the approval prompt on a first run, takes a capture, and reports the display. It is the fastest way to catch the #1 first-run failure (relay not loaded).
45
47
 
46
48
  ## Tools
47
49
 
@@ -55,7 +57,7 @@ Connects, takes a capture, and reports the display — the fastest way to catch
55
57
  | `type` | Type text into the focused element. |
56
58
  | `key` | Press a key or combo, e.g. `Enter`, `LCtrl+L`, `LAlt+Tab`. |
57
59
  | `scroll` | Scroll at the cursor. Positive scrolls down, negative up. `unit` is `wheel` (default, fine wheel clicks) or `page` (one PageDown or PageUp per unit, capped at 10). |
58
- | `read_page` | Extract the focused browser's current page as structured text (URL, forms, interactive elements, plain text) via an address-bar bookmarklet; the full dump, including cleaned HTML, lands in the clipboard. The result is read back through AppleScript (`URL of active tab of front window`), so the tool is registered only when the relay host is macOS, and the front browser must answer that query. Safari is detected but blocks the bookmarklet path. |
60
+ | `read_page` | Extract the focused browser's current page as structured text (URL, forms, interactive elements, plain text) via an address-bar bookmarklet; the full dump, including cleaned HTML, lands in the clipboard. The result is read back through AppleScript (`URL of active tab of front window`), so the tool is registered only when the relay host is macOS and **Allow custom Luau** is on, and the front browser must answer that query. Safari is detected but blocks the bookmarklet path. |
59
61
  | `meditate` | Research a problem page until a reasoning model has the data to solve it: fetches the URL directly, pulls the script bundles behind a JS shell so its API endpoints are visible, fetches those endpoints and assets, and reads the live page with vision. The browser steps (rendering a JS shell, same-origin fetches through the page, and the vision pass) need a macOS relay; elsewhere it reads pages over HTTP only. Returns a markdown dossier plus JSON `{error, problem, requirements, plan, submittable, form, live_captcha_hint, confidence, sources, assets, pending_fetch, notes, cost_usd}`. `error` is non-null when no problem could be identified or the page could not be read; partial data is still returned. Gated behind `REBIND_ALLOW_DELEGATION=1` (spends OpenRouter credits). See the pipeline below. |
60
62
  | `run_lua` | Run a Luau script on the connected machine and return its `return` value as JSON — the fast path for any deterministic sequence (launch apps, place windows, shell commands, pixel search). |
61
63
  | `lua_docs` | Full Luau SDK type definitions for `run_lua`. |
@@ -84,7 +86,11 @@ Connects, takes a capture, and reports the display — the fastest way to catch
84
86
 
85
87
  **One coordinate space.** All results are reported in the pixel space of the last screenshot — the same space the model emits — so no rescaling math ever falls on the model.
86
88
 
87
- **Machine-aware orientation.** At startup the server probes the connected machine through the relay (OS, shortcut modifier, available shell tools, installed apps) and serves it as the MCP `instructions` block, along with a policy: prefer `run_lua` for deterministic work, keyboard over mouse, and screenshots last. The full Luau SDK type definitions are embedded inline so the model never has to guess `run_lua` APIs (including the coroutine-safe HID path — `HID.Down`/`Up`/`MoveTo`/`Combo` work in a one-shot chunk, `HID.Press` does not). The OS is read from the relay, not `process.platform`, so the guidance is correct even when the server and the relay run on different machines.
89
+ **Machine-aware orientation.** At startup the server reads the OS from Remote Control's `hello` banner and, when custom Luau is allowed, probes the connected machine through the relay (available shell tools, installed apps) and serves it as the MCP `instructions` block, along with a policy: prefer `run_lua` for deterministic work, keyboard over mouse, and screenshots last. The full Luau SDK type definitions are embedded inline so the model never has to guess `run_lua` APIs (including the coroutine-safe HID path: `HID.Down`/`Up`/`MoveTo`/`Combo` work in a one-shot chunk, `HID.Press` does not). The OS is read from the relay, not `process.platform`, so the guidance is correct even when the server and the relay run on different machines.
90
+
91
+ ## Save what worked
92
+
93
+ When the user wants a working sequence kept, the server's instructions tell the agent to save it as a Rebind script that runs with no agent: `rebind init <dir>`, the sequence in `main.luau` bound to a key, `[runtime] permissions` in `rebind.toml`, then `rebind check <dir>`, `rebind pack <dir>`, `rebind install <name>.rbp --allow` (`--force` to replace) and `rebind script start packages/<name>/main.luau`, on the computer running Rebind.
88
94
 
89
95
  ## Solve pipeline
90
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebind.gg/mcp-server",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server exposing Rebind as computer-use tools: screenshot, verified click, type, key, scroll, and window control over the Remote Control protocol.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@ import { appendFileSync } from "node:fs";
10
10
  import { homedir } from "node:os";
11
11
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
- import { RebindRemote } from "@rebind.gg/client-ts";
13
+ import { RebindRemote, ServerError } from "@rebind.gg/client-ts";
14
14
  import { z } from "zod";
15
15
  import pkg from "../package.json";
16
16
  import { mapToImage } from "./coords.ts";
@@ -29,6 +29,7 @@ import {
29
29
  PROBE_CLIS_CMD,
30
30
  probeAppsCmd,
31
31
  RUN_LUA_DESC,
32
+ SAVE_WORK_DOCTRINE,
32
33
  toolPriority,
33
34
  } from "./prompts.ts";
34
35
  import { CLICK_EPS_PX, Session } from "./session.ts";
@@ -266,16 +267,32 @@ function assetsBlock(
266
267
  .join("\n\n");
267
268
  }
268
269
 
270
+ // The SDK as run_lua scripts see it: the typedef without its rb block, so the
271
+ // model writes the v1 names every relay version accepts (rb needs 3.9.0).
272
+ // src/rebind.d.luau is a byte-identical copy of packages/lua-sdk/types/
273
+ // rebind.d.luau shipped in the package; tests/lua-docs-sync.test.ts guards it.
274
+ async function v1Sdk(): Promise<string> {
275
+ const typedef = await Bun.file(
276
+ new URL("./rebind.d.luau", import.meta.url),
277
+ ).text();
278
+ return typedef.replace(/^-- rb\n[\s\S]*?(?=^-- v1 names\n)/m, "");
279
+ }
280
+
269
281
  // Server-level orientation (MCP `instructions`), built at startup. Every fact
270
282
  // is probed THROUGH the relay connection — process.platform is the wrong
271
283
  // signal because this process may run on a different machine than the relay.
272
284
  // Probes are best-effort: a locked-down remote (no exec permission) just
273
285
  // yields a shorter card. Machine facts and general technique only.
274
- // The probed OS is returned alongside: tools that pick a shortcut modifier
286
+ // The OS comes from the hello banner when the server reports it (Remote
287
+ // Control 0.3.0+), so it is right even with lua.exec off; older servers fall
288
+ // back to a uname probe. When the banner says lua.exec is off, the probes and
289
+ // read_page (both built on lua.exec) are skipped.
290
+ // The OS is returned alongside: tools that pick a shortcut modifier
275
291
  // (read_page) need it, and re-probing would be a second relay round-trip.
276
292
  async function buildInstructions(
277
293
  client: RebindRemote,
278
- ): Promise<{ instructions: string; os: OS }> {
294
+ hello: Hello,
295
+ ): Promise<{ instructions: string; os: OS; hasLua: boolean }> {
279
296
  const sh = async (cmd: string): Promise<string> => {
280
297
  try {
281
298
  const r = await client.luaExec(
@@ -286,23 +303,27 @@ async function buildInstructions(
286
303
  return "";
287
304
  }
288
305
  };
289
- const uname = await sh("uname");
290
- const os: OS = /darwin/i.test(uname)
291
- ? "macOS"
292
- : /linux/i.test(uname)
293
- ? "Linux"
294
- : "Windows";
295
- const clis = os === "Windows" ? "" : await sh(PROBE_CLIS_CMD);
296
- const apps = os === "Windows" ? "" : await sh(probeAppsCmd(os));
306
+ const hasLua = hello.gates?.lua !== false;
307
+ const os: OS =
308
+ HELLO_OS[hello.platform ?? ""] ??
309
+ (await (async () => {
310
+ const uname = hasLua ? await sh("uname") : "";
311
+ return /darwin/i.test(uname)
312
+ ? "macOS"
313
+ : /linux/i.test(uname)
314
+ ? "Linux"
315
+ : "Windows";
316
+ })());
317
+ const probe = hasLua && os !== "Windows";
318
+ const clis = probe ? await sh(PROBE_CLIS_CMD) : "";
319
+ const apps = probe ? await sh(probeAppsCmd(os)) : "";
297
320
  // Embed the full Luau SDK inline so the model never has to guess run_lua APIs
298
321
  // or make a lua_docs round-trip. It carries the coroutine-safety notes that
299
322
  // matter for HID: HID.Press sleeps and is coroutine-only, but HID.Down/Up/
300
323
  // MoveTo/Combo are synchronous and DO work in a one-shot chunk — so a mouse
301
324
  // click (e.g. ticking a checkbox) is scriptable as
302
325
  // `HID.MoveTo(x,y); HID.Down("Mouse1"); HID.Up("Mouse1")`.
303
- const sdk = await Bun.file(
304
- new URL("./rebind.d.luau", import.meta.url),
305
- ).text();
326
+ const sdk = await v1Sdk();
306
327
  const instructions = [
307
328
  `You control a real ${os} computer through Rebind. Keyboard shortcuts use ${modifierNote(os)}.`,
308
329
  clis
@@ -311,8 +332,9 @@ async function buildInstructions(
311
332
  apps
312
333
  ? `GUI applications installed (open via ${openAppHow(os)}): ${apps}`
313
334
  : "",
314
- `Work in as few steps as possible. ${DETERMINISTIC_WORK_DOCTRINE}`,
315
- toolPriority(true),
335
+ `Work in as few steps as possible.${hasLua ? ` ${DETERMINISTIC_WORK_DOCTRINE}` : ""}`,
336
+ SAVE_WORK_DOCTRINE,
337
+ toolPriority(hasLua),
316
338
  BATCHING_DOCTRINE,
317
339
  "When you do need to see the screen, prefer screenshot_window over a full-display screenshot — a window shot costs roughly a quarter of the image tokens. Every screenshot stays in your context for the rest of the session, so each one you take keeps costing you on every later turn: verify with values (clipboard, file contents, run_lua reads) where possible.",
318
340
  guiTips(os),
@@ -321,14 +343,34 @@ async function buildInstructions(
321
343
  ]
322
344
  .filter(Boolean)
323
345
  .join("\n");
324
- return { instructions, os };
346
+ return { instructions, os, hasLua };
325
347
  }
326
348
 
349
+ /** the fields of the Remote Control hello banner this server reads */
350
+ type Hello = { platform?: string; gates?: { lua?: boolean } };
351
+
352
+ const HELLO_OS: Record<string, OS> = {
353
+ macos: "macOS",
354
+ linux: "Linux",
355
+ windows: "Windows",
356
+ };
357
+
358
+ /** the name Remote Control shows in its approval prompt and remembers */
359
+ const APP_NAME = "Rebind MCP server";
360
+
327
361
  async function main() {
328
362
  const url = process.env.REBIND_URL ?? "ws://127.0.0.1:19561";
329
- const client = new RebindRemote(url, {
363
+ // name this client on every connect, reconnects included: Remote Control
364
+ // approves programs by the name they declare in hello. setState fires before
365
+ // connect() resolves, so the hello goes out ahead of any tool call.
366
+ let hello: Promise<Hello> = Promise.resolve({});
367
+ const client: RebindRemote = new RebindRemote(url, {
330
368
  token: process.env.REBIND_TOKEN ?? "",
331
369
  timeoutMs: 15000,
370
+ onStateChange: (s) => {
371
+ if (s !== "connected") return;
372
+ hello = client.call<Hello>("hello", { app: APP_NAME }).catch(() => ({}));
373
+ },
332
374
  });
333
375
 
334
376
  // --selftest: verify the relay is reachable and capturing, then exit. Used by
@@ -337,15 +379,41 @@ async function main() {
337
379
  if (process.argv.includes("--selftest")) {
338
380
  try {
339
381
  await client.connect();
340
- const cap = await client.screenCapture({});
382
+ await hello;
383
+ // a first connection waits on the approval prompt in Remote Control:
384
+ // retry for up to a minute while the person decides.
385
+ const deadline = Date.now() + 60_000;
386
+ let cap: Awaited<ReturnType<RebindRemote["screenCapture"]>>;
387
+ for (;;) {
388
+ try {
389
+ cap = await client.screenCapture({});
390
+ break;
391
+ } catch (e) {
392
+ const waiting =
393
+ e instanceof ServerError && e.code === "awaiting_approval";
394
+ if (!waiting || Date.now() + 2000 > deadline) throw e;
395
+ console.error(
396
+ `selftest: allow "${APP_NAME}" in the Remote Control prompt on that computer...`,
397
+ );
398
+ await sleep(2000);
399
+ }
400
+ }
341
401
  console.error(
342
402
  `selftest OK: relay ${url}, display ${cap.display.index} ${cap.display.width}x${cap.display.height}`,
343
403
  );
344
404
  process.exit(0);
345
405
  } catch (e) {
406
+ if (e instanceof ServerError) {
407
+ console.error(
408
+ e.code === "awaiting_approval" || e.code === "not_approved"
409
+ ? `selftest FAILED: "${APP_NAME}" was not allowed in Remote Control's prompt on ${url}. (${e.message})`
410
+ : `selftest FAILED: Remote Control on ${url} refused the capture. (${e.message})`,
411
+ );
412
+ process.exit(1);
413
+ }
346
414
  console.error(
347
415
  `selftest FAILED: Rebind relay not reachable on ${url}.\n` +
348
- `Start Rebind and the Remote Control package (rebind install @rebind/remote-control).\n(${e})`,
416
+ `Start Rebind and the Remote Control package (rebind install @rebind/remote-control --allow).\n(${e})`,
349
417
  );
350
418
  process.exit(1);
351
419
  }
@@ -355,7 +423,10 @@ async function main() {
355
423
 
356
424
  const session = new Session(client);
357
425
 
358
- const { instructions, os } = await buildInstructions(client);
426
+ const { instructions, os, hasLua } = await buildInstructions(
427
+ client,
428
+ await hello,
429
+ );
359
430
  const server = new McpServer(
360
431
  { name: "rebind", version: pkg.version },
361
432
  { instructions },
@@ -1071,9 +1142,9 @@ return { ok = Window.IsActive(h), name = name }
1071
1142
  );
1072
1143
 
1073
1144
  // read_page finds the front app through App.Front and reads its result back
1074
- // through AppleScript (tabUrl): both exist only on a macOS relay. A failed
1075
- // OS probe reads as "Windows", so this never offers the tool where it fails.
1076
- if (os === "macOS") {
1145
+ // through AppleScript (tabUrl), both over lua.exec: offered only on a macOS
1146
+ // relay with lua.exec on, so never where it fails.
1147
+ if (os === "macOS" && hasLua) {
1077
1148
  register(
1078
1149
  "read_page",
1079
1150
  {
@@ -1146,10 +1217,7 @@ return { ok = Window.IsActive(h), name = name }
1146
1217
  {
1147
1218
  description: LUA_DOCS_DESC,
1148
1219
  },
1149
- // byte-identical copy of packages/lua-sdk/types/rebind.d.luau, shipped in
1150
- // the package; tests/lua-docs-sync.test.ts guards against drift
1151
- async () =>
1152
- text(await Bun.file(new URL("./rebind.d.luau", import.meta.url)).text()),
1220
+ async () => text(await v1Sdk()),
1153
1221
  );
1154
1222
 
1155
1223
  // Server-side subagents. Gated: a stock install ships without them. They make
package/src/prompts.ts CHANGED
@@ -69,6 +69,10 @@ export function guiTips(os: OS): string {
69
69
  export const DETERMINISTIC_WORK_DOCTRINE =
70
70
  "run_lua is your PRIMARY tool — reach for it FIRST and use it aggressively. It exposes the ENTIRE machine SDK (every namespace below), so almost anything a task needs — file operations, text processing, arithmetic, clipboard, launching and positioning apps, shell commands, reading the screen — is ONE run_lua call, never a click loop. Do not operate calculators, browsers, or file managers through the GUI when a script can do the work. Fall back to screenshot/click ONLY for state that exists purely on screen and cannot be scripted.";
71
71
 
72
+ /** MCP server only (the bench keeps its own prompt): keep what worked. */
73
+ export const SAVE_WORK_DOCTRINE =
74
+ "When a sequence works and the user wants to keep it, save it as a Rebind script so it runs again with no agent and no tokens. On the computer running Rebind: `rebind init <dir>` scaffolds rebind.toml and main.luau; put the sequence in main.luau bound to a key (Bind or OnDown; hooks never fire inside run_lua, they do in an installed script); add `[runtime] permissions = [...]` to rebind.toml listing only what it uses; then run `rebind check <dir>`, `rebind pack <dir>` (writes <name>.rbp in the current directory), `rebind install <name>.rbp --allow` (it prints what the package asks for; add --force to replace an earlier install) and `rebind script start packages/<name>/main.luau`. Tell the user which key runs it, and that it runs until Rebind restarts and can be started again from Rebind's scripts list.";
75
+
72
76
  // --- tool descriptions (shared only where the underlying behavior is identical
73
77
  // across both surfaces: run_lua, lua_docs, and key all hit the same relay) ---
74
78
 
package/src/rebind.d.luau CHANGED
@@ -1,18 +1,28 @@
1
- -- rebind sdk type definitions for luau language server
2
- -- provides autocomplete, hover docs, and type checking for all sdk namespaces.
3
- -- reference this file in your .luaurc: { "languageMode": "strict", "paths": ["types"] }
1
+ -- Rebind SDK type definitions for the Luau language server: autocomplete,
2
+ -- hover docs and type checking for every SDK function.
3
+ -- Reference this file in your .luaurc: { "languageMode": "strict", "paths": ["types"] }
4
+ --
5
+ -- The rb names need Rebind 3.9.0 or later; the v1 names work on every
6
+ -- version.
4
7
 
5
- -- handle types
8
+ -- handle types. A handle answers to its v1 method names on every version,
9
+ -- and to their snake_case forms from Rebind 3.9.0.
6
10
 
7
11
  export type TaskHandle = {
8
12
  Cancel: (self: TaskHandle) -> (),
9
13
  IsRunning: (self: TaskHandle) -> boolean,
14
+ stop: (self: TaskHandle) -> (),
15
+ is_running: (self: TaskHandle) -> boolean,
10
16
  }
11
17
 
12
18
  export type TimerHandle = {
13
19
  Cancel: (self: TimerHandle) -> (),
14
20
  Pause: (self: TimerHandle) -> (),
15
21
  Resume: (self: TimerHandle) -> (),
22
+ stop: (self: TimerHandle) -> (),
23
+ pause: (self: TimerHandle) -> (),
24
+ resume: (self: TimerHandle) -> (),
25
+ is_running: (self: TimerHandle) -> boolean,
16
26
  }
17
27
 
18
28
  export type MacroHandle = {
@@ -21,18 +31,34 @@ export type MacroHandle = {
21
31
  Resume: (self: MacroHandle) -> (),
22
32
  IsPlaying: (self: MacroHandle) -> boolean,
23
33
  GetProgress: (self: MacroHandle) -> number,
34
+ -- coroutine-only: waits until the macro finishes
35
+ Wait: (self: MacroHandle) -> (),
36
+ stop: (self: MacroHandle) -> (),
37
+ pause: (self: MacroHandle) -> (),
38
+ resume: (self: MacroHandle) -> (),
39
+ is_playing: (self: MacroHandle) -> boolean,
40
+ get_progress: (self: MacroHandle) -> number,
41
+ -- coroutine-only: waits until the macro finishes
42
+ wait: (self: MacroHandle) -> (),
24
43
  }
25
44
 
26
45
  export type BindHandle = {
27
46
  unbind: (self: BindHandle) -> (),
47
+ stop: (self: BindHandle) -> (),
28
48
  disable: (self: BindHandle) -> (),
29
49
  enable: (self: BindHandle) -> (),
30
50
  enabled: boolean,
31
51
  }
32
52
 
53
+ -- returned by rb.on (Rebind 3.9.0 and later); stop removes the handler
54
+ export type EventHandle = {
55
+ stop: (self: EventHandle) -> (),
56
+ }
57
+
33
58
  export type ServerHandle = {
34
59
  port: number,
35
60
  Stop: (self: ServerHandle) -> (),
61
+ stop: (self: ServerHandle) -> (),
36
62
  }
37
63
 
38
64
  -- UDP: socket handle returned by Net.UDPListen. SendTo sends from the bound
@@ -41,6 +67,8 @@ export type UdpHandle = {
41
67
  port: number,
42
68
  SendTo: (self: UdpHandle, ip: string, port: number, bytes: string) -> boolean,
43
69
  Stop: (self: UdpHandle) -> (),
70
+ send_to: (self: UdpHandle, ip: string, port: number, bytes: string) -> boolean,
71
+ stop: (self: UdpHandle) -> (),
44
72
  }
45
73
 
46
74
  -- WebSocket: server-side per-connection handle passed to OnConnect/OnMessage/OnClose.
@@ -50,6 +78,9 @@ export type WsClient = {
50
78
  Send: (self: WsClient, text: string) -> (),
51
79
  SendBinary: (self: WsClient, bytes: string) -> (),
52
80
  Close: (self: WsClient) -> (),
81
+ send: (self: WsClient, text: string) -> (),
82
+ send_binary: (self: WsClient, bytes: string) -> (),
83
+ close: (self: WsClient) -> (),
53
84
  }
54
85
 
55
86
  -- WebSocket: server handle returned by Net.WSListen.
@@ -59,6 +90,10 @@ export type WsServerHandle = {
59
90
  BroadcastBinary: (self: WsServerHandle, bytes: string) -> (),
60
91
  ClientCount: (self: WsServerHandle) -> number,
61
92
  Stop: (self: WsServerHandle) -> (),
93
+ broadcast: (self: WsServerHandle, text: string) -> (),
94
+ broadcast_binary: (self: WsServerHandle, bytes: string) -> (),
95
+ client_count: (self: WsServerHandle) -> number,
96
+ stop: (self: WsServerHandle) -> (),
62
97
  }
63
98
 
64
99
  -- WebSocket: outbound-connection handle returned by Net.WSConnect.
@@ -66,21 +101,32 @@ export type WsConnectHandle = {
66
101
  Send: (self: WsConnectHandle, text: string) -> (),
67
102
  SendBinary: (self: WsConnectHandle, bytes: string) -> (),
68
103
  Close: (self: WsConnectHandle) -> (),
104
+ send: (self: WsConnectHandle, text: string) -> (),
105
+ send_binary: (self: WsConnectHandle, bytes: string) -> (),
106
+ close: (self: WsConnectHandle) -> (),
69
107
  }
70
108
 
71
109
  -- WebSocket: server event-handler table passed to Net.WSListen.
72
110
  -- All fields are optional; handlers that are absent are never called.
111
+ -- From Rebind 3.9.0 each handler is also read under its snake_case name.
73
112
  export type WsListenHandlers = {
74
113
  OnConnect: ((client: WsClient) -> ())?,
75
114
  OnMessage: ((client: WsClient, payload: string, isBinary: boolean) -> ())?,
76
115
  OnClose: ((client: WsClient) -> ())?,
116
+ on_connect: ((client: WsClient) -> ())?,
117
+ on_message: ((client: WsClient, payload: string, is_binary: boolean) -> ())?,
118
+ on_close: ((client: WsClient) -> ())?,
77
119
  }
78
120
 
79
121
  -- WebSocket: client event-handler table passed to Net.WSConnect.
122
+ -- From Rebind 3.9.0 each handler is also read under its snake_case name.
80
123
  export type WsConnectHandlers = {
81
124
  OnOpen: (() -> ())?,
82
125
  OnMessage: ((payload: string, isBinary: boolean) -> ())?,
83
126
  OnClose: (() -> ())?,
127
+ on_open: (() -> ())?,
128
+ on_message: ((payload: string, is_binary: boolean) -> ())?,
129
+ on_close: (() -> ())?,
84
130
  }
85
131
 
86
132
  export type PipeHandle = {
@@ -90,6 +136,24 @@ export type PipeHandle = {
90
136
  Read: (self: PipeHandle) -> string?,
91
137
  Write: (self: PipeHandle, data: string) -> (),
92
138
  Close: (self: PipeHandle) -> (),
139
+ read: (self: PipeHandle) -> string?,
140
+ write: (self: PipeHandle, data: string) -> (),
141
+ close: (self: PipeHandle) -> (),
142
+ }
143
+
144
+ export type SoundHandle = {
145
+ Stop: (self: SoundHandle) -> (),
146
+ Pause: (self: SoundHandle) -> (),
147
+ Resume: (self: SoundHandle) -> (),
148
+ IsPlaying: (self: SoundHandle) -> boolean,
149
+ SetVolume: (self: SoundHandle, vol: number) -> (),
150
+ GetVolume: (self: SoundHandle) -> number,
151
+ stop: (self: SoundHandle) -> (),
152
+ pause: (self: SoundHandle) -> (),
153
+ resume: (self: SoundHandle) -> (),
154
+ is_playing: (self: SoundHandle) -> boolean,
155
+ set_volume: (self: SoundHandle, vol: number) -> (),
156
+ get_volume: (self: SoundHandle) -> number,
93
157
  }
94
158
 
95
159
  -- data types
@@ -178,6 +242,122 @@ export type HttpRequestOptions = {
178
242
  timeout: number?,
179
243
  }
180
244
 
245
+ export type ExecOptions = {
246
+ -- milliseconds; default 5000. The process is killed on expiry and Exec errors.
247
+ timeout: number?,
248
+ -- working directory for the command; default: the host process's cwd.
249
+ cwd: string?,
250
+ }
251
+
252
+ export type ExecResult = {
253
+ exit: number,
254
+ stdout: string,
255
+ stderr: string,
256
+ }
257
+
258
+ export type ExecDetachedOptions = {
259
+ cwd: string?,
260
+ }
261
+
262
+ -- a joypad state; every field is optional and defaults to neutral. buttons
263
+ -- lists pressed buttons 1-32, hat is 0 (up) to 7 clockwise (nil = centered),
264
+ -- x/y/z/rz are -1..1 (0 = center), slider and dial are 0..1.
265
+ export type JoypadState = {
266
+ buttons: { number }?,
267
+ hat: number?,
268
+ x: number?,
269
+ y: number?,
270
+ z: number?,
271
+ rz: number?,
272
+ slider: number?,
273
+ dial: number?,
274
+ }
275
+
276
+ export type ProcessEntry = {
277
+ pid: number,
278
+ name: string,
279
+ }
280
+
281
+ export type PixelMatch = {
282
+ x: number,
283
+ y: number,
284
+ }
285
+
286
+ export type MonitorEntry = {
287
+ index: number,
288
+ x: number,
289
+ y: number,
290
+ width: number,
291
+ height: number,
292
+ primary: boolean,
293
+ }
294
+
295
+ export type CaptureOptions = {
296
+ -- 1-based display index; default: the display under the cursor.
297
+ display: number?,
298
+ -- display-local logical coords; default: the whole display.
299
+ region: { x: number, y: number, w: number, h: number }?,
300
+ -- downscale so the long edge is at most this; default and 0: native.
301
+ max_edge: number?,
302
+ }
303
+
304
+ export type CaptureResult = {
305
+ image: string, -- base64 PNG
306
+ width: number,
307
+ height: number,
308
+ display: MonitorEntry,
309
+ cursor: { x: number, y: number },
310
+ }
311
+
312
+ export type FindImageOptions = {
313
+ display: number?,
314
+ region: { x: number, y: number, w: number, h: number }?,
315
+ -- 0.0 exact .. 1.0, per-channel fraction of 255 (same scale as SearchForColor).
316
+ tolerance: number?,
317
+ -- ratios to resample the needle by before scanning, tried in order; default { 1 }.
318
+ scales: { number }?,
319
+ }
320
+
321
+ export type ImageMatch = {
322
+ x: number,
323
+ y: number,
324
+ width: number,
325
+ height: number,
326
+ }
327
+
328
+ export type AppInfo = {
329
+ name: string,
330
+ pid: number,
331
+ }
332
+
333
+ export type PlayOptions = {
334
+ volume: number?,
335
+ loop: boolean?,
336
+ }
337
+
338
+ export type DialogMessageOptions = {
339
+ title: string?,
340
+ level: string?,
341
+ }
342
+
343
+ export type DialogFileFilter = {
344
+ name: string,
345
+ extensions: { string },
346
+ }
347
+
348
+ export type DialogFileOptions = {
349
+ title: string?,
350
+ location: string?,
351
+ filters: { DialogFileFilter }?,
352
+ }
353
+
354
+ export type RegexMatch = {
355
+ match: string,
356
+ captures: { string },
357
+ start: number,
358
+ finish: number,
359
+ }
360
+
181
361
  -- macro action types
182
362
 
183
363
  export type MacroMoveAction = {
@@ -186,6 +366,8 @@ export type MacroMoveAction = {
186
366
  delay: number,
187
367
  }
188
368
 
369
+ -- from Rebind 3.9.0, holdMs and charDelay are also read as hold_ms and
370
+ -- char_delay
189
371
  export type MacroExplicitAction = {
190
372
  action: string,
191
373
  code: string?,
@@ -197,12 +379,15 @@ export type MacroExplicitAction = {
197
379
  amount: number?,
198
380
  holdMs: number?,
199
381
  charDelay: number?,
382
+ hold_ms: number?,
383
+ char_delay: number?,
200
384
  delay: number,
201
385
  }
202
386
 
203
387
  export type MacroAction = MacroMoveAction | MacroExplicitAction
204
388
 
205
- -- ui widget option types
389
+ -- ui widget option types; from Rebind 3.9.0, showIf and maxLength are also
390
+ -- read as show_if and max_length
206
391
 
207
392
  export type WidgetOpts = {
208
393
  label: string?,
@@ -210,6 +395,7 @@ export type WidgetOpts = {
210
395
  group: string?,
211
396
  tab: string?,
212
397
  showIf: string?,
398
+ show_if: string?,
213
399
  }
214
400
 
215
401
  export type ToggleOpts = WidgetOpts
@@ -228,6 +414,7 @@ export type SelectOpts = WidgetOpts
228
414
  export type TextOpts = WidgetOpts & {
229
415
  placeholder: string?,
230
416
  maxLength: number?,
417
+ max_length: number?,
231
418
  }
232
419
 
233
420
  export type ColorOpts = WidgetOpts
@@ -264,6 +451,453 @@ export type RebindInfo = {
264
451
  platform: string,
265
452
  }
266
453
 
454
+ -- options of the rb calls that take one options table (Rebind 3.9.0 and later)
455
+
456
+ export type RbHttpOptions = {
457
+ headers: { [string]: string }?,
458
+ timeout: number?,
459
+ }
460
+
461
+ export type RbPlayOptions = {
462
+ -- playback speed multiplier
463
+ speed: number?,
464
+ -- "parallel" (default, plays alongside running macros) | "replace"
465
+ -- (stops all running macros first)
466
+ mode: string?,
467
+ }
468
+
469
+ export type RbExecDetachedOptions = {
470
+ args: { string }?,
471
+ cwd: string?,
472
+ }
473
+
474
+ -- fields left out keep their current value
475
+ export type RbWindowRect = {
476
+ x: number?,
477
+ y: number?,
478
+ width: number?,
479
+ height: number?,
480
+ }
481
+
482
+ export type RbBindOptions = {
483
+ -- false lets the key through to the system; default: the key is taken
484
+ swallow: boolean?,
485
+ -- checked on each press; false skips the bind for that press
486
+ when: ((device: InputDevice?) -> boolean)?,
487
+ -- runs when the key is released
488
+ release: ((device: InputDevice?) -> ())?,
489
+ -- nickname or id: the bind answers only to that device
490
+ device: string?,
491
+ }
492
+
493
+ -- rb.ai and rb.phone are Rebind Pro (a 7-day trial starts on first use).
494
+ -- Each rb.ai call waits for the model, so call it inside rb.run; its cost
495
+ -- counts against the monthly AI allowance. A script that declares
496
+ -- permissions needs ai, or ai:decide, ai:find or ai:read. A window option
497
+ -- names a window by title (or app name on macOS); the call reads the screen
498
+ -- pixels inside that window's rectangle, through the screen: permission.
499
+
500
+ export type AiDecideOptions = {
501
+ choices: { string },
502
+ -- send the display under the cursor (the target window under screen:target)
503
+ screen: boolean?,
504
+ -- send this window instead
505
+ window: string?,
506
+ }
507
+
508
+ export type AiFindOptions = {
509
+ -- look in this window; default: the display under the cursor
510
+ window: string?,
511
+ }
512
+
513
+ export type AiReadOptions = {
514
+ window: string?,
515
+ -- read only around this point (screen coordinates, such as rb.ai.find's)
516
+ at: AiPoint?,
517
+ }
518
+
519
+ -- screen coordinates, for rb.hid.move_to
520
+ export type AiPoint = {
521
+ x: number,
522
+ y: number,
523
+ }
524
+
525
+ export type RbAi = {
526
+ -- picks one of opts.choices; returns it and the model's confidence (0 to 1)
527
+ decide: (question: string, opts: AiDecideOptions) -> (string, number),
528
+ -- the centre of what is described, or nil when it is not visible
529
+ find: (what: string, opts: AiFindOptions?) -> AiPoint?,
530
+ -- the text described, as it appears on screen
531
+ read: (what: string, opts: AiReadOptions?) -> string,
532
+ }
533
+
534
+ export type PhoneResult = {
535
+ delivered: boolean,
536
+ reason: string?,
537
+ }
538
+
539
+ export type RbPhone = {
540
+ -- no phone app yet: returns { delivered = false, reason = "no phone connected" }
541
+ notify: (text: string, title: string?) -> PhoneResult,
542
+ }
543
+
544
+ -- events for rb.on (Rebind 3.9.0 and later), with the arguments each
545
+ -- handler receives. For down, up, move and scroll, a handler that returns
546
+ -- false swallows the input.
547
+ export type Event =
548
+ | "start" -- ()
549
+ | "stop" -- ()
550
+ | "focus" -- (window: WindowInfo)
551
+ | "blur" -- (window: WindowInfo)
552
+ | "tick" -- (dt_ms: number)
553
+ | "error" -- (message: string)
554
+ | "reload" -- ()
555
+ | "down" -- (key: string, device: InputDevice?)
556
+ | "up" -- (key: string, duration_ms: number, device: InputDevice?)
557
+ | "move" -- (dx: number, dy: number, device: InputDevice?) hardware mode only
558
+ | "scroll" -- (delta: number, device: InputDevice?)
559
+ | "scroll_h" -- (delta: number)
560
+ | "input_down" -- (key: string, device: InputDevice?)
561
+ | "input_up" -- (key: string, duration_ms: number, device: InputDevice?)
562
+ | "input_move" -- (dx: number, dy: number, device: InputDevice?)
563
+ | "input_scroll" -- (delta: number, device: InputDevice?)
564
+
565
+ -- valid key names for input events and binds:
566
+ --
567
+ -- letters: A-Z
568
+ -- numbers: 0-9
569
+ -- function: F1-F12
570
+ -- mouse: Mouse1 (left), Mouse2 (right), Mouse3 (middle), Mouse4, Mouse5
571
+ -- modifiers: LCtrl, RCtrl, LShift, RShift, LAlt, RAlt, LWin, RWin
572
+ -- navigation: Up, Down, Left, Right, Home, End, PageUp, PageDown, Insert, Delete
573
+ -- editing: Backspace, Tab, Enter, Space, Escape
574
+ -- punctuation: Minus, Equal, LeftBrace, RightBrace, Backslash, Semicolon,
575
+ -- Apostrophe, Grave, Comma, Period, Slash
576
+ -- locks: CapsLock, ScrollLock, NumLock
577
+ -- system: PrintScreen, Pause, Menu
578
+ -- numpad: Kp0-Kp9, KpPlus, KpMinus, KpMultiply, KpDivide, KpEnter, KpDot
579
+ --
580
+ -- HID output only:
581
+ -- function: F13-F24
582
+ -- media: MediaNext, MediaPrev, MediaStop, MediaPlay, VolumeUp, VolumeDown, Mute
583
+ -- aliases: Numpad0-9, NP0-9, NPAdd, NPSubtract, NPMultiply, NPDivide, NPEnter,
584
+ -- NPDecimal, Ctrl, Shift, Alt, Win, Meta, Del, PgUp, PgDn, etc.
585
+
586
+ -- rb
587
+ -- The scripting surface, for scripts that declare min_sdk=3.9.0 or later.
588
+ -- Every name is the snake_case of a v1 name (HID.MoveTo is rb.hid.move_to).
589
+ -- Hooks are rb.on(event, fn) and key names passed to rb ignore case.
590
+
591
+ declare rb: {
592
+ -- runtime identity: script name, versions, platform
593
+ info: RebindInfo,
594
+ -- runs fn as a coroutine, where rb.sleep and other coroutine-only calls work
595
+ run: (fn: () -> ()) -> TaskHandle,
596
+ -- coroutine-only: call inside rb.run
597
+ sleep: (ms: number) -> (),
598
+ after: (ms: number, fn: () -> ()) -> TaskHandle,
599
+ -- wraps fn so it runs on the coroutine scheduler; when auto-spawned the call
600
+ -- returns false instead of fn's results.
601
+ async: (fn: (...any) -> ...any) -> (...any) -> ...any,
602
+ -- adds a handler for an event; handlers run in the order they were added.
603
+ -- returns a handle whose stop removes the handler.
604
+ on: (event: Event, fn: (...any) -> boolean?) -> EventHandle,
605
+ -- rb.bind(key, fn, options) takes the key unless options.swallow is false.
606
+ -- the callback's return value is ignored.
607
+ bind: typeof(setmetatable({} :: {
608
+ toggle: (key: string, ui_id: string) -> BindHandle,
609
+ remap: (from: string, to: string) -> BindHandle,
610
+ }, {} :: {
611
+ __call: (self: any, key: string, fn: ((device: InputDevice?) -> ...any)?, options: RbBindOptions?) -> BindHandle,
612
+ })),
613
+ -- each level takes any number of values, joined like print
614
+ log: {
615
+ info: (...any) -> (),
616
+ warn: (...any) -> (),
617
+ error: (...any) -> (),
618
+ debug: (...any) -> (),
619
+ },
620
+ system: {
621
+ time: () -> number,
622
+ mouse: () -> (number, number),
623
+ screen: () -> (number, number),
624
+ window: () -> WindowInfo,
625
+ -- runs cmd through the shell (sh -c / cmd.exe), output capped at 64KB per
626
+ -- stream. Requires the 'exec' permission. Inside rb.run it runs off the
627
+ -- tick thread; elsewhere it blocks the tick thread and warns once.
628
+ exec: (cmd: string, options: ExecOptions?) -> ExecResult,
629
+ -- fire-and-forget spawn (no shell, argv array); returns the pid.
630
+ exec_detached: (cmd: string, options: RbExecDetachedOptions?) -> number,
631
+ -- types text through the host OS (Unicode aware), never through the HID
632
+ -- transport. raises when the OS refuses the injection.
633
+ type_text: (text: string) -> (),
634
+ },
635
+ hid: {
636
+ down: (code: string) -> (),
637
+ up: (code: string) -> (),
638
+ -- coroutine-only (sleeps for hold_ms): call inside rb.run. From a plain
639
+ -- chunk use down and up, or combo.
640
+ press: (code: string, hold_ms: number?) -> (),
641
+ -- fires a chord in one synchronous call (press left to right, release
642
+ -- right to left). modifier tokens accept macOS aliases: "cmd",
643
+ -- "opt"/"option", plus "ctrl"/"shift"/"alt"/"win".
644
+ combo: (code: string) -> (),
645
+ -- instant whole-string insertion; typewriter types one character at a
646
+ -- time and is coroutine-only.
647
+ type: (text: string) -> (),
648
+ typewriter: (text: string, delay_ms: number?) -> (),
649
+ move: (dx: number, dy: number) -> (),
650
+ move_to: (x: number, y: number) -> (),
651
+ scroll: (amount: number) -> (),
652
+ set_mouse_mode: (mode: string) -> (),
653
+ get_mouse_mode: () -> string,
654
+ -- keyboard HID usage for a key name, or nil (media keys return nil). down,
655
+ -- up, press and combo also accept "usage:N" (decimal or 0x hex, 0x04-0xE7).
656
+ usage: (name: string) -> number?,
657
+ -- sets the whole joypad state (last write wins across scripts). Needs a
658
+ -- Rebind Link whose USB profile has a joypad; raises otherwise.
659
+ joypad: (state: JoypadState?) -> (),
660
+ -- back to neutral; the relay also does this when a script stops or errors
661
+ joypad_reset: () -> (),
662
+ },
663
+ input: {
664
+ is_down: (code: string) -> boolean,
665
+ duration: (code: string) -> number,
666
+ keys: () -> { string },
667
+ mouse_pos: () -> MousePos,
668
+ modifiers: () -> ModifierState,
669
+ },
670
+ ui: {
671
+ schema: (descriptors: { [string]: any }) -> UiProxy,
672
+ toggle: (default: boolean, opts: ToggleOpts?) -> any,
673
+ slider: (default: number, opts: SliderOpts?) -> any,
674
+ keybind: (default: string, opts: KeybindOpts?) -> any,
675
+ select: (default: string, choices: { string }, opts: SelectOpts?) -> any,
676
+ text: (default: string, opts: TextOpts?) -> any,
677
+ color: (default: string, opts: ColorOpts?) -> any,
678
+ get: (id: string) -> any,
679
+ set: (id: string, value: any) -> (),
680
+ get_all: () -> { [string]: any },
681
+ get_schema: () -> { any },
682
+ notify: (text: string, variant: string?) -> (),
683
+ },
684
+ timer: {
685
+ after: (ms: number, callback: () -> ()) -> TimerHandle,
686
+ every: (ms: number, callback: () -> ()) -> TimerHandle,
687
+ stop_all: () -> (),
688
+ },
689
+ macro: {
690
+ play: (macro: { MacroAction }, options: RbPlayOptions?) -> MacroHandle,
691
+ stop_all: () -> (),
692
+ record: (options: RecordOpts?) -> (),
693
+ finish: () -> { MacroAction },
694
+ -- the saved-macro format: a `macro=true` modeline, then `return` of the
695
+ -- actions. require() of a saved macro returns the actions.
696
+ to_script: (macro: { MacroAction }, name: string) -> string,
697
+ stream: (macro: { MacroAction }) -> (),
698
+ abort: () -> (),
699
+ },
700
+ math: {
701
+ random: (min: number, max: number) -> number,
702
+ gaussian: (mean: number, std_dev: number) -> number,
703
+ scale: (macro: { MacroAction }, x_factor: number, y_factor: number) -> { MacroAction },
704
+ spline: (macro: { MacroAction }, tension: number) -> { MacroAction },
705
+ resample: (macro: { MacroAction }, interval_ms: number) -> { MacroAction },
706
+ time_comp: (macro: { MacroAction }, target_ms: number) -> { MacroAction },
707
+ -- mode: "linear" | anything else = positional curve (default).
708
+ interpolate: (macro: { MacroAction }, interval_ms: number, mode: string?) -> { MacroAction },
709
+ },
710
+ json: {
711
+ parse: (s: string) -> any,
712
+ stringify: (value: any) -> string,
713
+ },
714
+ process: {
715
+ exists: (name: string) -> number?,
716
+ list: (name: string?) -> { ProcessEntry },
717
+ kill: (pid: number) -> boolean,
718
+ },
719
+ -- pixel reads are limited by a screen:target or screen:none permission entry
720
+ screen: {
721
+ pixel_hex: (x: number?, y: number?) -> string,
722
+ -- reads the published frame. regions up to 200k physical pixels answer on
723
+ -- the calling thread; larger ones yield inside rb.run and are refused
724
+ -- outside it.
725
+ search_color: (region: { number }, color: string, tolerance: number?) -> PixelMatch?,
726
+ list: () -> { MonitorEntry },
727
+ -- yields inside rb.run; refused outside a coroutine.
728
+ capture: (options: CaptureOptions?) -> CaptureResult,
729
+ -- needle is a PNG path relative to the script directory. yields inside
730
+ -- rb.run; refused outside a coroutine.
731
+ find_image: (needle: string, options: FindImageOptions?) -> ImageMatch?,
732
+ -- usable desktop of the display containing the point (cursor when omitted).
733
+ -- Windows only; raises elsewhere.
734
+ work_area: (x: number?, y: number?) -> WindowPos,
735
+ },
736
+ window: {
737
+ find: (title: string) -> number?,
738
+ list: (title: string?) -> { WindowEntry },
739
+ get_title: (handle: number?) -> string,
740
+ get_class: (handle: number?) -> string,
741
+ get_pos: (handle: number?) -> WindowPos,
742
+ get_pid: (handle: number?) -> number,
743
+ activate: (handle: number) -> (),
744
+ move: (handle: number?, rect: RbWindowRect?) -> (),
745
+ set_always_on_top: (handle: number, enabled: boolean) -> (),
746
+ set_transparency: (handle: number, alpha: number) -> (),
747
+ set_title: (handle: number, title: string) -> (),
748
+ minimize: (handle: number?) -> (),
749
+ maximize: (handle: number?) -> (),
750
+ restore: (handle: number?) -> (),
751
+ hide: (handle: number?) -> (),
752
+ show: (handle: number?) -> (),
753
+ close: (handle: number?) -> (),
754
+ kill: (handle: number?) -> (),
755
+ is_visible: (handle: number) -> boolean,
756
+ is_active: (handle: number?) -> boolean,
757
+ get_foreground: () -> number?,
758
+ from_point: (x: number, y: number) -> number?,
759
+ -- "minimized" | "maximized" | "normal". Windows only; raises elsewhere.
760
+ get_state: (handle: number?) -> string,
761
+ wait: (title: string, timeout: number?) -> number?,
762
+ wait_active: (title: string, timeout: number?) -> boolean,
763
+ wait_close: (title: string, timeout: number?) -> boolean,
764
+ },
765
+ -- application control. macOS matches app display names ignoring case;
766
+ -- Windows matches executable names (".exe" optional) and cannot hide;
767
+ -- Linux raises on every call.
768
+ app: {
769
+ front: () -> AppInfo?,
770
+ is_front: (name: string) -> boolean,
771
+ is_running: (name: string) -> boolean,
772
+ is_hidden: (name: string) -> boolean,
773
+ activate: (name: string) -> (),
774
+ hide: (name: string) -> (),
775
+ quit: (name: string) -> (),
776
+ },
777
+ -- permission: net, or net:<host> for the client calls and ws_connect
778
+ net: {
779
+ get: (url: string, options: RbHttpOptions?) -> HttpResponse,
780
+ post: (url: string, body: string, options: RbHttpOptions?) -> HttpResponse,
781
+ put: (url: string, body: string, options: RbHttpOptions?) -> HttpResponse,
782
+ patch: (url: string, body: string, options: RbHttpOptions?) -> HttpResponse,
783
+ delete: (url: string, options: RbHttpOptions?) -> HttpResponse,
784
+ head: (url: string, options: RbHttpOptions?) -> HttpResponse,
785
+ request: (options: HttpRequestOptions) -> HttpResponse,
786
+ listen: (port: number, handler: (request: HttpRequest) -> HttpResponseSpec | string) -> ServerHandle,
787
+ ws_listen: (port: number, handlers: WsListenHandlers) -> WsServerHandle,
788
+ ws_connect: (url: string, handlers: WsConnectHandlers) -> WsConnectHandle,
789
+ -- binds a UDP socket; handler receives each datagram's raw bytes and sender.
790
+ udp_listen: (bind_ip: string, port: number, handler: (data: string, ip: string, port: number) -> ()) -> UdpHandle,
791
+ -- this computer's IPv4 address on its default route, or nil when there is none.
792
+ local_ip: () -> string?,
793
+ },
794
+ pipe: {
795
+ open: (name: string, options: { size: number? }?) -> PipeHandle,
796
+ },
797
+ -- paths are relative to the script directory; absolute paths are rejected
798
+ file: {
799
+ read: (path: string) -> string,
800
+ read_bytes: (path: string) -> string,
801
+ write: (path: string, content: string) -> (),
802
+ append: (path: string, content: string) -> (),
803
+ exists: (path: string) -> boolean,
804
+ delete: (path: string) -> boolean,
805
+ -- nil path lists the script's own directory
806
+ list: (path: string?) -> { string },
807
+ mkdir: (path: string) -> boolean,
808
+ -- recursive; returns true even when the directory did not exist.
809
+ rmdir: (path: string) -> boolean,
810
+ copy: (src: string, dst: string) -> (),
811
+ move: (src: string, dst: string) -> (),
812
+ read_json: (path: string) -> any,
813
+ write_json: (path: string, value: any) -> (),
814
+ script_dir: () -> string,
815
+ size: (path: string) -> number,
816
+ time: (path: string) -> number,
817
+ is_dir: (path: string) -> boolean,
818
+ is_file: (path: string) -> boolean,
819
+ },
820
+ audio: {
821
+ beep: () -> (),
822
+ play: (path: string, options: PlayOptions?) -> SoundHandle,
823
+ stop_all: () -> (),
824
+ set_master_volume: (vol: number) -> (),
825
+ get_master_volume: () -> number,
826
+ },
827
+ -- every dialog call is coroutine-only: it yields until the dialog closes
828
+ -- and errors outside rb.run. the pickers return nil on cancel.
829
+ dialog: {
830
+ message: (text: string, options: DialogMessageOptions?) -> (),
831
+ confirm: (text: string, options: DialogMessageOptions?) -> boolean,
832
+ open_file: (options: DialogFileOptions?) -> string?,
833
+ open_dir: (options: DialogFileOptions?) -> string?,
834
+ save_file: (options: DialogFileOptions?) -> string?,
835
+ },
836
+ -- limited by clipboard:read, clipboard:write, clipboard:none
837
+ clipboard: {
838
+ get: () -> string?,
839
+ set: (text: string) -> (),
840
+ },
841
+ script: {
842
+ exit: (reason: string?) -> (),
843
+ reload: () -> (),
844
+ },
845
+ regex: {
846
+ is_match: (text: string, pattern: string) -> boolean,
847
+ find: (text: string, pattern: string) -> RegexMatch?,
848
+ find_all: (text: string, pattern: string) -> { RegexMatch },
849
+ replace: (text: string, pattern: string, replacement: string) -> string,
850
+ replace_all: (text: string, pattern: string, replacement: string) -> string,
851
+ split: (text: string, pattern: string) -> { string },
852
+ },
853
+ config: {
854
+ parse_toml: (text: string) -> { [string]: any },
855
+ to_toml: (value: { [string]: any }) -> string,
856
+ read_toml: (path: string) -> { [string]: any },
857
+ write_toml: (path: string, value: { [string]: any }) -> (),
858
+ },
859
+ hash: {
860
+ md5: (data: string) -> string,
861
+ sha1: (data: string) -> string,
862
+ sha256: (data: string) -> string,
863
+ sha512: (data: string) -> string,
864
+ crc32: (data: string) -> number,
865
+ hmac: (algo: string, key: string, data: string) -> string,
866
+ },
867
+ codec: {
868
+ base64: (data: string) -> string,
869
+ base64_decode: (str: string) -> string,
870
+ hex: (data: string) -> string,
871
+ hex_decode: (str: string) -> string,
872
+ },
873
+ env: {
874
+ get: (name: string) -> string?,
875
+ set: (name: string, value: string) -> (),
876
+ home: () -> string?,
877
+ config: () -> string?,
878
+ data: () -> string?,
879
+ desktop: () -> string?,
880
+ documents: () -> string?,
881
+ downloads: () -> string?,
882
+ appdata: () -> string?,
883
+ temp: () -> string?,
884
+ },
885
+ -- Windows only
886
+ registry: {
887
+ read: (key_path: string, value_name: string) -> string | number,
888
+ write: (key_path: string, type: string, value_name: string, value: any) -> (),
889
+ delete_value: (key_path: string, value_name: string) -> (),
890
+ delete_key: (key_path: string) -> (),
891
+ create_key: (key_path: string) -> (),
892
+ },
893
+ -- Rebind Pro; require("@rebind/pro") returns rb
894
+ ai: RbAi,
895
+ phone: RbPhone,
896
+ }
897
+
898
+ -- v1 names
899
+ -- The names every Rebind version accepts. They keep working.
900
+
267
901
  -- global functions
268
902
 
269
903
  declare function Run(fn: () -> ()): TaskHandle
@@ -284,23 +918,6 @@ declare _REBIND: RebindInfo
284
918
 
285
919
  -- system namespace
286
920
 
287
- export type ExecOptions = {
288
- -- milliseconds; default 5000. The process is killed on expiry and Exec errors.
289
- timeout: number?,
290
- -- working directory for the command; default: the host process's cwd.
291
- cwd: string?,
292
- }
293
-
294
- export type ExecResult = {
295
- exit: number,
296
- stdout: string,
297
- stderr: string,
298
- }
299
-
300
- export type ExecDetachedOptions = {
301
- cwd: string?,
302
- }
303
-
304
921
  declare System: {
305
922
  Time: () -> number,
306
923
  Mouse: () -> (number, number),
@@ -322,20 +939,6 @@ declare System: {
322
939
 
323
940
  -- hid namespace
324
941
 
325
- -- a joypad state; every field is optional and defaults to neutral. buttons
326
- -- lists pressed buttons 1-32, hat is 0 (up) to 7 clockwise (nil = centered),
327
- -- x/y/z/rz are -1..1 (0 = center), slider and dial are 0..1.
328
- export type JoypadState = {
329
- buttons: { number }?,
330
- hat: number?,
331
- x: number?,
332
- y: number?,
333
- z: number?,
334
- rz: number?,
335
- slider: number?,
336
- dial: number?,
337
- }
338
-
339
942
  declare HID: {
340
943
  Down: (code: string) -> (),
341
944
  Up: (code: string) -> (),
@@ -457,11 +1060,6 @@ declare JSON: {
457
1060
 
458
1061
  -- process namespace
459
1062
 
460
- export type ProcessEntry = {
461
- pid: number,
462
- name: string,
463
- }
464
-
465
1063
  declare Process: {
466
1064
  Exists: (name: string) -> number?,
467
1065
  List: (name: string?) -> { ProcessEntry },
@@ -470,53 +1068,6 @@ declare Process: {
470
1068
 
471
1069
  -- screen namespace
472
1070
 
473
- export type PixelMatch = {
474
- x: number,
475
- y: number,
476
- }
477
-
478
- export type MonitorEntry = {
479
- index: number,
480
- x: number,
481
- y: number,
482
- width: number,
483
- height: number,
484
- primary: boolean,
485
- }
486
-
487
- export type CaptureOptions = {
488
- -- 1-based display index; default: the display under the cursor.
489
- display: number?,
490
- -- display-local logical coords; default: the whole display.
491
- region: { x: number, y: number, w: number, h: number }?,
492
- -- downscale so the long edge is at most this; default and 0: native.
493
- max_edge: number?,
494
- }
495
-
496
- export type CaptureResult = {
497
- image: string, -- base64 PNG
498
- width: number,
499
- height: number,
500
- display: MonitorEntry,
501
- cursor: { x: number, y: number },
502
- }
503
-
504
- export type FindImageOptions = {
505
- display: number?,
506
- region: { x: number, y: number, w: number, h: number }?,
507
- -- 0.0 exact .. 1.0, per-channel fraction of 255 (same scale as SearchForColor).
508
- tolerance: number?,
509
- -- ratios to resample the needle by before scanning, tried in order; default { 1 }.
510
- scales: { number }?,
511
- }
512
-
513
- export type ImageMatch = {
514
- x: number,
515
- y: number,
516
- width: number,
517
- height: number,
518
- }
519
-
520
1071
  -- pixel reads are limited by a screen:target or screen:none permission entry
521
1072
 
522
1073
  declare Screen: {
@@ -544,7 +1095,7 @@ declare Window: {
544
1095
  GetPos: (handle: number?) -> WindowPos,
545
1096
  GetPID: (handle: number?) -> number,
546
1097
  Activate: (handle: number) -> (),
547
- Move: (handle: number, x: number?, y: number?, width: number?, height: number?) -> (),
1098
+ Move: (handle: number?, x: number?, y: number?, width: number?, height: number?) -> (),
548
1099
  SetAlwaysOnTop: (handle: number, enabled: boolean) -> (),
549
1100
  SetTransparency: (handle: number, alpha: number) -> (),
550
1101
  SetTitle: (handle: number, title: string) -> (),
@@ -574,11 +1125,6 @@ declare Window: {
574
1125
  -- Activate focuses a running app and does not launch one; Quit closes its
575
1126
  -- windows; Hide/IsHidden raise. Linux: every call raises.
576
1127
 
577
- export type AppInfo = {
578
- name: string,
579
- pid: number,
580
- }
581
-
582
1128
  declare App: {
583
1129
  Front: () -> AppInfo?,
584
1130
  IsFront: (name: string) -> boolean,
@@ -642,20 +1188,6 @@ declare File: {
642
1188
 
643
1189
  -- audio namespace
644
1190
 
645
- export type PlayOptions = {
646
- volume: number?,
647
- loop: boolean?,
648
- }
649
-
650
- export type SoundHandle = {
651
- Stop: (self: SoundHandle) -> (),
652
- Pause: (self: SoundHandle) -> (),
653
- Resume: (self: SoundHandle) -> (),
654
- IsPlaying: (self: SoundHandle) -> boolean,
655
- SetVolume: (self: SoundHandle, vol: number) -> (),
656
- GetVolume: (self: SoundHandle) -> number,
657
- }
658
-
659
1191
  declare Audio: {
660
1192
  Beep: () -> (),
661
1193
  Play: (path: string, options: PlayOptions?) -> SoundHandle,
@@ -666,22 +1198,6 @@ declare Audio: {
666
1198
 
667
1199
  -- dialog namespace
668
1200
 
669
- export type DialogMessageOptions = {
670
- title: string?,
671
- level: string?,
672
- }
673
-
674
- export type DialogFileFilter = {
675
- name: string,
676
- extensions: { string },
677
- }
678
-
679
- export type DialogFileOptions = {
680
- title: string?,
681
- location: string?,
682
- filters: { DialogFileFilter }?,
683
- }
684
-
685
1201
  -- every Dialog call is coroutine-only: it yields until the dialog closes and
686
1202
  -- errors outside Run(). the file and folder pickers return nil on cancel.
687
1203
  declare Dialog: {
@@ -708,13 +1224,6 @@ declare Script: {
708
1224
 
709
1225
  -- regex namespace
710
1226
 
711
- export type RegexMatch = {
712
- match: string,
713
- captures: { string },
714
- start: number,
715
- finish: number,
716
- }
717
-
718
1227
  declare Regex: {
719
1228
  IsMatch: (text: string, pattern: string) -> boolean,
720
1229
  Find: (text: string, pattern: string) -> RegexMatch?,
@@ -778,27 +1287,6 @@ declare Registry: {
778
1287
  CreateKey: (keyPath: string) -> (),
779
1288
  }
780
1289
 
781
- -- valid key names for input hooks (OnDown, OnUp):
782
- --
783
- -- letters: A-Z
784
- -- numbers: 0-9
785
- -- function: F1-F12
786
- -- mouse: Mouse1 (left), Mouse2 (right), Mouse3 (middle), Mouse4, Mouse5
787
- -- modifiers: LCtrl, RCtrl, LShift, RShift, LAlt, RAlt, LWin, RWin
788
- -- navigation: Up, Down, Left, Right, Home, End, PageUp, PageDown, Insert, Delete
789
- -- editing: Backspace, Tab, Enter, Space, Escape
790
- -- punctuation: Minus, Equal, LeftBrace, RightBrace, Backslash, Semicolon,
791
- -- Apostrophe, Grave, Comma, Period, Slash
792
- -- locks: CapsLock, ScrollLock, NumLock
793
- -- system: PrintScreen, Pause, Menu
794
- -- numpad: Kp0-Kp9, KpPlus, KpMinus, KpMultiply, KpDivide, KpEnter, KpDot
795
- --
796
- -- HID output only (HID.Down, HID.Press, etc.):
797
- -- function: F13-F24
798
- -- media: MediaNext, MediaPrev, MediaStop, MediaPlay, VolumeUp, VolumeDown, Mute
799
- -- aliases: Numpad0-9, NP0-9, NPAdd, NPSubtract, NPMultiply, NPDivide, NPEnter,
800
- -- NPDecimal, Ctrl, Shift, Alt, Win, Meta, Del, PgUp, PgDn, etc.
801
-
802
1290
  -- hooks: declare these as global functions in your script.
803
1291
  -- uncomment the ones you use; the runtime calls them when events occur.
804
1292