Help us shape the future of Go! Take the 2026 Go Developer Survey

unity-cli-loop

module
v3.0.0-beta.80+incompa... Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: MIT

README

Unity CLI Loop

English | 日本語

Unity License
ClaudeCode Cursor Codex Antigravity GitHubCopilot

logo-black-bg
(Logo inspired by Daft Punk's Human After All album art)

Let an AI agent compile, test, and operate your Unity project from popular LLM tools via CLI.

Designed to keep AI-driven development loops running autonomously inside your existing Unity projects.

[!IMPORTANT]

  • What's New in V3 — the move to a native Go CLI, the end of port management, the new hot-reload / pause-point tools, automatic CLI updates with per-project version selection, and connections that stay stable with Unity in the background or several Editors running in parallel
  • Migrating Custom Tools and Skills to V3 — for anyone with C# custom tools or hand-written skills/scripts that invoke uloop. Everyone else migrates just by updating the package and the CLI

Concept

Unity CLI Loop is a Unity integration tool designed so that AI can drive your Unity project forward with minimal human intervention. Tasks that humans typically handle manually — compiling, running the Test Runner, checking logs, editing scenes, capturing windows to verify UI layouts, and even operating a freshly implemented feature to confirm it actually works — can all be carried out from LLM tools.

Unity CLI Loop is built around four core ideas:

  1. A self-hosted development loop where AI autonomously compiles, tests, inspects logs, and fixes issues — it can even pause execution at any source line without editing code and read the variables at that moment to pin down a cause. Method-body fixes can be applied instantly to the running game without waiting for a recompile. Uses compile, run-tests, get-logs, clear-console, pause-point, hot-reload.
  2. AI-driven Unity Editor operation — scene building, object manipulation, menu execution, and UI refinement from screenshots. Uses execute-dynamic-code, screenshot.
  3. PlayMode automated testing — AI clicks buttons, drags elements, presses keys, replays recorded input, and verifies game behavior. Uses simulate-mouse-ui, simulate-mouse-input, simulate-keyboard, replay-input, execute-dynamic-code, screenshot.
  4. Achieving the above with a minimal set of tools. See Design Philosophy.

https://github.com/user-attachments/assets/569a2110-7351-4cf3-8281-3a83fe181817

Installation

This section installs the Unity package. The CLI itself (a native binary) is installed after the package, in Quickstart Step 1. A terminal-only way to install the CLI without going through Unity is folded into the same step.

Via Unity Package Manager

  1. Open Unity Editor
  2. Open Window > Package Manager
  3. Click the "+" button
  4. Select "Add package from git URL"
  5. Enter the following URL:
https://github.com/hatayama/unity-cli-loop.git?path=/Packages/src

After the global uloop CLI is on PATH, install the Unity package from a terminal at the project root (or pass --project-path):

uloop package install
uloop package status

uloop package install writes the OpenUPM scoped registry and the io.github.hatayama.uloopmcp dependency into Packages/manifest.json. Pass --version <x.y.z> to pin a specific package version instead of OpenUPM dist-tags.latest. Use uloop package status to confirm the registry and dependency are present.

Manual setup in Unity Package Manager
  1. Open Project Settings window and go to Package Manager page
  2. Add the following entry to the Scoped Registries list:
Name: OpenUPM
URL: https://package.openupm.com
Scope(s): io.github.hatayama.uloopmcp
  1. Open Package Manager window and select OpenUPM in the My Registries section. Unity CLI Loop will be displayed.

Quickstart

Step 1: Install the CLI

Select Window > Unity CLI Loop > Settings. A dedicated window will open. If the CLI button is not highlighted in blue, press Install CLI.

The installer places the global uloop command on PATH. Project-specific uloop-project-runner binaries are downloaded into the user cache automatically from each project's .uloop/project-runner-pin.json.

Working with V2 projects side by side

Keep the v3 dispatcher installed when working with both v2 and v3 projects. If Unity resolves a project to a v2 io.github.hatayama.uloopmcp package, the dispatcher automatically installs the matching v2 uloop-cli release into its versioned user cache and delegates the command to it. The resolved package version takes precedence over a stale v3 project-runner pin left after a downgrade. The initial npm installation and the v2-mode notice are written to stderr so stdout remains the delegated command's output. V3 projects continue to use the project runner selected by their pin.

The global install, update, uninstall, and launch commands remain owned by the v3 dispatcher in every project. Other project commands, help, and the project-scoped version request are delegated for detected v2 projects.

V2 delegation requires Node.js 22 or later, including npm for the first command that populates the cache. Do not press Update CLI or Downgrade CLI in a v2 project's Settings window. These buttons are normally hidden because the delegated CLI reports the matching v2 version, but using one can restore a global npm CLI that hides the v3 dispatcher depending on PATH order.

CLI-only terminal install

Use this only when you want to install the standalone global CLI without opening Unity package setup.

Homebrew (macOS)
brew install hatayama/tap/uloop

Updates are managed by brew upgrade uloop. The formula is refreshed automatically on every dispatcher release.

Shell installer (curl / PowerShell)

The installer verifies the downloaded archive against the digest list in Packages/src/project-runner-pin.json (same pin Unity's Install CLI button uses). Optional env: ULOOP_REF (git ref for the pin; default main), ULOOP_INSTALL_DIR. ULOOP_VERSION is accepted only when it matches the pin's dispatcherReleaseTag.

[!NOTE] The terminal install trusts the same repository pin as the Unity GUI. The manual flow that passes an explicit ULOOP_ARCHIVE_MANIFEST (derived from Sigstore verification) remains as a hardened option for choosing an arbitrary release tag.

On macOS or Windows Git Bash:

curl -fsSL https://raw.githubusercontent.com/hatayama/unity-cli-loop/main/scripts/install.sh | sh

On Windows Git Bash the installer extracts the dispatcher zip with unzip, then tar (bsdtar / Windows tar.exe), then PowerShell Expand-Archive. unzip is not required when a fallback is available.

On Windows PowerShell:

irm https://raw.githubusercontent.com/hatayama/unity-cli-loop/main/scripts/install.ps1 | iex
Manual attestation-verified install (choose your own release tag)

Install gh and jq through your operating-system or package channel first. The bootstrap does not install or fall back from gh. Select the immutable dispatcher Release tag and its source branch; use refs/heads/main for a main release or refs/heads/v3-beta for a v3-beta release.

On macOS or Windows Git Bash:

REPOSITORY=hatayama/unity-cli-loop
RELEASE_TAG=dispatcher-v<RELEASE_VERSION>
SOURCE_REF=refs/heads/v3-beta
tmp_dir=$(mktemp -d)
gh release download "$RELEASE_TAG" --repo "$REPOSITORY" --pattern 'install.sh' --pattern 'install.sh.sigstore.json' --dir "$tmp_dir" && \
tag_sha=$(gh api "repos/$REPOSITORY/commits/$RELEASE_TAG" --jq .sha) && \
gh attestation verify "$tmp_dir/install.sh" --bundle "$tmp_dir/install.sh.sigstore.json" --repo "$REPOSITORY" --signer-workflow "$REPOSITORY/.github/workflows/dispatcher-publish.yml" --source-ref "$SOURCE_REF" --source-digest "$tag_sha" && \
manifest=$(jq -r '.dsseEnvelope.payload | @base64d | fromjson | .subject[] | "\(.digest.sha256)  \(.name)"' "$tmp_dir/install.sh.sigstore.json" | LC_ALL=C sort) && \
ULOOP_VERSION="$RELEASE_TAG" ULOOP_ARCHIVE_MANIFEST="$manifest" sh "$tmp_dir/install.sh"

On Windows PowerShell:

$repository = 'hatayama/unity-cli-loop'
$releaseTag = 'dispatcher-v<RELEASE_VERSION>'
$sourceRef = 'refs/heads/v3-beta'
$temporaryDirectory = New-Item -ItemType Directory -Force -Path (Join-Path $env:TEMP ([guid]::NewGuid()))
gh release download $releaseTag --repo $repository --pattern 'install.ps1' --pattern 'install.ps1.sigstore.json' --dir $temporaryDirectory.FullName
if ($LASTEXITCODE -ne 0) { throw 'Installer download failed.' }
$tagSha = gh api "repos/$repository/commits/$releaseTag" --jq .sha
if ($LASTEXITCODE -ne 0) { throw 'Release tag resolution failed.' }
gh attestation verify (Join-Path $temporaryDirectory.FullName 'install.ps1') --bundle (Join-Path $temporaryDirectory.FullName 'install.ps1.sigstore.json') --repo $repository --signer-workflow "$repository/.github/workflows/dispatcher-publish.yml" --source-ref $sourceRef --source-digest $tagSha
if ($LASTEXITCODE -ne 0) { throw 'Installer attestation verification failed.' }
$bundle = Get-Content -Raw -Encoding UTF8 (Join-Path $temporaryDirectory.FullName 'install.ps1.sigstore.json') | ConvertFrom-Json
$statement = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($bundle.dsseEnvelope.payload)) | ConvertFrom-Json
$manifest = [string]::Join("`n", @($statement.subject | ForEach-Object { "$($_.digest.sha256)  $($_.name)" } | Sort-Object))
$env:ULOOP_VERSION = $releaseTag
$env:ULOOP_ARCHIVE_MANIFEST = $manifest
& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $temporaryDirectory.FullName 'install.ps1')

After installing the native CLI, the installer automatically tries to remove the old npm package with npm uninstall -g uloop-cli. If npm is unavailable or the old command belongs to a different Node prefix, the installer prints the manual command to run:

npm uninstall -g uloop-cli

Do not install a v2 CLI globally to switch projects. If your terminal resolves uloop to an old npm installation instead of the native dispatcher, remove the npm installation and reinstall the native dispatcher:

npm uninstall -g uloop-cli
# Run the verified native installer above again.
which uloop
uloop --version

On Windows PowerShell:

npm uninstall -g uloop-cli
# Run the verified native installer above again.
Get-Command uloop
uloop --version
The Settings window with the CLI not yet installed, showing the Install CLI button

The Settings window shows whether the global uloop command is detected.

If you see the following display, the installation was successful.

The Settings window after successful CLI detection, showing a green indicator and the CLI version

Step 2: Install Skills

Select your target (Claude Code, Codex, etc.) and press the Install Skills button.

The Skills section of the Settings window, with a target selected and the Install Skills button ready
To install from terminal
# Install for Claude Code project
uloop skills install --claude

# Install for OpenAI Codex project
uloop skills install --codex

# Or install globally
uloop skills install --claude --global

# Or sync into a custom directory (e.g. an external skill-package store),
# flat as <path>/<skill-name>; unmanaged top-level files there are left
# untouched (skill-owned directories such as references/ are replaced wholly)
uloop skills install --output-dir path/to/skills

That's it! After installing Skills, LLM tools can automatically handle instructions like these:

Your Instruction Skill Used by LLM Tools
"Launch Unity for this project" /uloop-launch
"Fix the compile errors" /uloop-compile
"Apply this fix right now without compiling" /uloop-hot-reload
"Run the tests and tell me why they failed" /uloop-run-tests + /uloop-get-logs
"Check the scene hierarchy" /uloop-get-hierarchy
"Play the game and bring Unity to the front" /uloop-control-play-mode + /uloop-focus-window
"Bulk-update prefab parameters" /uloop-execute-dynamic-code
"Take a screenshot of Game View and adjust the UI layout" /uloop-screenshot + /uloop-execute-dynamic-code
"Replay the recorded input" /uloop-replay-input
"Pause at this line and investigate the bug" /uloop-pause-point
All 18 Bundled Skills
  • /uloop-launch - Launch Unity with correct version
  • /uloop-compile - Execute compilation
  • /uloop-get-logs - Get console logs
  • /uloop-run-tests - Run tests
  • /uloop-hot-reload - Apply method-body changes to running code instantly, without recompiling
  • /uloop-clear-console - Clear console
  • /uloop-focus-window - Bring Unity Editor to front
  • /uloop-get-hierarchy - Get scene hierarchy
  • /uloop-find-game-objects - Find GameObjects
  • /uloop-screenshot - Capture EditorWindow
  • /uloop-pause-point - Pause execution at any line and capture variables
  • /uloop-set-game-view-size - Read and set the Game View custom resolution
  • /uloop-simulate-mouse-ui - Simulate mouse click, long-press, and drag on PlayMode UI elements
  • /uloop-simulate-mouse-input - Simulate mouse input in PlayMode via Input System
  • /uloop-simulate-keyboard - Simulate keyboard input in PlayMode via Input System
  • /uloop-replay-input - Replay input recorded in the Recordings window during PlayMode
  • /uloop-control-play-mode - Control Play Mode
  • /uloop-execute-dynamic-code - Execute dynamic C# code
Direct CLI Usage (Advanced)

You can also call the CLI directly without using Skills:

# List available tools
uloop list

# Launch Unity project with correct version
uloop launch

# Launch with build target (Android, iOS, StandaloneOSX, etc.)
uloop launch -p Android

# Kill running Unity and restart
uloop launch -r

# Execute compilation
uloop compile

# Compile without waiting for Domain Reload
uloop compile --no-wait-for-domain-reload

# Apply the method bodies of changed .cs files to running code without recompiling
uloop hot-reload

# Get logs
uloop get-logs --max-count 10

# Run tests
uloop run-tests --filter-type all

# Execute dynamic code
uloop execute-dynamic-code --code 'using UnityEngine; Debug.Log("Hello from CLI!");'

Configuration for Claude Code

Claude Code runs shell commands inside a sandbox, and the sandbox blocks network access by default. The IPC connection to Unity is also affected, so uloop fails with UNITY_NOT_REACHABLE (connect: operation not permitted) at the moment it tries to connect — even when Unity itself is running fine.

Add uloop * to sandbox.excludedCommands in ~/.claude/settings.json to exclude uloop commands from the sandbox:

{
  "sandbox": {
    "excludedCommands": ["uloop *"]
  }
}

The pattern is matched against the command string as typed, so invocations starting with uloop are excluded. See docs/claude-code-sandbox.md for details and measured results.

How It Works

A uloop command reaches the Unity Editor through the following chain:

  • The global uloop dispatcher — the single entry point on PATH. It interprets the command and delegates it to the runner for the target project
  • uloop-project-runner — the per-project runner. The version to use is determined by each project's .uloop/project-runner-pin.json (the pin) and downloaded automatically into a per-version user cache, which is how multiple projects on different versions coexist on one machine → project runner pin details
  • The IPC server inside the Unity Editor — accepts connections from the runner, executes Unity APIs, and returns the results

The connection uses no TCP ports. It goes over a Unix domain socket on macOS/Linux and a named pipe on Windows, so there is no port to configure and no port collision with other Editor instances.

Design Philosophy

Unity CLI Loop does not chase tool count. With dynamic C# code execution (execute-dynamic-code), almost any Unity Editor operation can be accomplished through a single tool.

Too many tools make it harder for AI to choose the right one. And even when tools are packaged as Skills, each tool's description still consumes the context window. We believe keeping tools to the essential minimum is good design.

Dedicated tools exist only for operations that dynamic code execution cannot handle by nature — such as frame-spanning input simulation and screenshot capture — and for operations called so frequently in the development loop, like compile and get-logs, that generating C# code each time would waste tokens.

When you find yourself wanting a new dedicated tool, a Skill usually suffices. Write the routine operation down as SKILL.md instructions, a shell script, or a C# snippet passed to execute-dynamic-code, and the AI just invokes it. The skill body is loaded only at startup and nothing has to be regenerated per run, so the additional token cost is close to zero. See the Custom Tool Development Guide for how to build one.

Key Features

For detailed descriptions and usage examples of every tool, see the Tool Reference.

Development Loop Tools

  • compile - Run compilation and return errors and warnings
  • get-logs - Retrieve the same logs as the Console, filtered by type or search string
  • run-tests - Run Unity Test Runner (PlayMode / EditMode)
  • hot-reload - Apply method-body changes to running code instantly, without recompiling
  • pause-point - Pause PlayMode at any line without editing code and read the variables at that moment

Unity Editor Automation & Discovery Tools

  • clear-console - Clear the Console logs
  • find-game-objects - Search scene objects and inspect components
  • get-hierarchy - Retrieve the scene structure as JSON
  • focus-window - Bring the Unity Editor window to the front
  • screenshot - Save screenshots of EditorWindows or the Game View
  • set-game-view-size - Read and set the Game View custom resolution
  • control-play-mode - Play, stop, and pause Play Mode
  • execute-dynamic-code - Execute dynamic C# code

PlayMode Automated Testing Tools

  • simulate-mouse-ui - Simulate mouse operations on UI elements (via EventSystem)
  • simulate-mouse-input - Simulate mouse input via Input System
  • simulate-keyboard - Simulate keyboard input via Input System
  • replay-input - Replay input recorded in the Recordings window during PlayMode

Unity CLI Loop Extension Development

Add project-specific custom tools in a type-safe way without touching the core package. Ship a Skill/SKILL.md alongside your tool and AI agents discover it automatically.

For implementation steps and how to write Skills, see the Custom Tool Development Guide.

Other

Unity CLI Loop Files

UserSettings/UnityCliLoopSettings.json stores per-user editor settings and should always remain local-only.

The .uloop/ directory at the project root stores CLI cache, tool registry, and runtime outputs. Most of its contents are local-only, but some files can optionally be git-tracked for team sharing.

File Purpose Git-track?
project-runner-pin.json Project runner version contract used by the global dispatcher Yes
settings.tools.json Per-tool enable/disable preferences Optional
tools.json Auto-generated CLI tool registry No
outputs/ Runtime outputs (test results, screenshots, hierarchy dumps) No

[!TIP] Recommended .gitignore pattern

**/.uloop/*
!**/.uloop/project-runner-pin.json
!**/.uloop/settings.tools.json

This ignores auto-generated files and runtime outputs while allowing the dispatcher pin and team-shared configuration to be tracked. Remove the ! line if you don't need to share tool enable/disable preferences.

License

MIT License

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL