Appearance
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 anywhereWith 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.comYou 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-FGHIJOne-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 editormeans 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 / projectfind <query|#tag>— entities whose name / tag / component matchesinspect <name>— dump an entity's components (or a data/config/script asset)get <path>— read a value:get gameSettings.gravity,get Wall.positioncontext [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 /runop catalog the AI service prepends — same content asactions).context map/context editorshow one halfactions— the full AI action catalog: everyopthatrun <json>accepts, with usage notesconsole [errors|warn] [N]— the editor console (scriptconsole.*+ engine log/warn/error) — read it afterplayor animport scriptto see runtime errors;console clearempties itgetScript <name>·listAssets·inspectModel <name>·describeGeometry <name>·inspectPose <name>·getAnimation <name>— the AI read-ops as bare commands (same result asrun {"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 shapecreateAnimationaccepts
Play & view
select <name> | #tag | all | none·deselect·focus [name]play·stop— enter / leave Play modepause·resume·step— freeze / unfreeze the play-test sim;stepadvances one frame while pausedplaystate— a live snapshot of the running play-test (player position, entity sample) so a script can verify a game actually runs;not in Play modeuntil youplayview 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> srename <name> <newName>·tag <name> <tag>·duplicate <name> [count]·delete <name>—delete/renametarget an entity, or fall through to a project asset (script/material/prefab/…) by nameattach <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 themset <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.importmerges a built-in kit (Obby, Vehicle, Ragdoll, …) into the project;export <name>packs all project assets into a.galatrix-packfilesave— persist the map to the local autosave (IndexedDB), same as the editor's ownload [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, soimport ./tree.glbandimport ./water.fragjust 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 viagetData("<name>")), and shaders (.frag/.vert/.glsl/… → a shader asset; the extension also picks the stage —.vert/.vsis 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 withrun op:spawnModel model:<name> name:Thing; a script attaches withattach <name> <entity>; a font is used viafontFamily "<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-dropschematic import <url> [scale=1] [center=on|off] [transparent=on|off] [bake=on|off] [split=128]— import a Minecraft build (.schemsponge,.schematicclassic,.litematic). Unlikeimportabove, the browser fetches the URL, so this works in the in-app Terminal too — pass anhttp(s)://URL or a path your editor's server serves (inlinedata: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:
scalemultiplies block size,center=offkeeps the build's own coordinates instead of centring it on the origin,transparent=offdrops glass, leaves and water — and with the default block grid it drops them from storage too, so they cost nothing at all.bake=onswitches 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 (splitsets 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 secondsexport [gz|json] [pretty]— save the map to disk as a file (gz= compressed, the default). Bothexportandkit exportdownload 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 filebuild [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:listAssetsShorthand 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.