Skip to content

CLI: drive the editor from a terminal

The galatrix CLI lets you drive a running Galatrix editor from a terminal — the same commands as the editor's built-in Terminal (spawn, move, set, run <json>, scene, …), typed at a command prompt or scripted. It's handy for repeatable scene edits, batch spawning, and building maps from a script.

It pairs to your own editor, on the same machine, for a time-boxed window. Everything the editor's Terminal does works over the bridge — scene edits, reads, save (the local autosave) and publish (submits for review) — except ai, which stays in the editor so a bridge session can't spend AI budget.

Install (first time only)

The CLI is a small Node package — its source is at github.com/Galatrix-com/Galatrix-CLI. You need Node. Clone and build it once:

bash
git clone https://github.com/Galatrix-com/Galatrix-CLI
cd Galatrix-CLI
npm install
npm run build     # → dist/cli.js
npm link          # optional: exposes a global `galatrix` command anywhere

With npm link you can run galatrix … from any folder (used throughout this page). Without it, run the built file directly: node dist/cli.js … from the Galatrix-CLI folder.

Pair

1. Get a code. In the editor at editor.galatrix.com: Settings → Expose to CLI → set the Window (minutes) → Start. Copy the code (e.g. ABCDE-FGHIJ) and leave the editor tab open.

2. Run one command — in a terminal on the same machine as your browser:

bash
galatrix pair ABCDE-FGHIJ --server https://editor-api.galatrix.com

You land at a prompt: type commands, Ctrl-D to quit.

Other ways to run it

Set the server once — export it so you can drop the --server flag:

bash
export GALATRIX_SERVER=https://editor-api.galatrix.com
galatrix pair ABCDE-FGHIJ

One-shot — run commands and exit instead of opening the prompt:

bash
galatrix pair ABCDE-FGHIJ "spawn box Wall 0 1 0 ; tag Wall goal"

Didn't npm link? Run the built file directly: node dist/cli.js pair … from the Galatrix-CLI folder — every example works the same way.

Running the editor locally? The server defaults to http://localhost:4005, so drop --server entirely.

How the window behaves

  • Lasts the minutes you chose. The window stays open for its whole duration — only Stop (or the time running out) ends it. The code works the entire time; you don't re-generate it.
  • One CLI at a time. A second CLI trying to attach while one is connected is refused (already_paired). Disconnect the first (close its terminal) to attach another.
  • Reconnect freely. The same code re-attaches after the CLI disconnects, and the window survives the editor tab reloading, being backgrounded, or even briefly closed — a returning editor picks the session back up automatically. While the editor is away, commands answer "the editor is not connected right now"; focus the editor tab and retry.
  • Same machine (same public IP). The CLI must connect from the same IP address as your editor browser tab — your own computer. your IP does not match the editor means it's running somewhere else (a different network, or a server).

Commands

Every command is deterministic (no AI) and every edit is undoable in the editor. Type help for the full list. The essentials:

Read

  • scene — a summary of the active map (entity/asset counts, gravity, player speed)
  • ls [entities|assets|scripts] — list what's in the scene / project
  • find <query|#tag> — entities whose name / tag / component matches
  • inspect <name> — dump an entity's components (or a data/config/script asset)
  • get <path> — read a value: get gameSettings.gravity, get Wall.position
  • context [map|editor] — the exact context the AI editor is given: map = the live scene snapshot (what's in the map); editor = how to use the commands (the AI editor system prompt / run op catalog the AI service prepends — same content as actions). context map / context editor show one half
  • actions — the full AI action catalog: every op that run <json> accepts, with usage notes
  • console [errors|warn] [N] — the editor console (script console.* + engine log/warn/error) — read it after play or an import script to see runtime errors; console clear empties it
  • getScript <name> · listAssets · inspectModel <name> · describeGeometry <name> · inspectPose <name> · getAnimation <name> — the AI read-ops as bare commands (same result as run {"op":…}): a script's source, asset names by type, a rigged model's bones+clips, a mesh's verts/tris/bbox, a bone's world orientation, and an animation clip's full keyframe tracks (every {t,v,ease?}) in the exact shape createAnimation accepts

Play & view

  • select <name> | #tag | all | none · deselect · focus [name]
  • play · stop — enter / leave Play mode
  • pause · resume · step — freeze / unfreeze the play-test sim; step advances one frame while paused
  • playstate — a live snapshot of the running play-test (player position, entity sample) so a script can verify a game actually runs; not in Play mode until you play
  • view top|front|iso|… — snap the camera

Edit (friendly wrappers over run)

  • spawn <kind> [name] [x y z] — e.g. spawn box Wall 0 1 0 (kinds: box, sphere, cylinder, capsule, cone, plane, torus, wedge)

  • move / rotate <name> x y z · scale <name> s

  • rename <name> <newName> · tag <name> <tag> · duplicate <name> [count] · delete <name>delete/rename target an entity, or fall through to a project asset (script/material/prefab/…) by name

  • attach <script> <name> · parent <child> <parent> · unparent <child>

  • bake — bake the NavMesh (so NPC / navMeshAgent pathing works) · snap — drop the selected entities onto the surface below them

  • set <path> = <value>set gameSettings.gravity = -60 · set Wall.color = "#ff0000"

  • new map|data|config|material [name] · window <name> [close] (opens/raises a panel)

  • map [list|new|switch|rename|delete|duplicate] — manage sub-maps/levels (switch = load a level)

  • kit [list|import <name|index>|export <name>] — asset kits. import merges a built-in kit (Obby, Vehicle, Ragdoll, …) into the project; export <name> packs all project assets into a .galatrix-pack file

  • save — persist the map to the local autosave (IndexedDB), same as the editor's own

  • load [name|index] — list your saved maps (My Maps), or open one (replaces the current scene)

  • import [<type>] <path> [name] — import a small local asset file (under the 1 MB relay limit). The type is optional — leave it off and it's inferred from the file extension, so import ./tree.glb and import ./water.frag just work; pass a type only to override or for an ambiguous/extension-less file. Recognised: models (.glb/.gltf), textures (.png/.jpg/.jpeg/.webp), audio (.mp3/.ogg/.wav/.m4a), fonts (.ttf/.otf/.woff/.woff2), scripts (.js/.ts/.mjs → a script asset), data (.json → a data asset, read from scripts via getData("<name>")), and shaders (.frag/.vert/.glsl/… → a shader asset; the extension also picks the stage — .vert/.vs is the vertex source, anything else the fragment source, with a valid default kept for the other stage). The CLI reads the file off your disk and sends the bytes — the browser editor can't read a path itself, so this only works over the CLI (not the in-app Terminal). A model spawns with run op:spawnModel model:<name> name:Thing; a script attaches with attach <name> <entity>; a font is used via fontFamily "<name>"; a shader is assigned to a mesh's Material (shaderAssetId). An unknown extension errors (pass the type explicitly). Bigger files + FBX: use the editor's drag-drop

  • schematic import <url> [scale=1] [center=on|off] [transparent=on|off] [bake=on|off] [split=128] — import a Minecraft build (.schem sponge, .schematic classic, .litematic). Unlike import above, the browser fetches the URL, so this works in the in-app Terminal too — pass an http(s):// URL or a path your editor's server serves (inline data: bytes work as well). The parser is picked from the extension in the URL; no recognised extension is treated as .schem.

    By default you get one object holding the blocks, and the geometry is built as the map runs. That is roughly 100x smaller than storing the meshes: an 8-million-block build is a few MB instead of hundreds. Its Inspector configures block size, collision and streaming distance.

    Options: scale multiplies block size, center=off keeps the build's own coordinates instead of centring it on the origin, transparent=off drops glass, leaves and water — and with the default block grid it drops them from storage too, so they cost nothing at all. bake=on switches to the old behaviour: the build is meshed once and stored as models, giving ordinary mesh objects you can edit and re-material, at the cost of storing every triangle in the map (split sets the chunk edge in blocks for that path only). The reply reports blocks and objects. Large builds take a while — millions of blocks is minutes, not seconds

  • export [gz|json] [pretty] — save the map to disk as a file (gz = compressed, the default). Both export and kit export download to the machine the editor runs on — the same box as the CLI, since the bridge is same-machine — so a script can pull the map/kit out to a file

  • build [name] [html|zip] — build the standalone game to a downloadable file (html = one self-contained file, the default; zip = separate files)

  • publish <name> [| description] — submit the map to the server for review (pending moderation) — the one outward-facing command; it never posts instantly-public. ai (the LLM) stays off the bridge

run <json | key:value …> — anything the AI editor can do

run takes a single action object or an array, applied in order. This is the power tool — it reaches every editor operation (creating sub-maps, setting components, materials, terrain, …), not just the friendly wrappers.

bash
run {"op":"spawn","kind":"sphere","name":"Ball","position":[0,5,0],"color":"#ff0000"}
run [{"op":"addMap","name":"Level 2"}, {"op":"spawn","kind":"box","name":"Floor","scale":[20,1,20]}]

Shorthand (no JSON ceremony): pass one action as space-separated key:value pairs. Anything that parses as JSON ([0,1,0], numbers, true, {…}) is taken as-is; everything else is a plain string (so color:#ff0000 and kind:box just work). Wrap a string that contains spaces in "quotes":

bash
run op:spawn kind:box name:Wall position:[0,1,0] color:#ff0000
run op:setComponent name:Wall component:rigidBody props:{"type":"dynamic","mass":5}
run op:modify name:"My Wall" rename:"Big Wall"
run op:listAssets

Shorthand is one action per run; use the JSON array form for a batch. Input that starts with { or [ is always parsed as JSON.

Every op is also a bare command. You can drop the run op: prefix — the op name is a command on its own, so setComponent name:Wall component:rigidBody props:{…} is the same as run op:setComponent name:Wall …. It takes the same key:value shorthand or a {json} object (one action — use run [...] for a batch). The friendly wrappers above (spawn, move, duplicate, …) keep their nicer positional syntax; the bare-op form is how you reach every other op directly. actions lists them all.

bash
setComponent name:Wall component:rigidBody props:{"type":"dynamic","mass":5}
createMaterial name:Shiny color:#ffffff metalness:1 roughness:0.1
savePrefab name:Wall
spawnModel model:Tree name:Oak position:[4,0,2]
createScript name:Spin source:"class Spin extends Behaviour { update(){ this.entity.rotate(0,1,0) } }"

Bare-op commands chain with ; like any other (spawn box A 0 1 0 ; setComponent name:A component:rigidBody props:{"type":"dynamic"} ; tag A goal) — a ; inside a "quoted" string or {json} payload (e.g. a createScript source) is left alone, so it won't split your command.

Its ops span everything the AI editor can make — createMesh (procedural models), createAnimation, createMaterial/createShader, createScript/createEventSheet, createTerrain/sculptTerrain, createTilemap, createUICanvas, rigMesh, savePrefab, sub-maps, and every component. Discover them with actions (the full catalog) + context (the current scene): read both, then issue run [...]. The ai <prompt> LLM agent is intentionally not exposed over the bridge — an external agent brings its own reasoning and drives the deterministic run.

run also answers the AI editor's read ops — full parity, so an agent driving the terminal reads exactly the way the AI editor does: getScript (a script's full source), listAssets (names by type), inspectModel (a rigged model's bones + clips), describeGeometry (a mesh's verts/tris/bbox), inspectPose (a bone's actual world orientation), getAnimation (an animation clip's full keyframe tracks). They return their result as output; you can mix reads and writes in one array, though a write can't depend on a read's result within the same run — split those into two calls.

bash
run {"op":"listAssets"}
run {"op":"getScript","name":"MovingPlatform"}

Edit an animation. getAnimation returns a clip's tracks + every {t,v,ease?} keyframe in the exact shape createAnimation accepts, so read → tweak the keys → createAnimation with the same name updates the clip in place (a different name makes a new one). The round-trip is lossless — ease (per-key bezier), opacity/color tracks, all preserved.

bash
run {"op":"getAnimation","name":"DoorOpen"}
run op:createAnimation name:DoorOpen duration:1.5 tracks:[{"property":"rotation","keys":[{"t":0,"v":[0,0,0]},{"t":1.5,"v":[0,90,0]}]}]

Chaining: separate commands with ;spawn box A 0 1 0 ; tag A goal ; select A. (A line that starts with run is taken whole, so put run last or on its own.)

Example: build a tower

bash
galatrix pair ABCDE-FGHIJ 'run [
  {"op":"spawn","kind":"box","name":"Start","position":[0,0,0],"scale":[6,0.5,6],"color":"#37c85a"},
  {"op":"spawn","kind":"box","name":"Step1","position":[2,2,0],"scale":[2.5,0.4,2.5]},
  {"op":"spawn","kind":"box","name":"Step2","position":[0,4,2],"scale":[2.5,0.4,2.5]},
  {"op":"spawn","kind":"box","name":"Goal","position":[2,6,2],"scale":[3,0.5,3],"color":"#ffd700"},
  {"op":"modify","name":"Goal","tag":"goal"}
]'

Security

The window is something you open deliberately and can revoke instantly:

  • You start it from the editor, for a duration you choose (default 120 min, max 480).
  • Locked to your machine by IP, and to your account — the CLI never asserts an identity; the server derives it from your signed-in editor.
  • Stop ends it immediately, and it auto-ends when the window elapses. (A closed editor tab does not end it early — the time-box you chose is the contract — but commands only run while an editor is attached.)
  • Scope: scene edits, reads, the play-test snapshot (playstate), the local autosave (save), exporting the map/kit/standalone to a file (export / kit export / build — all download to the editor's own machine, which the bridge shares), and submitting a map for review (publish — moderation-gated, never instantly public). Only the AI agent (ai, the LLM) stays off the bridge, so it never spends AI budget.

Press Stop now in the Expose panel whenever you're done.