Skip to content

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

javascript
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:

json
{ "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.

javascript
const psu = connector("psu");

const voltage = await psu.voltage();     // read
await psu.voltage(12);                   // write
await psu.reset();                       // action
await psu.rampTo(24);                    // custom method

The 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.

javascript
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.

javascript
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.

javascript
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.

javascript
stream("spectrometer", "spectrum", { wavelengths: [...], intensities: [...] });

delay(ms)

Pause execution. Abortable — if the script is stopped, delay() resolves immediately.

javascript
await delay(1000);  // wait 1 second

timestamp()

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.

javascript
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.

javascript
// 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.

javascript
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:

javascript
while (script.running) {
  // do work
  await delay(100);
}
// cleanup after loop exits

console.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() and import
  • fs, path, net, http, child_process and all Node.js modules
  • process, global, globalThis
  • eval() (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.

javascript
// 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 })
javascript
// Or use a main() function
function main() {
  return { status: "ok", timestamp: timestamp() };
}

Script Lifecycle

  1. Start: A dedicated worker thread is spawned. Script code is wrapped in (async () => { ... })() and executed in a vm.Context inside the worker
  2. Main function: If the script defines a function main(), it is called automatically after the top-level code runs
  3. Running: script.running is true, all APIs are available. Connector calls are bridged to the main thread via message passing (transparent to the script)
  4. Stop requested: The worker thread is terminated. For cooperative scripts (those using await), script.running becomes false and delay() resolves immediately. For non-cooperative scripts (tight loops without await), the worker is forcibly killed
  5. Cleanup: Event listeners registered with on() are automatically removed
  6. Finished: scripts:stopped event is emitted on the EventBus

Note: Scripts run in isolated worker threads. Even a tight while(script.running) { count++ } loop without any await can be stopped — the worker is forcibly terminated. However, using await 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.

javascript
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

javascript
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

javascript
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

javascript
// 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

javascript
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);

Muxit — Hardware Orchestration Platform