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

HandleReaches
fn.list, fn.callAny export of src/main/** and src/shared/** with JSON args
fn.window.list, fn.window.callPure renderer modules: Tuner curves, rotation, rect, scene gizmo
scratch.dirFiles, symlinks and sqlite databases in a temp dir the other handles work in
paths.guardEvery path guard, on paths and symlinks
hooks.installThe Claude, Cursor, Grok and Kimi hook installers and ~/.codex/config.toml, against a temp HOME
gitQueue.runThe git queue's priority order, timeouts and stuck-output release, with a fake git
fake.start, fake.hitsFake HTTP upstreams and a fake app control socket
relay.start, relay.http, relay.ws, relay.stateA local relay child process
mcp.callOne packages/mcp/unity_mcp function in a scratch project
unity.killSIGKILL of the test project's editor, for crash recovery
scratch.list, scratch.closeWhat 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> of bash 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>
userDatasatura-space-devsatura-space-dev-<name>
renderer:5273:5373 + slot
Unity ports10210+12210 + 1000 × slot
hook server~/.saturaown, 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 -noMainWindow there, it has no GameView and crashes the render thread. macOS uses winhook.dylib and -noMainWindow.
  • Windows relaunch after engine or MCP edits: cd scripts/windows; ./launch.ps1.