xript Guide
Every non-primitive node in epixtudio is a xript. Not “powered by” or “built on top of”; is a xript. The built-in nodes use the same system you use when you write your own. There’s no inner ring.
What Works Today
The Custom Nodes tutorial covers the in-app node editor. You define a name, category, ports, and a JavaScript script; epixtudio stores it in your local database and treats it like any built-in node. No files to manage, no build step, no manifest to hand-write.
Scripts run in a QuickJS WASM sandbox. They receive inputs via inputs.portName(), process data in plain JavaScript, and return results as the last expression. The sandbox has no access to the filesystem, network, or browser APIs. This means custom nodes today are pure computation: string manipulation, math, JSON reshaping, data formatting. Anything that doesn’t need to reach outside the sandbox.
The built-in nodes that do reach outside (file I/O, HTTP requests, image processing) use host bindings that are wired up in Rust on the backend. Custom nodes don’t have access to those bindings yet.
The Sandbox
The sandbox (@xriptjs/runtime) isn’t a suggestion or a best practice; it’s the execution model.
- No network access — scripts can’t make HTTP requests
- No filesystem access — scripts can’t read or write files
- No browser APIs — no
document,window,fetch - Deterministic — same inputs, same outputs
This is a feature, not a limitation. When someone shares a custom node with you, the sandbox is the reason you can trust it. The worst a sandboxed script can do is return bad data; it can’t touch your files, phone home, or side-channel anything.
What’s Coming: Host Bindings and Capabilities
The architecture supports host bindings (Rust functions exposed to the sandbox), but custom nodes can’t use them yet. The plan:
Host bindings are functions the sandbox can call into the host. A small set of generic primitives would cover most use cases:
| Binding | Purpose |
|---|---|
net.request(url, method, headers, body) | Generic HTTP; covers REST APIs, webhooks, SMTP-via-API |
fs.read(path) | Read a file from disk |
fs.write(path, data) | Write a file to disk |
The built-in nodes already use internal equivalents of these. Exposing them to custom nodes is plumbing, not invention.
Capabilities are the permission model. Each binding requires a declared capability in the node’s manifest. When you write a node yourself and it goes straight into your local database, no approval is needed. You wrote it; you know what it does.
When someone else’s node enters the picture, the capability declarations become the consent layer. Before an imported node can be added to your database, epixtudio shows you what it’s asking for (network access, filesystem access, etc.) and you approve or reject. One-time decision. Once approved, the node runs like any other. No runtime prompts, no per-execution dialogs.
This applies to individual nodes shared between users. Graph-level import (a .xtudio file referencing custom nodes you don’t have) is a harder problem that’s still being worked out.
The Manifest System
Under the hood, every xript is a JSON manifest paired with a JavaScript module. The in-app node editor generates this for you, but the format matters if you’re building tooling or distributing nodes.
{
"xript": "0.1",
"name": "my-custom-node",
"description": "What this node does",
"inputPorts": [
{ "name": "text", "type": "Text" }
],
"outputPorts": [
{ "name": "result", "type": "Text" }
],
"capabilities": {
"network": {
"description": "Makes HTTP requests to external APIs",
"risk": "medium"
}
}
}
xript — manifest version. Currently "0.1".
name — unique identifier for the node type.
inputPorts / outputPorts — typed port declarations. Types: Text, Number, Boolean, Json, Image, Any.
capabilities — what the node needs beyond pure computation. Each capability has a description (shown to the user on import) and a risk level (low, medium, high).
Presentation Layer
There’s a separate presentation xript for UI extensibility. Same sandbox, different surface area. It has access to a limited set of host bindings for theming and keybindings:
ui namespace:
setThemeVar(name, value)— sets a CSS custom property on the app rootgetThemeVar(name)— reads a CSS custom propertyregisterHotkey(combo, action)— registers a keyboard shortcut
app namespace (read-only):
getNodeCount()— total nodes in the current graphgetSelectedNodeId()— the currently selected node’s ID
npm Packages
| Package | Purpose |
|---|---|
@xriptjs/runtime | QuickJS WASM sandbox for script execution |
@xriptjs/validate | Manifest schema validation |
@xriptjs/typegen | TypeScript type generation from manifests |
@xriptjs/docgen | Documentation generation from manifests |
@xriptjs/init | Scaffolding CLI for new xript projects |
These are the building blocks for tooling around xripts. @xriptjs/init scaffolds a valid manifest and entry point; @xriptjs/validate catches structural problems before they turn into silent failures at load time.
Principles
- Sandbox by default — zero access unless explicitly granted
- Self-authored nodes are trusted — no gatekeeping on what you write for yourself
- Import requires consent — nodes from outside declare their capabilities; you approve before they enter your database
- Capabilities are visible — the user always knows what a node can do before it can do it
- Deterministic — same graph state, same results; no hidden state, no side-channel surprises