Control the Unity Editor from the command line. No manual server startup, no port management — the bridge starts when the Editor opens, and the CLI finds the right project automatically.
Most Unity automation tools fall into two camps:
MCP mega-tools wrap dozens of actions behind a Python/Node intermediary. Every call hops through AI → MCP server → HTTP → Unity plugin. More moving parts, more latency, more failure modes.
Dynamic code execution sends raw C# to Unity and reads results from Debug.Log. Infinitely flexible, but the LLM must generate correct C# every time, and reading results requires a second call.
unity-cli-bridge takes a third path: declarative commands over direct IPC.
CLI ──── local IPC ──── Unity Editor (bridge)
(Named Pipe on Windows, Unix socket on macOS/Linux)
- One hop, no intermediary. CLI talks directly to the Editor over local IPC. Average response: 265 ms.
- Declarative, not imperative.
scene add-object --primitive Cube --position 3,0,0instead of writing C# code. The LLM picks options, not APIs. - Token-aware responses.
--output compactstrips envelope metadata.--omit-defaultscuts material info by 71% and scene inspect by 41%. - Structured results in stdout. No polling, no log scraping, no file reads. Every command returns JSON immediately.
- Fail fast, fail loud. Invalid options get a clear error. Unrecognized patch keys return warnings instead of silent success.
See Benchmark: 3-Way Comparison for measured results against two other approaches on the same scenario.
Option A: Unity Package Manager
In Unity, open Package Manager and choose Add package from git URL. Paste:
https://github.com/yhc509/unity-cli-bridge.git?path=/unity-package/com.yhc509.unity-cli-bridge#main
Option B: Edit Packages/manifest.json manually
Add the following to your Packages/manifest.json:
{
"dependencies": {
"com.yhc509.unity-cli-bridge": "https://github.com/yhc509/unity-cli-bridge.git?path=/unity-package/com.yhc509.unity-cli-bridge#main"
}
}The bridge starts automatically when the Editor opens. No configuration needed.
Upgrading from 0.3.x?
0.4.0bumps the wire protocol to5, so a0.3.xCLI cannot talk to a0.4.0package. From0.4.1on you no longer have to choose: install the CLI from each project's Window > Unity CLI Manager, and theunity-clion your PATH hands off to the version that matches whichever project you are pointing at.
Option A: From Unity Editor (recommended)
Open Window > Unity CLI Manager in the Editor menu. Click Install CLI — the CLI matching this project's package version is downloaded automatically.
The install is versioned, and ~/.unity-cli-bridge/unity-cli/ stays the directory you put on your PATH:
~/.unity-cli-bridge/
unity-cli/
unity-cli # PATH target -> newest installed version
versions/
0.3.5/unity-cli # protocol 4
0.4.1/unity-cli # protocol 5
Projects can sit on different package versions. Run Install CLI once from each project's CLI Manager. When a command reaches a bridge that speaks an older protocol, the CLI re-runs itself as the matching version — same unity-cli command, no flags, nothing to configure. If no installed version speaks that protocol, the command fails with PROTOCOL_MISMATCH and tells you to run Install CLI in that project.
Old versions are never deleted automatically (each binary is ~78 MB). The CLI Manager lists what is installed and gives each version a Remove button.
Heads-up when upgrading: a project still on package
0.4.0or earlier has the old CLI Manager, which installs a single flat binary over~/.unity-cli-bridge/unity-cli/and clobbers the dispatcher. To recover, open Window > Unity CLI Manager in any project on0.4.1+ and click Install CLI — it restores the PATH target and files away the flat binary it found.What happens to that flat binary. If a previous CLI Manager installed it, its version is on record: it is filed under
versions/<version>/and stays available for hand-off — which is how a project still on0.3.xkeeps working without any change of its own. If it was downloaded by hand (Option B), there is no version on record and nothing in the binary reports one, so its wire protocol is unknown and it cannot be a hand-off candidate. It is not deleted — it is moved to~/.unity-cli-bridge/orphaned/and listed in the CLI Manager under Unidentified Binaries, so you can still get at it or remove it yourself.Either way, the old Manager cannot install a versioned CLI for its own project. If a
0.3.xproject ends up with no CLI that speaks its protocol, upgrade that project's package to0.4.1+ and install from its own CLI Manager.
Option B: Manual download
Download from GitHub Releases:
| Platform | File |
|---|---|
| macOS (Apple Silicon) | unity-cli-osx-arm64.tar.gz |
| Windows (x64) | unity-cli-win-x64.zip |
Extract and add the binary to your PATH. A manually placed binary carries no version metadata, so it is not a hand-off candidate: it only talks to projects whose package speaks its own protocol. Use Option A if you work across projects on different package versions.
Tip: A short, fixed install path (
~/.unity-cli-bridge/unity-cli/) saves tokens when AI agents invoke the CLI repeatedly — every character in the path is repeated on each call.
In the Unity Editor, open Window > Unity CLI Manager. Select your AI tool (Claude Code or Codex), keep Scope set to Project unless you need a global install, and click Install Skill.
Once installed, the same section offers Remove Skill for the selected tool and scope.
Project-scoped installs go under <project-root>/.claude/skills/ or <project-root>/.codex/skills/. Commit that folder if you want the team to share the same skill version as the Unity package.
The skill teaches AI agents how to pick the right commands, run them safely, and verify results with read-console. The installed SKILL.md records the package version it came from, so the CLI Manager can tell you when the skill needs an update.
Check that the CLI can reach the running Editor:
unity-cli status --project /path/to/your-project --jsonIf two worktrees happen to collide on the same 12-character SHA256 prefix, the bridge still listens on separate socket/pipe names. Use
--project <path>to route by canonical project root.
When --project is omitted, the CLI first uses the current Unity project directory, then an explicit default set with unity-cli instances use <projectPath|projectName>, then the single live Editor if exactly one is running. If multiple live Editors remain and none is pinned, the command fails with a usage error that lists candidates instead of guessing.
unity-cli status --project MyGame # Editor state, Unity version, current scene
unity-cli play / pause / stop # Play Mode control
unity-cli compile # Trigger recompile
unity-cli compile --wait # Wait until compile/import finishes and bridge is reachable
unity-cli refresh --wait # Refresh assets and wait for Editor readiness
unity-cli screenshot --path /tmp/shot.png # Game View capture (default), or --view scene
unity-cli screenshot --format jpg --quality 70 --max-width 1024 --path /tmp/shot.jpg
unity-cli record start --duration 5 --wait --path /tmp/play.mp4
unity-cli record start # Manual recording; stop with record stop
unity-cli record status
unity-cli record stop
unity-cli read-console --type error # Check for errors after any operation
unity-cli read-console --type error --no-stacktrace
unity-cli execute-menu --list "GameObject" # Browse Unity menus
unity-cli execute --code "Debug.Log(1);" --force # Run arbitrary C# (escape hatch)
unity-cli execute --code "Debug.Log(__pucArgsJson);" --args '{"k":"v"}' --force
unity-cli execute --code "__pucResult = new Vector3(1.5f, 0f, 3.25f);" --force
# 협력적 timeout — 사용자 코드는 __pucToken 체크 필요
unity-cli execute --file my-script.cs --force --timeout 60execute --args exposes the supplied JSON as __pucArgsJson; do not declare variables with the reserved __puc_internal_* prefix in user code. Avoid putting secrets or credentials in --args, because CodeDOM compilation can briefly create temporary .cs files under the OS temp directory.
Assign a value to __pucResult when the caller needs a structured return value. The response includes hasResult: true and result as type-preserving JSON, with float formatted using G9 and double using G17 for round-trip precision. Editor-assembly custom [PucCommand] methods can return ExecuteValueSerializer.Serialize(obj); runtime-assembly commands must serialize precise JSON themselves.
--timeout(기본 30초, 상한 600초)은 협력적 cancel입니다. 사용자 코드가 __pucToken.ThrowIfCancellationRequested()(또는 다른 token API)를 호출해야 강제 종료됩니다. 호출하지 않으면 main thread를 계속 점유하므로 --force를 쓰는 사용자가 책임집니다. CLI는 Editor가 EXECUTE_TIMEOUT을 보고할 때까지 기다리도록 IPC 타임아웃을 --timeout보다 길게 잡습니다 — 그래서 --timeout보다 짧은 --timeout-ms를 줘도 그 값으로 먼저 끊지 않습니다.
record start captures the Play Mode Game View as mp4 via Unity Recorder. It returns STARTED plus a recordingId immediately; add --duration N --wait to auto-stop and poll until the mp4 is finalized. Without --duration, stop manually with record stop. Outputs default to Library/com.yhc509.unity-cli-bridge/recordings/; pass --path to move the finalized mp4.
Recording depends on Unity Recorder. The package requires Unity 2023.1 or newer and pins com.unity.recorder 5.1.6, which includes the compatibility fixes older Recorder releases need for Unity 6000.4 and newer.
unity-cli asset find --type Material # Find by type
unity-cli asset find --name Player --type Prefab # Find by name + type
unity-cli asset info --path Assets/Scenes/Main.unity # Asset metadata
unity-cli asset create --type material --path Assets/Materials/Red # Create
unity-cli asset mkdir --path Assets/NewFolder # Create folder
unity-cli asset move --from ... --to ... --force # Move/rename
unity-cli asset delete --path ... --force # Delete# Open and inspect
unity-cli scene open --path Assets/Scenes/Main.unity
unity-cli scene inspect --path ... --with-values --omit-defaults
unity-cli scene inspect --path ... --node "/Player[0]/Arm[0]" --max-depth 2
# Build scenes with convenience commands
unity-cli scene add-object --path Assets/Scenes/Main.unity --name Cube --primitive Cube \
--parent "/Environment[0]" --position 3,0,0
# → Response includes createdPath — no follow-up inspect needed
unity-cli scene set-transform --node "/Cube[0]" --position 0,1,0 --scale 2,2,2
unity-cli scene assign-material --node "/Cube[0]" --material Assets/Materials/Red.mat
# Component operations
unity-cli scene list-components --node "/Cube[0]"
unity-cli scene add-component --path Assets/Scenes/Main.unity --node "/Player[0]" --type Rigidbody --values '{"mass":5}'
unity-cli scene remove-component --path Assets/Scenes/Main.unity --node "/Player[0]" --type BoxCollider --index 0 --force
# Or use spec-based patching for complex edits
unity-cli scene patch --path ... --spec-file patch.jsonunity-cli prefab create --path Assets/Prefabs/Enemy.prefab \
--spec-json '{"root":{"name":"Enemy","children":[...]}}'
unity-cli prefab inspect --path ... --with-values
unity-cli prefab inspect --path ... --node "/Visual[0]" --max-depth 2
unity-cli prefab patch --path ... --spec-json '{"operations":[...]}'
unity-cli prefab patch --path ... --spec-file destructive-patch.json --force
# Component operations
unity-cli prefab list-components --path Assets/Prefabs/Player.prefab --node "/Visual[0]"
unity-cli prefab add-component --path Assets/Prefabs/Player.prefab --node "/Visual[0]" --type Rigidbody --values '{"mass":5}'
unity-cli prefab remove-component --path Assets/Prefabs/Player.prefab --node "/Visual[0]" --type BoxCollider --forcePatch ops: add-child, remove-node, set-node, add-component, remove-component, set-component-values
Friendly component keys are available in scene/prefab value patches for common Unity components:
- Rigidbody:
mass,damping,angularDamping,useGravity,isKinematic,constraints - Collider family:
isTrigger,material,contactOffset, plussize,radius,height,mesh,convex - Renderer family:
materials,materials[0],receiveShadows,shadowCastingMode,lightProbeUsage - Light:
color,intensity,range,type,shadows,shadowStrength - Camera:
fieldOfView,nearClipPlane,farClipPlane,backgroundColor,orthographicSize
unity-cli material info --path Assets/Materials/Red.mat --omit-defaults
unity-cli material set --path ... --property _BaseColor --value 1,0,0,1
unity-cli material set --path ... --texture _MainTex --asset Assets/Textures/wood.pngunity-cli package list
unity-cli package list --filter input --limit 5
unity-cli package add --name com.unity.inputsystem
unity-cli package remove --name ... --force
unity-cli package search --query "input"package list --filter matches package name or displayName case-insensitively, and --limit trims the filtered result to reduce response size. Package Manager commands use a 360-second CLI live timeout so the bridge can return its 300-second PACKAGE_TIMEOUT response. They are single-flight inside the Editor; a second package command returns PACKAGE_BUSY until the active request completes.
unity-cli test list --mode all
unity-cli test list --mode all --no-detail
unity-cli test run --mode edit --filter PlayerControllerTests
unity-cli test run --mode play --filter Smoke --wait --failures-only
unity-cli test results --run-id <runId> --failures-only
unity-cli test canceltest run --mode edit returns a synchronous result payload. test run --mode play starts the run and returns STARTED plus a runId; add --wait when the CLI should poll until the PlayMode result is ready. --filter is a case-insensitive substring match against each test's full name (Namespace.Class.Method), so a value like Smoke also matches every method on a SmokeTests class — when the class and method share a prefix, narrow it with a more specific fragment such as Smoke_. Cached results are stored under Library/com.yhc509.unity-cli-bridge/test-runs/<runId>.json and can be retrieved with test results. A non-Completed cached run result returns an error envelope and CLI exit code 1. Add --failures-only to test run or test results to keep summary counts intact while trimming tests[] to non-passed entries; add --no-detail to test list to return only fullName and mode. test cancel releases a stuck test-run lock: it attempts a graceful TestRunnerApi cancel then always clears the lock, returning the cancelled runId, or a no-op success when no run is active.
--no-domain-reload is an opt-in PlayMode-only speed option. It can avoid 30-120 seconds of domain reload overhead, but static state can leak between runs, so only use it for suites that reset their own state:
unity-cli test run --mode play --filter Smoke --wait --no-domain-reloadA typical AI repair loop is: make a focused code change, unity-cli refresh, run the relevant EditMode filter, inspect failing test names and messages from the JSON result, patch the code, and rerun the same command until it passes.
unity-cli qa click --qa-id StartButton
unity-cli screenshot --view game --path /tmp/qa-reference.png
unity-cli screenshot --view game --format jpg --quality 70 --max-width 1024 --path /tmp/qa-reference.jpg
unity-cli qa ui-dump --json
unity-cli qa ui-dump --text Start --interactable-only --limit 20 --omit-rect --json
unity-cli qa world-dump --json
unity-cli qa world-dump --text Enemy --limit 20 --json
unity-cli qa tap --x 400 --y 300
unity-cli qa tap --target /Battle/Units/Unit_Erich
unity-cli qa tap --target /Battle/EndTurnZone --button right
unity-cli qa swipe --from 100,200 --to 300,400
unity-cli qa swipe --target ... --from 0,0 --to 100,0 --duration 500
unity-cli qa key --key space
unity-cli qa wait-until --scene GameScene --timeout 5000
unity-cli qa wait-until --object-interactable StartButton --timeout 5000
unity-cli qa wait-until --object-gone LoadingSpinner --timeout 5000
unity-cli qa run-sequence --spec-json @seq.json --timeout 60000
unity-cli qa run-sequence --spec-json @seq.json --record --record-path /tmp/qa-seq.mp4qa ui-dump returns clickable UI elements with hierarchy path, visible text, interactable, and image-space rect/center fields. Feed a returned path to qa click --target, or centerX/centerY to qa tap --x --y. Add --limit N, --interactable-only, and --text <substring> to filter server-side after top-to-bottom sorting; add --omit-rect to omit x/y/width/height while keeping centerX/centerY.
qa world-dump lists non-UI world objects that opt in via IQaTappable (implement on your own component) or the QaTappable marker component. Each entry has a label, hierarchy path, image-space centerX/centerY, onScreen, and hasAction. Feed a path to qa tap --target: left click invokes the object's TryQaTap() action when present, otherwise simulates an Input System tap at the object's anchor; right click uses pointer handlers when available and otherwise simulates right-button Input System input. Off-screen objects are excluded unless --include-offscreen is passed. Add --limit N and --text <substring> to filter server-side; filtered responses keep onScreen when --include-offscreen is passed, otherwise omit it, and omit constant hasAction when it carries no per-element signal.
qa click, qa tap, and qa swipe default to --button left; pass --button right to drive right-click or right-drag input paths.
When multiple qa wait-until conditions are supplied, every condition must be satisfied (AND). qa wait-until --object-interactable waits for an active target whose effective interactable state is true; non-Selectable objects without that state are treated as interactable once active. qa wait-until --object-gone waits until an active target can no longer be resolved, which covers deactivated or destroyed objects.
qa run-sequence sends a JSON steps array to the bridge in one deferred request. Each step waits for all conditions, then runs its actions without a per-step CLI round trip. Conditions can check active, gone, transform, scene, log, interactable, or game state exposed by IQaQueryable; transform and query conditions support ==, !=, >=, <=, near, and changed. Actions support key, tap, swipe, and wait. On timeout the response reports completedSteps, failedStep.unmet, and failedStep.stateSnapshot. Add --record to capture the sequence interval as mp4; the response includes recordingPath when the recording finalizes.
{
"steps": [
{
"name": "confirm-ready",
"wait": [
{ "target": "/State", "query": "phase", "op": "==", "value": "Ready" }
],
"actions": [
{ "key": "space" }
]
}
]
}Implement UnityCliBridge.Bridge.IQaQueryable on a game component to expose state values for query conditions. Supported return values are numbers, bool, string, Vector2, and Vector3; unknown keys should return false.
Same-named sibling world objects share a path and resolve to the first match; give tappable objects unique names/labels to target them individually.
screenshot responses include both image size (width/height) and live input metadata (screenWidth/screenHeight, imageOrigin=top-left, coordinateOrigin=bottom-left). qa tap takes screenshot image coordinates as-is, reuses the last successful screenshot dimensions when --screenshot-width/--screenshot-height are omitted, and lets the bridge handle Y-flip plus resolution scaling into Unity screen space. See qa-testing.md for the coordinate workflow.
For AI agent workflows, minimize token consumption:
# --output compact: strip envelope metadata, return data payload only
unity-cli asset info --path ... --output compact
# --omit-defaults: strip default/identity values from inspect and material responses
unity-cli scene inspect --path ... --with-values --omit-defaults # 41% smaller
unity-cli material info --path ... --omit-defaults # 71% smaller
# --max-depth: limit hierarchy traversal depth
unity-cli scene inspect --path ... --max-depth 2
# dump/test/list trims: opt in only, default output remains unchanged
unity-cli qa ui-dump --text Start --interactable-only --limit 10 --omit-rect
unity-cli qa world-dump --text Enemy --limit 10
unity-cli read-console --limit 20 --no-stacktrace
unity-cli test run --mode edit --filter Player --failures-only
unity-cli test list --mode all --no-detail
unity-cli instances list --briefcli/UnityCli.Cli/ CLI executable (.NET 9, macOS arm64 + Windows x64)
cli/UnityCli.Protocol/ Shared protocol (linked from Unity package)
unity-package/ com.yhc509.unity-cli-bridge (UPM package)
tools/skills/unity-cli-operator/ AI agent skill (dev copy; end-users install via CLI Manager)
tests/ xUnit CLI tests + live IPC test scenarios
docs/ Generated CLI reference, specs
- Destructive or dangerous ops require
--force:asset delete,execute,package remove,scene remove-component,prefab remove-component(always);asset move/asset rename/asset create/prefab createwhen overwriting an existing path; destructive operations insidescene patch(delete-gameobject,remove-component) andprefab patch(remove-node,remove-component). - Raw force payloads are explicit:
raw --forceonly injectsforce=truewhen the payload omitsforceor already sets it totrue; conflicting payload values fail fast. - Patch and overwrite rollback: Scene/prefab patch and asset overwrite flows create backups for the asset body and
.metaunderLibrary/com.yhc509.unity-cli-bridge/backups/, keeping rollback files outsideAssets/and normal git tracking. - Dirty scene patch refusal:
scene patchrefuses a target scene that is already loaded with unsaved changes, even with--force; save or discard the scene first. - Asset paths: Write operations are
Assets/...only. Reads allowPackages/...too. - Package Manager requests:
package list,package add,package remove, andpackage searchuse a longer live timeout and returnPACKAGE_BUSYif another package command is already running. - Scene paths:
/Root[0]/Child[0]format with sibling indices./is the virtual scene root. - Inspect before patch: Always
scene inspect --with-valuesorprefab inspect --with-valuesbefore writing patch specs. - Friendly component keys: Common Rigidbody, Collider, Renderer, Light, and Camera patch keys are resolved to Unity
SerializedProperty.propertyPathvalues. - set-node warnings: Unrecognized keys now return warnings instead of silent success.
The unity-cli-bridge skill teaches AI agents how to use the CLI safely: pick the right command, verify with read-console, follow inspect-before-patch patterns.
Install from Window > Unity CLI Manager in the Unity Editor — select your AI tool and scope, then click Install Skill. Once installed, use Remove Skill from the same section to remove that scoped copy. Project scope is the default and writes to <project-root>/.claude/skills/ or <project-root>/.codex/skills/; global scope writes to the user's agent skills directory. Supports Claude Code and Codex.
Requires the .NET 9 SDK. The projects that run (CLI, doc generator, tests) set
RollForward=Major, so a newer runtime (.NET 10+) also works when the .NET 9
runtime itself is not installed.
dotnet build UnityCliBridge.sln -c Debug
dotnet test UnityCliBridge.sln
dotnet run --project cli/UnityCli.DocGen -- --check # Verify docs match code- macOS arm64 and Windows x64 supported
- Live IPC required — commands fail fast when no Editor is running
- Scene patching targets saved
Assets/...unityscenes; multi-scene orchestration is out of scope - Prefab-internal object references and nested variants are not yet supported