Macro Scripts
The Run Script macro step executes arbitrary JavaScript in a sandboxed VM context. Use it for logic that doesn't fit the other step types — computing a value, iterating dynamic collections, conditional branching beyond the built-in condition steps.
The execution surface
A script runs in an isolated vm context with these globals:
| Global | What it is |
|---|---|
gem | The GemServer instance — the full server API: gem.command(...), gem.macro(...), gem.getAttribute(...), gem.setAttribute(...), and the live in-memory collections gem.zones, gem.devices, gem.subsystems, … |
context | The current macro context — variables and per-run state shared across the macro's steps (e.g. values set by a Set Variable step, the current item in a For Each). |
signal | An AbortSignal that fires when the macro is stopped. |
console, timers, Buffer, URL | Standard utilities. |
JSON, Math, Date, and core built-ins | Standard JS. |
A script may be synchronous or async (return a Promise). Its return value becomes the step's result.
// set a zone state
await gem.setAttribute('zone', 5, 'state', 'on', 'string');
// summarize live state
const zones = Object.values(gem.zones);
const active = zones.filter(z => z.state === 'on');
return { count: active.length };
Limits
- 30-second hard timeout — the script is aborted if it runs longer. Avoid long loops; the timeout will kill them.
- Scripts respect the macro's abort signal, so a long-running async script exits when the macro is stopped.
:::warning gem.* and context.* are a frozen public API
Saved macros and user scripts are stored JSON written by past versions. Changing the signature of a gem.* or context.* method silently breaks every stored script that uses it. New helpers are added; existing ones are never repurposed or removed. Prefer the documented gem.<helper> methods over raw database access.
:::
See the Automation guides for building macros, and Architecture for how gem relates to the rest of the system.