Widgets/Custom
Script Widget
Script Widget
A "Custom" type widget. Script Widget lets you run custom JavaScript code within your Recipe, enabling advanced logic, data manipulation, and custom functionality beyond what's possible with standard widgets.
Features
- Write and execute custom JavaScript code
- Define custom inputs and outputs with specific data types
- Access Recipe variables
- Use timers and event listeners
- Process data from other widgets and send data to connected widgets
- Console for debugging with log, error, warn, and info methods
- Code generator to assists with generate code
- Access secure recipe secrets
- Set status values for the Status Panel
Inputs
The Script Widget allows you to define custom input ports using the getWidgetInputs function. Each input port can accept data of any specified DataType.
Outputs
The Script Widget allows you to define custom output ports using the getWidgetOutputs function. Each output port can transmit data of any specified DataType
Custom Settings
The Script widget provides several tools to help you write and manage your code:
Script Tools
-
Save Script: Saves your current code changes. The script is automatically validated when saving, and any compilation errors will be shown
-
Code Assistant (AI): Opens an AI-powered chat interface with:
-
Context Options: Provide context to the assistant using:
- Clipboard Text: Last copied text is shown as a tag. Click the tag to use it as context
- Selection as Context: Select one or more lines in the Script Widget code editor (shows as "Selection L1-L2")
Note: You can combine multiple contexts (clipboard and selections) to give the assistant more information
- Text Area: Enter your prompts here to get AI assistance with your code
- New Chat: Clears current conversation history and starts a new session with the assistant
-
-
Help Panel (?): Opens this documentation panel with detailed information about using the Script widget

Writing Scripts
Scripts in Kemu follow these main patterns:
Defining Input/Output Ports
You can define custom inputs and outputs using the getWidgetInputs and getWidgetOutputs functions:
Note: The name of the inputs and outputs must be unique.
// Define input ports const getWidgetInputs = () => [ { name: 'in1', type: DataType.Number }, { name: 'trigger', type: DataType.Boolean } ]; // Define output ports const getWidgetOutputs = () => [ { name: 'result', type: DataType.JsonObj }, { name: 'error', type: DataType.String } ];
Processing Events
The onParentEvent function is called whenever data arrives at one of your input ports. It receives a single object parameter, so you can destructure only what you need (for example, targetPort, sourcePort, event):
const onParentEvent = async ({ targetPort, sourcePort, event }) => { // targetPort is the port on this widget that received the data // sourcePort contains info about the widget and port that sent the data // event contains the actual data and metadata under event.data if (targetPort.name === 'in1') { // Process the incoming data const result = processData(event.data.value); // Send data to an output port return setOutput({ port: 'result', data: { type: DataType.JsonObj, // This should match the type defined in getWidgetOutputs value: result, }, }); } };
Execution Contexts (advanced)
Some Hub Services keep session data per execution path using an internal event context. When a Script performs blocking operations and receives multiple invocations before finishing the previous one, you might need to forward results using the original execution path. The event.executionContext provided to onParentEvent, together with the optional executionContext property in setOutput, allows restoring the correct path.
const onParentEvent = async ({ targetPort, event }) => { if (targetPort.name === 'anyInput') { // Generate a random delay between 1000ms (1 second) and 5000ms (5 seconds) const delayMilliseconds = Math.floor(Math.random() * (5000 - 1000 + 1)) + 1000; console.log(`Received data on port '${targetPort.name}'. Delaying for ${delayMilliseconds}ms...`); // Pause execution using Kemu.helpers.delay await Kemu.helpers.delay(delayMilliseconds); console.log(`Delay complete. Forwarding data: ${JSON.stringify(event.data.value)} (Type: ${event.data.type})`); // Forward the original data and its type to the output port await setOutput({ port: 'processedOutput', data: { type: event.data.type, value: event.data.value, }, // Here we pass the original eventId and execution path so we can resume a previous event. // When a HubService variant is invoked this way, it can restore its event context if used. // The same applies to Input/Output widgets which also use event contexts internally; they must receive // the correct execution path and eventId to dispatch responses to the right session/request. executionContext: event.executionContext, }); } };
Script Initialization
The main function is executed once when:
- The script is first loaded
- The script's code is updated and saved
const main = async (context) => { // Initialize variables, set up event listeners, etc. // WARNING: Any blocking operation here will delay the loading of the script and potentially the loading of the recipe. console.log('Script initialized'); };
Available APIs
Kemu Object
The Kemu object provides access to various services and helpers:
// Image processing helpers Kemu.image.getPixelValueAtIndex(imageData, index); // Get pixel value at a specific index Kemu.image.getBoundingBox(imageData, targetColor, config); // Get bounding box from blob detection Kemu.image.copyImageData(imageData); // Create a copy of image data Kemu.image.setPixelValueAtIndex(imageData, index, rgb); // Set pixel value at a specific index // Variable management - allows you to read and write variables that can be accessed by other Script and Variable widgets. Kemu.variable.get('my-var'); // Read a global variable Kemu.variable.set('my-var', 123, DataType.Number); // Set a global variable Kemu.variable.onValueChange('my-var', ({ value, type }) => { console.log('New value:', value, 'of type:', type) }); // Listen for changes // UI fields (when script is inside a Widget Group): use getUIValue/setUIValue so the visible control updates Kemu.variable.getUIValue('fieldName'); // Read a UI field's value await Kemu.variable.setUIValue('fieldName', 'new value'); // Set a UI field's value (updates the control) // Utility helpers Kemu.helpers.decodeDataType(value); // Decode the data type of a value Kemu.helpers.delay(1000); // Promise-based delay Kemu.helpers.http(config); // Send HTTP request See more details in the HTTP Helper section
// Secret management - allows you to access recipe secrets securely Kemu.secrets.requestAccess('my-secret'); // Request access to a secret Kemu.secrets.read(['my-secret']); // Read the value of a secret
// Status management - allows you to set status values that appear in the Status Panel Kemu.status.set('my-status', 'Active'); // Set a status value (DataType inferred) Kemu.status.set('progress', { type: DataType.Number, value: 75 }); // Set a status with explicit type
// Global variable management - allows you to work with global variables Kemu.globalVars.get('my-global'); // Get a global variable value await Kemu.globalVars.set({ name: 'my-global', value: 'Hello' }); // Set a global variable Kemu.globalVars.onValueChange('my-global', async (info) => { console.log(info.value); }); // Listen for changes
Working with Variables
The Kemu.variable API allows your scripts to interact with Recipe variables and UI variables defined in a parent Widget Group.
Recipe Variables
Recipe variables are shared across the entire recipe and can be accessed by any widget.
Reading a Variable
Use Kemu.variable.get(name, defaultValue) to retrieve the value of a recipe variable.
/** * Gets the value of a variable. * @param name - The name of the variable to get. * @param defaultValue - (Optional) A value to return if the variable doesn't exist. It can be a supported type (number, string, boolean, etc) or a PartialData object with `value` and `type` properties. * @returns A read-only object with `value` and `type`, or `undefined`. */ const myVar = Kemu.variable.get('my-variable'); if (myVar) { console.log(`The value of my-variable is ${myVar.value}`); } // Providing a default value const anotherVar = Kemu.variable.get('another-variable', 'default value'); // or const anotherVar = Kemu.variable.get('another-variable', { value: 'default value', type: DataType.String }); console.log(`The value is ${anotherVar.value}`); // "default value" if not set
Setting a Variable
Use Kemu.variable.set(name, value, type) to set the value of a recipe variable.
/** * Sets the value of an existing variable. * @param name - The name of the variable to set. * @param value - The value to set. * @param type - (Optional) The data type. If omitted, it's inferred from the value. */ await Kemu.variable.set('my-variable', 123); await Kemu.variable.set('another-variable', 'hello', DataType.String);
Listening for Changes
Use Kemu.variable.onValueChange(name, handler) to execute code whenever a variable's value changes.
/** * Creates an event listener for variable changes. * @param name - The name of the variable to watch. * @param handler - A function to execute on change. The handler receives an event object. */ Kemu.variable.onValueChange('my-variable', async (event) => { console.log(`Variable ${event.varName} changed to: ${event.value}`); // The event object contains: value, type, varName, isUIElement, setByWidgetType, setByWidgetId });
UI Variables
When a Script widget is placed inside a Widget Group, it can interact with the custom UI fields (like sliders, text inputs, etc.) defined in that group's settings. For reading and writing those fields, use getUIValue and setUIValue, so the visible control stays in sync.
Getting a UI Variable's Value
Use Kemu.variable.getUIValue(name) to read the current value of a UI field. If the variable hasn't been set, it returns the field's default value.
/** * Reads the current value of a UI variable. * @param name - The "Variable Name" assigned to the field in the parent Widget Group's settings. * @returns A read-only object with `value` and `type`. * @throws An error if the script is not inside a WidgetGroup or the field doesn't exist. */ const { value: top } = Kemu.variable.getUIValue('Rect.top'); console.log(`The UI field 'Rect.top' has a value of: ${top}`);
Important: This function is only available from inside WidgetGroups with pre-defined UI field variables. Attempting to read a field variable from outside a WidgetGroup will throw an error.
Setting a UI Variable's Value
Use Kemu.variable.setUIValue(fieldName, value, type) to update a UI field so the control (text input, textarea, checkbox, etc.) shows the new value.
/** * Sets the value of a UI field in the parent widget group. The control updates to show the new value. * @param fieldName - The "Variable Name" assigned to the field in the parent Widget Group's settings. * @param value - The value to set. * @param type - (Optional) The data type. If omitted, it's inferred from the value. * @returns A Promise. * @throws An error if the script is not inside a Widget Group. */ await Kemu.variable.setUIValue('myTextField', 'Updated text'); await Kemu.variable.setUIValue('count', 42); // type inferred as Number
Listening for UI Variable Changes
Use Kemu.variable.onUIValueChange(name, handler) to react to changes from UI elements in the parent Widget Group.
/** * Creates a listener for UI-specific variable changes. * @param name - The "Variable Name" of the UI field. * @param handler - A function to execute when the UI value changes. */ Kemu.variable.onUIValueChange('Rect.top', async (event) => { // This function is called whenever the UI element's value changes console.log('My UI element value:', event.value); }); // The event object (`evt`) passed to the handler has the following structure: // - `value`: The new value of the variable. // - `type`: The `DataType` of the variable. // - `varName`: The name of the variable that changed. // - `isUIElement`: A boolean indicating if the change originated from a UI element (true). // - `setByWidgetType`: The `WidgetType` of the widget that set the value. // - `setByWidgetId`: The ID of the widget that set the value.
Working with Global Variables
Global variables are a special type of variable that can be accessed and modified from anywhere in the recipe, including from the Kemu interface. They persist across widget lifecycle events and can be set by any widget.
get
/** * Gets the value of a global variable. * @param nameOrConfig - Either a full path (string) or an object with { group, name } * - If a string is provided, it's treated as the full dot-notation path (e.g., "group1.group2.myVar") * - If an object is provided, the full path is built from group + name * - For ungrouped variables, use just the variable name (no dots) * @returns A GlobalVariableInfo object with name, value, type, and controlType, or null if the variable doesn't exist */ // Access an ungrouped variable const globalVar = Kemu.globalVars.get('my-global'); if (globalVar) { console.log(`Global variable '${globalVar.name}' has value: ${globalVar.value} (type: ${globalVar.type})`); } // Access a grouped variable using full path (dot notation) const groupedVar = Kemu.globalVars.get('settings-group.api-url'); if (groupedVar) { console.log(`Grouped variable value: ${groupedVar.value}`); } // Access a grouped variable using { group, name } format const groupedVar2 = Kemu.globalVars.get({ group: 'settings-group', name: 'api-url' }); if (groupedVar2) { console.log(`Grouped variable value: ${groupedVar2.value}`); } // Access a nested grouped variable (multiple levels) const nestedVar = Kemu.globalVars.get('group1.group2.group3.myVar'); // Or using object format: const nestedVar2 = Kemu.globalVars.get({ group: 'group1.group2.group3', name: 'myVar' });
set
/** * Sets or creates a global variable. * @param config - Configuration object containing: * - name: The display name of the global variable (must not contain dots) * - value: The value to set (can be null) * - type: Optional DataType for the value * - controlType: Optional control type ('text', 'number', 'dropdown', 'slider', 'checkbox', 'multiSelect', 'multilineText', 'image', 'binaryFile', 'button', 'group', 'anything') * - controlSettings: Optional settings specific to the control type (min/max/step for number/slider, options for dropdown/multiSelect, placeholder for text/multilineText) * - helpText: Optional descriptive text displayed in the UI * - group: Optional full path of the parent group (e.g., "group1.group2"). If provided, all parent groups are automatically created if they don't exist * - groupHelpText: Optional help text for the immediate parent group (only applied when creating the group) * - order: Optional number to control the display order in the UI * - emitChangeEvent: Whether to notify other widgets about the change (default: true) * - emitDefineEvent: Whether to emit event if variable didn't exist (default: true) * * Note: Variable names cannot contain dots. Use the 'group' parameter to organize variables hierarchically. */ await Kemu.globalVars.set({ name: 'my-global', value: 'Hello, World!', type: DataType.String, controlType: 'text', }); // Set a number variable await Kemu.globalVars.set({ name: 'counter', value: 42, type: DataType.Number, controlType: 'number', }); // Set with minimal config await Kemu.globalVars.set({ name: 'flag', value: true, }); // Set with helpText and control settings for better documentation and validation await Kemu.globalVars.set({ name: 'temperature', value: 72, type: DataType.Number, controlType: 'number', helpText: 'Current room temperature in Fahrenheit', order: 1, controlSettings: { min: 0, max: 100, step: 1, }, }); // Set a dropdown with predefined options await Kemu.globalVars.set({ name: 'status', value: 'active', controlType: 'dropdown', controlSettings: { options: ['active', 'inactive', 'pending'], }, }); // Set a slider with min, max, and step await Kemu.globalVars.set({ name: 'volume', value: 50, type: DataType.Number, controlType: 'slider', controlSettings: { min: 0, max: 100, step: 5, }, }); // Set without triggering notifications await Kemu.globalVars.set({ name: 'internal-state', value: 'quiet-update', emitChangeEvent: false, });
onValueChange
/** * Creates an event listener for a specific global variable change. * @param name - The name of the global variable to watch * @param handler - A function to execute when the global variable changes */ Kemu.globalVars.onValueChange('my-global', async (info) => { console.log(`Global variable '${info.name}' changed!`); console.log(`New value: ${info.value}`); console.log(`Type: ${info.type}`); console.log(`Control type: ${info.controlType}`); });
onAnyValueChange
/** * Creates an event listener for any global variable change. * @param handler - A function to execute when any global variable changes */ Kemu.globalVars.onAnyValueChange(async (info) => { console.log(`Any global variable changed: ${info.name}`); console.log(`New value: ${info.value}`); });
Control Types and Settings
Global variables support different control types that determine how they appear and behave in the UI. Each control type may have specific settings:
Available Control Types:
'anything'- Default type with no specific UI control. Can hold any value.'number'- Numeric input with optional min/max/step constraints'slider'- Visual slider control with min/max/step settings'text'- Single-line text input with placeholder'multilineText'- Multi-line text input with placeholder'dropdown'- Dropdown menu with predefined options'multiSelect'- Multi-select dropdown with predefined options'checkbox'- Boolean checkbox control'image'- Image upload control'binaryFile'- Binary file upload control'button'- Button control that can trigger actions when clicked'group'- Group container that allows variables to be organized hierarchically. Variables within a group are displayed as a nested list and are not listed in the Global Variables widget
Control Type Settings:
Each control type has specific settings that can be configured:
// Number control with min, max, and step constraints await Kemu.globalVars.set({ name: 'temperature', value: 72, type: DataType.Number, controlType: 'number', controlSettings: { min: 0, max: 100, step: 1, }, }); // Slider control with min, max, and step await Kemu.globalVars.set({ name: 'volume', value: 50, type: DataType.Number, controlType: 'slider', controlSettings: { min: 0, max: 100, step: 5, }, }); // Text input with placeholder await Kemu.globalVars.set({ name: 'username', value: '', type: DataType.String, controlType: 'text', controlSettings: { placeholder: 'Enter username', }, }); // Multiline text input with placeholder await Kemu.globalVars.set({ name: 'description', value: '', type: DataType.String, controlType: 'multilineText', controlSettings: { placeholder: 'Enter description', }, }); // Dropdown with predefined options await Kemu.globalVars.set({ name: 'status', value: 'active', type: DataType.String, controlType: 'dropdown', controlSettings: { options: ['active', 'inactive', 'pending', 'archived'], }, }); // Multi-select with predefined options await Kemu.globalVars.set({ name: 'tags', value: ['urgent', 'important'], type: DataType.Array, controlType: 'multiSelect', controlSettings: { options: ['urgent', 'important', 'review', 'draft'], }, }); // Checkbox (no settings required) await Kemu.globalVars.set({ name: 'enabled', value: true, type: DataType.Boolean, controlType: 'checkbox', }); // Image (no settings required) await Kemu.globalVars.set({ name: 'avatar', value: null, type: DataType.Image, controlType: 'image', }); // Binary file (no settings required) await Kemu.globalVars.set({ name: 'attachment', value: null, type: DataType.Binary, controlType: 'binaryFile', controlSettings: { maxSize: 1024 * 1024 * 10, // 10MB allowedMimeTypes: ['application/pdf', 'application/zip', 'application/x-7z-compressed'], }, }); // Anything type (no settings required) await Kemu.globalVars.set({ name: 'data', value: { key: 'value' }, type: DataType.JsonObj, controlType: 'anything', }); // Button control (no settings required) await Kemu.globalVars.set({ name: 'action-button', value: null, controlType: 'button', helpText: 'Click to trigger an action', }); // Group container for organizing variables await Kemu.globalVars.set({ name: 'settings-group', value: null, controlType: 'group', helpText: 'Application settings', order: 1, }); // These variables will appear nested under the group and won't be listed in the Global Variables widget await Kemu.globalVars.set({ name: 'api-url', group: 'settings-group', value: 'https://api.example.com', type: DataType.String, controlType: 'text', helpText: 'API endpoint URL', }); await Kemu.globalVars.set({ name: 'timeout', group: 'settings-group', value: 5000, type: DataType.Number, controlType: 'number', helpText: 'Request timeout in milliseconds', controlSettings: { min: 1000, max: 30000, step: 1000, }, }); // Nested groups: Create variables in nested group hierarchies // When you specify a nested group path, all parent groups are automatically created await Kemu.globalVars.set({ name: 'database-host', group: 'settings-group.database', // This will automatically create 'database' group under 'settings-group' value: 'localhost', type: DataType.String, controlType: 'text', groupHelpText: 'Database configuration', // Help text for the 'database' group }); // Deeply nested groups (multiple levels) await Kemu.globalVars.set({ name: 'max-connections', group: 'settings-group.database.connection-pool', // Automatically creates: 'settings-group' -> 'database' -> 'connection-pool' value: 100, type: DataType.Number, controlType: 'number', controlSettings: { min: 1, max: 1000, step: 10, }, groupHelpText: 'Connection pool settings', // Help text only for 'connection-pool' group }); // You can also create groups explicitly before adding variables await Kemu.globalVars.set({ name: 'api-config', group: 'settings-group', value: null, controlType: 'group', helpText: 'API configuration settings', }); // Then add variables to the nested group await Kemu.globalVars.set({ name: 'api-key', group: 'settings-group.api-config', value: '', type: DataType.String, controlType: 'text', });
Button Control
Button controls allow you to create interactive buttons in the global variables interface. When a button is clicked, it triggers a value change event that can be listened to using Kemu.globalVars.onValueChange. The button's value is typically set to a timestamp or click count when clicked.
// Create a button await Kemu.globalVars.set({ name: 'refresh-button', value: null, controlType: 'button', helpText: 'Click to refresh data', }); // Listen for button clicks Kemu.globalVars.onValueChange('refresh-button', async (info) => { console.log('Refresh button clicked!'); // Perform refresh action await refreshData(); }); // Listen for changes to grouped variables using full path Kemu.globalVars.onValueChange('settings-group.api-url', async (info) => { console.log('API URL changed:', info.value); });
Group Control
Group controls allow you to organize related global variables hierarchically using dot notation. Variables that belong to a group are displayed as a nested list in the interface.
Key characteristics of groups:
- Variables within a group are displayed as a nested list under the group in the interface
- Grouped variables are not listed in the Global Variables widget (they only appear within their parent group)
- Groups help organize and categorize related variables for better UI organization
- A group itself doesn't hold a value
- Groups can be nested to create multi-level hierarchies (e.g., "group1.group2.group3")
- When creating a variable with a group path, all parent groups are automatically created if they don't exist
- Variable names cannot contain dots - use the
groupparameter to specify the parent group path - Group paths use dot notation (e.g., "parent-group.child-group") to represent the hierarchy
Nested Groups:
You can create deeply nested group hierarchies by specifying a full path in the group parameter. The system automatically creates all parent groups in the path if they don't exist.
// Creating a variable in a nested group automatically creates all parent groups await Kemu.globalVars.set({ name: 'myVariable', group: 'level1.level2.level3', // Automatically creates: 'level1' -> 'level1.level2' -> 'level1.level2.level3' value: 'Hello', controlType: 'text', groupHelpText: 'Help text for level3 group', // Only applied to the immediate parent (level3) }); // Access the variable using full path const var1 = Kemu.globalVars.get('level1.level2.level3.myVariable'); // Or using object format const var2 = Kemu.globalVars.get({ group: 'level1.level2.level3', name: 'myVariable' });
Important Notes:
- Variable display names (the
nameparameter) cannot contain dots - dots are reserved for group path notation - The
groupparameter accepts the full path of the parent group using dot notation - When you specify a nested group path, parent groups are created automatically with default settings
- The
groupHelpTextparameter only applies to the immediate parent group (the last group in the path), not to ancestor groups - Ungrouped variables are accessed using just their name (no dots)
Secret Management
The Kemu.secrets API allows your scripts to securely access recipe secrets that have been configured in the recipe settings.
requestAccess
/** * Requests access to a secret. This should be called before attempting to read the secret, ideally at the top of the script. * Calling this method will cause the secret to appear in the Secrets Mapping modal. * * @param name - The name of the secret to request access for */ Kemu.secrets.requestAccess('api-key');
read
/** * Reads the values of one or more secrets. * * @param names - An array of secret names to read * @returns A promise that resolves to an object mapping secret names to their values (string) or null if not mapped */ const secrets = await Kemu.secrets.read(['api-key', 'database-url']); if (secrets['api-key']) { console.log('API key loaded successfully'); } else { console.log('API key not configured'); } if (secrets['database-url']) { console.log('Database URL loaded successfully'); } else { console.log('Database URL not configured'); }
Status Management
The Kemu.status API allows your scripts to set status values that will be displayed in the Status Panel, providing real-time information about your script's operation.
set
/** * Sets the value of a status that appears in the Status Panel. * * @param name - The name of the status to set * @param value - The value to set. Use null to initialize the status without a value. * Can be a primitive value (DataType is auto-detected) or a PartialData object with `type` and `value` properties. */ Kemu.status.set('current-operation', 'Processing data'); // Set a status with explicit data type Kemu.status.set('progress', { type: DataType.Number, value: 75 }); // Initialize a status (will appear in Status Panel but show no value until set) Kemu.status.set('connection-status', null);
Image Processing Helpers
The Script widget provides several built-in functions for image processing:
getPixelValueAtIndex
/** * Finds the RGB pixel values at the given buffer index. * Useful when looping through an image buffer. * * @param imageData - The ImageData object containing the pixel data * @param index - The index in the buffer to get the pixel value from * @returns An object with RGB values and average brightness */ const pixelValues = Kemu.image.getPixelValueAtIndex(imageData, index); // Returns: { avr: number, r: number, g: number, b: number }
copyImageData
/** * Makes a copy of the given image data. * Creates a new ImageData instance whose buffer doesn't reference the original. * * @param imageData - The ImageData to copy * @returns A new ImageData instance with identical pixel data */ const imageCopy = Kemu.image.copyImageData(imageData);
setPixelValueAtIndex
/** * Sets the pixel value at a specific index in the image buffer. * * @param imageData - The image data to modify * @param index - The index in the buffer to set * @param rgb - Either a single number (used for all channels) or an object with r, g, b properties */ // Using a single value for all channels Kemu.image.setPixelValueAtIndex(imageData, index, 255); // Using specific values for each channel Kemu.image.setPixelValueAtIndex(imageData, index, { r: 255, g: 0, b: 0 });
getBoundingBox (multiBlobDetection)
/** * Finds areas of the image matching a target color and draws bounding boxes around them. * * @param imageData - The ImageData to analyze * @param targetColor - The target color to match, as a Pixel object { r, g, b } * @param config - Optional configuration: * - threshold: Maximum color difference (default: 0) * - minWidth: Minimum width of bounding box (default: 1) * - minHeight: Minimum height of bounding box (default: 1) * - maxBoxes: Maximum number of boxes to return (default: unlimited) * @returns An array of Rect objects { left, top, width, height } */ const boundingBoxes = Kemu.image.getBoundingBox( imageData, { r: 255, g: 0, b: 0 }, { threshold: 10, minWidth: 5, minHeight: 5, maxBoxes: 10 } );
Timers
// Use setTimeout for delayed execution setTimeout(async () => { // Code to run after delay await setOutput({ port: 'output', data: { type: DataType.String, value: 'Timer completed' } }); }, 1000);
Console
console.log('Regular message'); console.error('Error message'); console.warn('Warning message'); console.info('Info message');
Data Types
Use the DataType enum to specify the type of data being sent/received:
DataType.StringDataType.NumberDataType.BooleanDataType.JsonObjDataType.BinaryDataType.StreamDataType.ImageDataType.VideoDataType.Anything
HTTP Helper
The Script Widget supports making HTTP requests via the Kemu.helpers.http method. This allows you to perform HTTP requests through a Hub Service integration, which requires the Hub Desktop App to be running.
Using the HTTP Helper
You can configure the HTTP request by passing an ActionConfig object to Kemu.helpers.http. This configuration allows you to specify the request method, URL, headers, body, and more.
const actionConfig = { method: 'POST', url: 'https://my.domain.com/sse-endpoint', responseType: 'text', streamResponses: true, // Enable streaming onStream: (streamEvent) => { console.log(`STREAM Event: ${JSON.stringify(streamEvent)}`); }, }; const promise = Kemu.helpers.http(actionConfig); // Abort the request if needed promise.abort(); // Handle the response promise.then(response => { if (response.error) { console.error('Error:', response.error); } else { console.log('Results:', response.results); } });
Stream Responses
To receive stream responses, set streamResponses to true in the ActionConfig. The onStream callback will be invoked with each stream event, allowing you to handle different phases of the response:
- Start Phase: When the response starts
- Data Phase: As data chunks are received
- End Phase: When the response is complete
Example console log for stream response:
STREAM Event: { phase: 'start', statusCode: 200, headers: { ... }, requestId: 1 } STREAM Event: { phase: 'data', sequence: 1, size: 512, chunk: '...', requestId: 1 } STREAM Event: { phase: 'end', bytesReceived: 1024, statusCode: 200, requestId: 1 }
Aborting Requests
The promise returned by Kemu.helpers.http includes an abort method that can be used to cancel any pending request.
Response Structure
The resolved value of the promise will contain either a results or error object. Instead of throwing an error, the function resolves with an object containing an error property if something goes wrong.
ActionConfig Type
The ActionConfig type includes the following properties:
method: HTTP method (e.g., 'GET', 'POST')url: Request URLheaders: Optional headersquery: Optional query parametersbody: Optional request bodyresponseType: Expected response type ('text', 'json', 'blob', 'arrayBuffer')responseEncoding: Optional text encodingfollowRedirects: Whether to follow redirectsdecompress: Whether to decompress responses. Defaults totruestreamResponses: Whether to enable streamingonStream: Callback for stream events. RequiresstreamResponsesto betrue
Examples
Basic Counter
This example demonstrates a simple counter that increases its value every time an event arrives at its input port, and then shows how to enhance it using Generate Script to detect even/odd numbers.
Drag the example onto the workspace to explore and experiment!
Part 1: Basic Counter
- The Button widget triggers an event to start the process
- The Script widget receives each event, increments an internal counter, and transmits the updated count
- The Display widget shows the current count each time it is updated
Part 2: Using Generate Script
- Run the basic counter and click on its "count" output port
- In the Port Inspector, click "Generate Script" to open the AI assistant
- Enter this prompt:
Create a script that: 1. Receives events in an input port named "trigger" 2. Counts up by 1 each time 3. Has two outputs: - "count": the current number (type: Number) - "type": text that says "Even" or "Odd" (type: String) - Click Generate to create the enhanced counter script
- Connect the new widget:
- Button widget to trigger counting
- "count" output to a Display widget
- "type" output to a Text widget
- Run the recipe to see both the number and its even/odd classification
Data Transformation
This example transforms incoming JSON strings into objects.
Drag the example onto the workspace to explore and experiment!
In this example
This example uses the Script widget to transform incoming JSON strings into objects. If the input is valid JSON, it transmits the parsed object; if not, it transmits an error message.
- The Button widget triggers the Text widget containing a JSON string (e.g.,
{"country":"Australia","code":"AU"}) or an invalid JSON string - The Script widget receives the string and tries to parse it as JSON
- If parsing succeeds, it transmits the object to the "transformed" port
- If parsing fails, it transmits an error message to the "error" port
- The Get Property widget receives the parsed object and extracts a specific property (e.g., "code")
- The Text widget displays the extracted property value
- Another Text widget displays an error message if the input was invalid
📌 Additional Notes
- Scripts are expected to be compatible with non-browser environments, thus, avoid using browser-specific APIs
- The Script Widget validates your code when you save it and will show compilation errors if there are issues
- For complex logic that requires external libraries, consider creating a Hub Service instead
- Use the console for debugging, which is accessible from the widget's UI
Glossary
-
DataType
An enumeration that defines the types of data that can be transmitted between widgets.
-
Hub Service
A service that runs on the server and can execute complex logic or use external libraries that are not available in the browser environment.
-
Recipe
A visual workflow created by connecting widgets together to process data and perform actions.
-
Widget Group
A container widget that allows you to group multiple widgets together and create custom UI controls for managing their behavior.