Script Guide
Muxit scripts are sandboxed JavaScript automation programs that control your devices. They have access to connected devices but cannot access the filesystem, network, or Node.js APIs.
Quick Reference
const dev = connector("name"); // get a device
await dev.voltage() // read a property
await dev.voltage(12) // write a property
await dev.reset() // execute an action
await dev.rampTo(24) // call a custom method
log.info("message") // logging (info/warn/error/debug)
emit("event-name", { data: 123 }) // send an event
on("event-name", (data) => { ... }) // listen for events
stream("connector", "stream", data) // emit stream data
await delay(1000) // wait 1 second (abortable)
script.running // false when stop is requested
script.name // this script's name
timestamp() // current ISO timestamp
say("message") // send text to chat panel (spoken via TTS)
await ai("prompt") // single-shot LLM call (returns string)
await ai("prompt", imageData) // LLM call with vision (image analysis)Not available: require, import, fs, process, net, eval — scripts cannot access the filesystem or network.
Running Scripts
From the GUI: Open a script file and click Run in the editor toolbar.
From WebSocket:
{ "type": "scripts.start", "name": "hello" }
{ "type": "scripts.stop", "name": "hello" }Available Globals
connector(name) / device(name)
Returns a proxy object for the named connector. Properties and actions are called as async functions.
const psu = connector("psu");
const voltage = await psu.voltage(); // read
await psu.voltage(12); // write
await psu.reset(); // action
await psu.rampTo(24); // custom methodThe proxy convention:
- No arguments → reads the property (
driver.get(name)) - One argument → writes the property (
driver.set(name, value)) - Known action → executes the action (
driver.execute(name, args))
log
Structured logging with four levels. Output appears in the bottom panel and server console.
log.info("Voltage set to 12V");
log.warn("Temperature approaching limit");
log.error("Connection lost");
log.debug("Raw response: " + data);emit(event, data)
Publish an event on the EventBus. Other scripts and WebSocket clients can subscribe.
emit("reading", { voltage: 12.3, current: 0.5 });Events are namespaced as script:<event> on the bus.
on(event, handler)
Subscribe to events from other scripts or the system. Returns a cleanup function.
const cleanup = on("alert", (data) => {
log.warn(`Alert: ${data.message}`);
});Event listeners are automatically cleaned up when the script finishes.
stream(connector, streamName, data)
Emit stream data on the EventBus. Used for continuous data like spectrometer readings or generated images.
stream("spectrometer", "spectrum", { wavelengths: [...], intensities: [...] });delay(ms)
Pause execution. Abortable — if the script is stopped, delay() resolves immediately.
await delay(1000); // wait 1 secondtimestamp()
Returns the current time as an ISO 8601 string.
say(text)
Send a message to the Chat Panel. If text-to-speech is enabled in the UI, the message is spoken aloud. Useful for scripts that report status audibly.
while (script.running) {
const temp = await device('thermocouple').temperature();
say(`Temperature is ${temp} degrees`);
await delay(60000); // announce every minute
}Messages appear in the Chat Panel with the script's name as the sender.
ai(prompt, image?)
Single-shot LLM call. Sends a prompt to the configured AI provider and returns the response text. Optionally pass an image for vision analysis.
// Text-only AI query
const answer = await ai("What is the capital of France?");
// Vision: analyze a camera image
const cam = connector('webcam');
const frame = await cam.snapshot();
const description = await ai("Describe what you see in this image", frame);
log.info(description);The call is asynchronous and returns a string. It uses the same AI provider and model configured in server.json. This is a single-shot call — it has no conversation memory and does not trigger the agentic tool loop.
script
Script lifecycle information.
script.running // true while active, false after stop()
script.name // the script's name (e.g., "hello")Use script.running in loops for graceful shutdown:
while (script.running) {
// do work
await delay(100);
}
// cleanup after loop exitsconsole.log(...args)
Alias for log.info().
Standard JavaScript Globals
Math, JSON, Date, parseInt, parseFloat, isNaN, isFinite, Number, String, Boolean, Array, Object, Map, Set, Promise.
What Is NOT Available
require()andimportfs,path,net,http,child_processand all Node.js modulesprocess,global,globalThiseval()(beyond the sandbox context)Proxy,Reflect
Return Values
Scripts can return values. The return value of the last expression is captured, or you can define a main() function that returns a value. When using the MCP run_code tool or the scripts.execute WebSocket message, the return value is included in the response.
// Last expression is captured as the return value
const v = await connector("psu").voltage();
const i = await connector("psu").current();
({ voltage: v, current: i, power: v * i })// Or use a main() function
function main() {
return { status: "ok", timestamp: timestamp() };
}Script Lifecycle
- Start: A dedicated worker thread is spawned. Script code is wrapped in
(async () => { ... })()and executed in avm.Contextinside the worker - Main function: If the script defines a
function main(), it is called automatically after the top-level code runs - Running:
script.runningistrue, all APIs are available. Connector calls are bridged to the main thread via message passing (transparent to the script) - Stop requested: The worker thread is terminated. For cooperative scripts (those using
await),script.runningbecomesfalseanddelay()resolves immediately. For non-cooperative scripts (tight loops withoutawait), the worker is forcibly killed - Cleanup: Event listeners registered with
on()are automatically removed - Finished:
scripts:stoppedevent is emitted on the EventBus
Note: Scripts run in isolated worker threads. Even a tight
while(script.running) { count++ }loop without anyawaitcan be stopped — the worker is forcibly terminated. However, usingawait delay()in loops is still recommended for cooperative shutdown.
Error Handling
Uncaught errors in scripts are caught and logged. They do not crash the server.
try {
await psu.voltage(100); // might throw if out of range
} catch (err) {
log.error(`Failed: ${err.message}`);
}Examples
Read and Log in a Loop
const psu = connector("psu");
while (script.running) {
const v = await psu.measured_voltage();
const i = await psu.measured_current();
log.info(`V=${v}V, I=${i}A, P=${(v * i).toFixed(3)}W`);
emit("reading", { voltage: v, current: i });
await delay(500);
}Ramp Voltage with Safety Check
const psu = connector("psu");
const maxVoltage = 24;
const target = 12;
await psu.output(true);
for (let v = 0; v <= target && script.running; v += 0.5) {
if (v > maxVoltage) {
log.error("Exceeded max voltage!");
break;
}
await psu.voltage(v);
await delay(100);
}
log.info(`Ramped to ${await psu.voltage()}V`);Cross-Script Communication
// Script A: producer
while (script.running) {
const temp = await sensor.temperature();
emit("temperature", { value: temp });
await delay(1000);
}
// Script B: consumer
on("temperature", (data) => {
if (data.value > 80) {
log.warn("Temperature too high!");
}
});Standard Script Pattern
const psu = connector("psu");
// Setup
await psu.voltage(5);
await psu.output(true);
// Main loop (exits gracefully on stop)
while (script.running) {
const v = await psu.measured_voltage();
log.info(`V=${v}`);
emit("reading", { voltage: v });
await delay(500);
}
// Cleanup
await psu.output(false);