Imported from menstood/unity-pipeline-commands-skill (
SKILL.md). Install upstream withnpx skills add menstood/unity-pipeline-commands-skill. Copyright stays with the author.
Unity CLI (com.unity.pipeline) — drive the Unity Editor over HTTP
The com.unity.pipeline package (v0.3.x, experimental) runs a local HTTP server inside the Unity Editor (and optionally inside development Player builds). Clients — the unity CLI, CI, or this agent — execute registered commands against it. This is how you can actually compile, test, screenshot, and edit scenes without a human in the Editor.
Prerequisite: package installed?
Check Packages/manifest.json for "com.unity.pipeline". If missing, report it and ask before installing — installing edits the manifest and triggers a domain reload. (Options once approved: unity pipeline install with the CLI, or Package Manager → Install package by name → com.unity.pipeline, version blank = latest.) The Editor must be running with the project open for the server to exist.
Connecting (do this first, every session)
- Read the port descriptor the Editor writes:
- Editor:
<projectPath>/Library/Pipeline/.unity-pipeline-port - Player:
<workingDirectory>/.unity-pipeline-runtime-port
- Editor:
- It's JSON — extract
portandevalToken(also haspid,projectName,unityVersion,mode,lastHeartbeat). - Call
http://127.0.0.1:<port>/...with headerAuthorization: Bearer <evalToken>. Always use127.0.0.1, notlocalhost(IPv6 pitfalls). Missing/wrong token →401.
Ports: Editor 7800–7849 (tests 7850–7899), Runtime 7900–7949 (tests 7950–7999). Token is 256-bit CSPRNG, base64, regenerated after every domain reload — re-read the descriptor after any recompile/package change.
Treat evalToken as a secret: never print, log, or commit it (descriptors live under Library/, which Unity projects git-ignore); read it only to build the auth header and re-read after reloads instead of storing it.
Project identity check (before any mutation)
A reachable server + valid token does NOT prove you're talking to the right project. Before the first mutating command:
- Confirm the descriptor's
projectPathmatches the project root you're working in. - Confirm
pidis alive andlastHeartbeatis recent — a stale descriptor means a dead Editor. - With multiple Editors open, target explicitly (
unity command --project-path <path> ...); never mutate when identity is ambiguous.
Endpoints
| Endpoint | Use |
|---|---|
GET /api/status, /api/editor_status |
Liveness / editor state |
GET /api/commands |
List every registered command (self-documenting — use this to discover) |
POST /api/exec |
Run a command: {"command":"<name>","parameters":{...}} — the key is parameters, NOT params (verified; wrong key silently binds nothing) |
GET /api/test-status |
Async test status |
PowerShell recipe (Windows)
$d = Get-Content "Library/Pipeline/.unity-pipeline-port" | ConvertFrom-Json
$h = @{ Authorization = "Bearer $($d.evalToken)" }
Invoke-RestMethod -Uri "http://127.0.0.1:$($d.port)/api/exec" -Method Post -Headers $h `
-ContentType 'application/json' `
-Body (@{ command = 'editor_status'; parameters = @{} } | ConvertTo-Json -Depth 10)
The unity CLI — prefer this when installed (verified with v1.0.0-beta.2)
Run unity --version at session start — the CLI is an experimental beta and behavior shifts between releases; unity --help and bare unity command are the source of truth for the installed version. The CLI does port/token discovery itself — no descriptor reading, no 401-after-domain-reload handling:
unity pipeline list # projects + server port + reachability
unity command # no name = list ALL commands with params (best discovery)
unity command <name> [--arg value ...] # run any command, e.g.:
unity command find_assets --type SceneAsset --name Init
unity command --project-path <path> <name> ... # explicit project
unity command --runtime MyGame.exe runtime_status # attach to a dev Player
unity command --runtime-path "C:\Builds\MyGame" runtime_status
Verified CLI behaviors:
- ObjectRef args take inline JSON — in PowerShell wrap in single quotes with no backslash escapes:
--target '{"hierarchyPath":"/Foo"}'(escaped\"inside single quotes passes literal backslashes and breaks resolution). - A plain string for an ObjectRef param is treated as a hierarchyPath shorthand:
--target '/Foo'. - For machine parsing prefer
--format json --no-bannerover the default human/tab layout; treat any non-zero exit code as failure and read stderr. (Observed with v1.0.0-beta.2: default output is tab-separatedCommand Success Result Parameters; failures printError:lines and exit 6.)
Fall back to the raw HTTP recipe above only when the CLI isn't installed.
Response envelope
Every command returns a CommandExecutionResponse: { success: bool, command: string, result: <payload>, executionTimeMs: long?, error: string? }. Authoring commands put an AuthoringResult in result — assets get assetPath/guid/fileId/type/globalId; scene objects get instanceId/hierarchyPath/type/globalId. Chain those identities into the next command's ObjectRef args.
ObjectRef — how you point at things
Any param typed as a reference accepts ONE of: {"globalId": "..."}, {"path": "Assets/Foo.mat"}, {"guid": "...", "fileId": ...}, {"instanceId": 12345}, {"hierarchyPath": "/Root/Child"}.
Safety conventions
confirm/dry_run are command-level conventions, not a global guarantee — non-destructive commands and custom [CliCommand]s may implement neither. Check the command's schema (unity command / GET /api/commands) before relying on them.
dry_run: true→ validate + preview, mutate nothing (wins even ifconfirmis also true).confirm: false(default) on destructive/overwriting commands → command refuses; passconfirm: trueto apply.- Scene/GameObject mutations are Undo-able (grouped as one Ctrl+Z step). AssetDatabase writes, file writes, package ops, and settings are NOT undoable.
- Bare paths (
Materials/Stone) resolve under the authoring root (defaultAssets; read/change viaget_authoring_root/set_authoring_root)...traversal is rejected. ExplicitAssets/...paths work as-is. - Most scene/asset authoring commands are blocked during Play mode.
Async pattern (fire → poll)
Long operations return immediately; poll their status command until completed:
| Start | Poll |
|---|---|
build |
build_status |
switch_build_target |
switch_build_target_status |
recompile |
recompile_status (idle/triggered/compiling/completed/up_to_date) |
run_tests (async_tests:true) |
test_status |
package_add / package_remove / package_resolve |
package_status (survives domain reload) |
bake_lighting / bake_navmesh / bake_occlusion_culling |
lighting_bake_status / navmesh_bake_status / occlusion_bake_status |
A domain reload (recompile, package add/remove, scripting-backend change) can drop the in-flight HTTP reply — treat a dropped connection as "poll the status command with a fresh token".
If the Editor is unfocused/minimized it may stop ticking — call set_autotick (enable:true) first; it auto-disables after a domain reload.
Common recipes
- Verify a code change compiles:
recompile→ pollrecompile_status→get_console_logs --severity error. - Run play-mode smoke test:
editor_play→get_console_logs/screenshot --view game→editor_stop. - Run the test suite:
run_tests --mode editor --filter <name>(orasync_tests:true+ polltest_status). - See what the scene looks like:
screenshot(game/scene view) orcapture_game_view(returns base64 PNG, params width/height/camera/save_path). - Inspect a scene:
get_scene_hierarchy→ nodes carryinstanceId+hierarchyPath→ feed intoget_component_properties/set_component_properties. - Escape hatch:
eval --code "<C#>"runs arbitrary C# via Roslyn (5s default timeout);eval_filefor a.csfile;menu --path "Assets/Reimport All"runs any menu item (omit path to list all). Policy: prefer a registered command when one exists; useevalprimarily for read-only inspection; ask before eval/menu calls that write files, start processes, or touch the network; verify the effect afterwards.
Field-tested gotchas (verified on this project, 2026-07)
/api/execbody key isparameters, notparams. The wrong key doesn't error on commands whose args are all optional — it silently binds nothing. If a command behaves like you passed no args, check this first.- Entering play mode rotates the evalToken (domain reload). Never reuse a token read before
editor_play— split scripts at every play/recompile/package boundary and re-read the descriptor after. get_console_logscan returntotal: 0even when the game is running. Don't treat an empty result as "no errors" — cross-check by tailing%LOCALAPPDATA%\Unity\Editor\Editor.log.evalis the best truth source for runtime state.list_open_scenesreflects the edit-mode setup; in play mode querySceneManagerviaevalinstead.- Interpolated strings inside
evalcode work fine; return a single string built withStringBuilderfor multi-part dumps.
Full reference (read as needed)
- references/commands-content.md — assets & files, scenes, GameObjects & components, prefabs, scripts, animation/animator/timeline, materials & shaders (every command + every parameter).
- references/commands-editor.md — baking (lighting/navmesh/occlusion), search & selection, capture, build/compile/tests, project settings, package manager, editor lifecycle & observability, runtime/player commands, hot reload.
- references/connectivity-and-setup.md — descriptors, ports, auth, runtime (Player) server setup, log file locations.
- references/extending.md — writing your own
[CliCommand]s, the authoring contract (ProjectPaths/ObjectResolver/AuthoringUndoScope), safety conventions, hot-reload authoring ([HotReload]/[HotReloadWithOverrides]), test architecture.