Contributing
Dev instances
Run a separate dev build of the app beside your everyday dev app, drive it with app handles, and stop it cleanly.
A dev instance is a dev build of the app that runs next to your everyday dev app (pnpm dev) and shares nothing with it. Each one has a name and its own userData, ports and Unity project, so several can run at once. The examples use alt; any other name works. The full guide is apps/orchestrator/DEV-INSTANCE.md.
Start
From apps/orchestrator on macOS:
bash scripts/macos/dev-instance.sh alt
corepack [email protected] dev:instance alt runs the launcher behind it (scripts/dev-instance.mjs) directly, on any platform.
Startup is good when the log shows [dev-instance] "alt" userData=…satura-space-dev-alt and [agent-hooks] dev instance "alt": endpoint …satura-space-dev-alt/agent-hooks/endpoint.
The test project is ~/projects/_satura-test-alt unless SATURA_TEST_PROJECT points elsewhere. Handles that write files only run on that project.
Drive it
node scripts/satura.mjs --instance alt handles
node scripts/satura.mjs --instance alt handle unity.ready
node scripts/satura.mjs --instance alt exec '<js>'
Call unity.ready first and read value and logs after every call.
exec runs a satura_exec script the way an agent's MCP does, with unity.*, handles.* and video.*. Inside it, every app handle is also app.handle(name, params). It returns { status, value, error, logs }, where logs are the app log lines that call caused, so one script can run handles across several areas, compare the values in JS and return one result. A handle checks nothing itself: it does the hard part and returns data, and the script does the asserting.
Handles for the app's own code
| Handle | Reaches |
|---|---|
fn.list, fn.call | Any export of src/main/** and src/shared/** with JSON args |
fn.window.list, fn.window.call | Pure renderer modules: Tuner curves, rotation, rect, scene gizmo |
scratch.dir | Files, symlinks and sqlite databases in a temp dir the other handles work in |
paths.guard | Every path guard, on paths and symlinks |
hooks.install | The Claude, Cursor, Grok and Kimi hook installers and ~/.codex/config.toml, against a temp HOME |
gitQueue.run | The git queue's priority order, timeouts and stuck-output release, with a fake git |
fake.start, fake.hits | Fake HTTP upstreams and a fake app control socket |
relay.start, relay.http, relay.ws, relay.state | A local relay child process |
mcp.call | One packages/mcp/unity_mcp function in a scratch project |
unity.kill | SIGKILL of the test project's editor, for crash recovery |
scratch.list, scratch.close | What is still open |
Dirs, fakes, relays and sockets opened in a script close when it ends; pass keep: true to keep one for later handle calls, then scratch.close. These handles run only from exec and the CLI, never from a report action or the phone. Paths they touch must be inside the temp jail ($TMPDIR/satura-handles).
Stop
- Cmd+Q in the instance window.
- From a shell:
kill -TERM <pid>ofbash scripts/macos/dev-instance.sh alt. The script stops its own Electron, which quits Unity cleanly first. Wait for it to exit before killing anything by hand: a killed Unity leaves a scene backup behind. - The terminal daemon outlives restarts on purpose. Full cleanup:
pkill -f 'satura-space-dev-alt/daemon'.
Heads up. Kill by pid or by the instance name. pkill -f electron-vite or osascript … quit app also hit your other dev app.
What is separate
dev app (pnpm dev) | dev instance <name> | |
|---|---|---|
| userData | satura-space-dev | satura-space-dev-<name> |
| renderer | :5273 | :5373 + slot |
| Unity ports | 10210+ | 12210 + 1000 × slot |
| hook server | ~/.satura | own, in its userData |
The slot is fixed for alt (0), t2d (1), t3d (2) and anim (3). Any other name hashes to a slot from 4 to 31, so two unusual names can collide; the launcher's first line prints the renderer port and the Unity port base.
~/.satura/agent-hooks/endpoint and ~/.claude/settings.json belong to the main dev app, so an instance runs its own hook server, writes its endpoint only into its userData and installs no agent hooks. Its terminals get that endpoint in SATURA_AGENT_HOOKS_ENDPOINT, so satura_exec from an instance terminal reaches that instance. It never writes ~/.codex/config.toml.
An instance refuses Unity and engine work on any project that a running Satura Space or dev app has open or in its recent folders, or one with another app's Unity open on it (log line [dev-instance] … won't touch …).
Native notes
- Windows Unity clones run as GUI editors with windows hidden by
unityhook.dll; never-noMainWindowthere, it has no GameView and crashes the render thread. macOS useswinhook.dyliband-noMainWindow. - Windows relaunch after engine or MCP edits:
cd scripts/windows; ./launch.ps1.