MackySoft.Ucli
0.9.0
dotnet tool install --global MackySoft.Ucli --version 0.9.0
dotnet new tool-manifest
dotnet tool install --local MackySoft.Ucli --version 0.9.0
#tool dotnet:?package=MackySoft.Ucli&version=0.9.0
nuke :add-package MackySoft.Ucli --version 0.9.0
uCLI - Reviewable Unity automation for agents, CI, and tools
Created by Hiroya Aramaki (Makihiro)
uCLI turns Unity Editor changes into reviewable, repeatable, machine-readable workflows for scripts, CI, and AI agents.
It reads Unity state, declares the intended change, applies it through Unity Editor APIs with validation and planning, chooses the save boundary, and returns structured evidence.
uCLI is not a remote-control wrapper around the Unity Editor. Use it when automated Unity changes need to be inspected, replayed, gated, and trusted.
โ Why uCLI?
uCLI starts from a simple premise:
- Unity is callable.
- Unity changes should be reviewable.
- The normal workflow is
status -> ready -> read -> call --withPlan -> verify.
uCLI keeps automated Unity changes reviewable: inspect the plan before mutation, see what happened after execution, and diagnose uncertain Editor state.
| Concern | What you need to know | How uCLI handles it |
|---|---|---|
| Editor readiness | Whether Unity can accept the request now. | Compile, domain reload, busy, play mode, shutdown, and blocked states are surfaced as readiness states or structured errors. |
| Live Unity state | Whether the mutation is applied through Unity itself. | Mutations go through Unity Editor APIs and re-resolve against live Unity state. |
| Edit context | Which scene, prefab, asset, or project context owns the edit. | Every edit declares a scene, prefab, asset, or project context. |
| Planned writes and drift | Whether planning is explicit and Unity state still matches the reviewed plan. | ucli call --withPlan validates, plans, and applies in one command; ucli plan and --planToken support separated review gates and validate request/state before mutation. |
| Project and worktree identity | Which project owns local state, indexes, artifacts, and launch coordination. | Daemon state, indexes, and artifacts are scoped by project identity; Unity process launches are serialized by physical project root. |
| Persistence | Whether a mutation also saved project data. | commit controls persistence with "none", "context", or "project". |
| Evidence | What changed, what was touched, and where diagnostics live. | JSON exposes opResults, applied, changed, touched, errors, logs, and artifacts. |
โจ What You Can Do
Use uCLI for automated Unity work that needs structured state, planned writes, and verifiable results:
- Agents can inspect Unity state, apply reviewed edits, and base follow-up decisions on structured evidence.
- CI jobs can run Unity-backed checks, tests, and builds without scraping editor logs as the primary result.
- Local tools can reuse daemon sessions, read indexes, and project-scoped artifacts across repeated commands.
๐ฆ Installation
โ Requirements
- .NET 8 or later.
- A Unity project with NuGetForUnity when installing the Unity plugin.
๐ป CLI
Install the CLI and confirm the command is available:
dotnet tool install --global MackySoft.Ucli --version <version>
ucli --version
Update an existing installation:
dotnet tool update --global MackySoft.Ucli --version <version>
๐ฎ Unity Plugin
Install MackySoft.Ucli.Unity from NuGetForUnity's Manage NuGet Packages window. The Unity project must be able to restore packages from nuget.org. NuGetForUnity resolves the package's declared dependencies during installation.
IMPORTANT: Use the same pinned
<version>for both the CLI and Unity plugin in released automation, and update them together.
๐ค Agent Skills
uCLI ships official agent skills with the CLI package. Install them when an agent host should know the uCLI read, plan, apply, verify, and troubleshooting workflows for a Unity repository.
Install the skills into a repository for the agent host you use:
ucli skills install --host openai --category basic --scope project
Supported host keys are openai, claude, and copilot. Project scope installs host-native skill files under the repository root:
| Host | Project target |
|---|---|
openai |
.agents/skills |
claude |
.claude/skills |
copilot |
.github/skills |
Run the command from the target repository. Use --repoRoot <path> only when the current working directory is outside the repository or when automation needs to select a repository explicitly.
The bundled official skills belong to the basic category. skills export, install, update, uninstall, and doctor require at least one package selector: --category or --skill. --skill selects exact skillName values, and combining it with --category requires the named skills to belong to the selected categories. If a selected skill declares dependencies, uCLI resolves the transitive dependency set before export, install, update, uninstall, or doctor execution; payload.skillNames keeps the requested root selection, while payload.skills and file operations include the resolved package set. skills list may omit both selectors to show all available categories, bundled skill counts, and each skill's dependencies. Options accept comma-separated values, such as --skill ucli-read-project,ucli-verify-changes.
Use skills list when you want to inspect the bundled skills, selected categories, available categories, supported hosts, target directories, and reload guidance:
ucli skills list
Export a single named skill when you do not want the whole category:
ucli skills export --host openai --skill ucli-read-project --output ./exported-skills
Use user scope only for local, non-repository defaults:
ucli skills install --host openai --category basic --scope user
Preview file changes before writing:
ucli skills install --host openai --category basic --scope project --dryRun --printDiff
Keep installed skills aligned with the current CLI version and diagnose drift:
ucli skills update --host openai --category basic --scope project
ucli skills doctor --host openai --category basic --scope project
After installing or updating, reload the agent host. The command result includes payload.reloadGuidance; for Codex, start a new session or restart the app so newly installed skills are loaded.
๐ Quick Start
Set the target Unity project once for your shell session:
export UCLI_PROJECT_PATH=./UnityProject
If your shell is already in the Unity project root, you can omit both UCLI_PROJECT_PATH and --projectPath for most commands.
Create repository defaults when you want project-local configuration:
ucli init
This step is optional; the commands below can run with UCLI_PROJECT_PATH or current-directory resolution.
Then confirm that uCLI can resolve the project:
ucli status
Wait until Unity can answer read-only work:
ucli ready --for execution
Inspect the installed operation catalog:
ucli ops list
ucli ops describe ucli.assets.find
Read project state:
ucli query assets find --pathPrefix Assets --limit 10
Run one planned Unity-backed request and capture the JSON result:
ucli call --withPlan > result.json <<'JSON'
{
"steps": [
{
"kind": "op",
"op": "ucli.assets.find",
"args": {
"pathPrefix": "Assets",
"limit": 10
}
}
]
}
JSON
Inspect result.json to confirm the request status, plan, and asset-search opResults.
For repeated local automation, start a daemon:
ucli daemon start
Use --projectPath <path> when a single command needs to override the environment value.
๐งญ Runtime Modes
uCLI can run commands that support multiple Unity execution paths through three modes.
Use --mode auto|daemon|oneshot only on commands that expose the option. Screenshot commands require an existing GUI Editor daemon and do not expose --mode.
| Mode | Use it for |
|---|---|
oneshot |
Start Unity in batchmode for isolated commands and CI jobs. |
daemon |
Require an existing Unity-backed daemon for repeated local automation. |
auto |
Reuse a running daemon when available; otherwise fall back to one-shot batchmode, so one-off local commands and CI jobs do not need a daemon. |
Requests mean the same thing in every mode. The mode only controls process reuse and startup behavior.
๐ค Machine-Readable Output
IMPORTANT: Automation commands listed below write one final JSON result envelope to standard output. Other human-readable command-line output, progress messages, diagnostics, and entry streams are written to standard error.
Automation should parse standard output as the final JSON result. Commands with entry streams, such as ucli logs unity read, ucli logs daemon read, and ucli test run, write entries to standard error before the final result.
Use --format json when automation needs newline-delimited JSON entries from standard error. Without --format json, standard error is diagnostic or human-readable output and is not a stable parse target.
When a command can write entries, drain standard error concurrently while waiting for the final standard output result.
The common JSON envelope contains protocolVersion, command, status, exitCode, message, payload, and errors.
Use status and errors[] for command-level success or failure.
For request commands, inspect payload.opResults to determine which steps applied, changed, or returned operation-specific result data.
For assurance commands, inspect payload.verdict, payload.verifiers[], payload.claims[], payload.reports, and payload.residualRisks[].
Use ucli codes describe IPC_TIMEOUT or another code value to read the static meaning of machine-readable codes.
Published JSON schemas are available for tools that validate uCLI output. They cover the common envelope and each command payload; operation-specific opResults[].result follows the resultContract.schema shown by ucli ops describe. Treat the established verdict and its supporting evidence references as returned result data, not as something JSON Schema alone can decide.
๐ Reading Project State
TIP: Read before you write. These commands emit machine-readable JSON.
Use ucli refresh when Unity project state may be stale. It may trigger refresh or import work; query commands remain the read-only inspection path.
ucli status
ucli ready --for execution
ucli refresh
ucli query assets find \
--type "UnityEngine.Material, UnityEngine.CoreModule" \
--limit 100
ucli query scene tree \
--path Assets/Scenes/Main.unity \
--depth 1
ucli query comp schema \
--type "Game.EnemySpawner, Assembly-CSharp"
ucli resolve \
--scene Assets/Scenes/Main.unity \
--hierarchyPath Root/Enemies/Spawner \
--componentType "Game.EnemySpawner, Assembly-CSharp"
๐๏ธ Read Index for Repeated Reads
uCLI includes a read index for read-heavy automation. It lets scripts and agents inspect operation details, asset search data, GUID/path mappings, and lightweight scene structure without reconnecting to Unity for every read.
Write commands still resolve targets against live Unity state. The read index speeds up planning, but call does not treat stored index data as final state.
For read-heavy workflows, --readIndexMode controls whether query-like commands may use stored index data. The --read-index-mode spelling is accepted as an alias.
| Mode | Behavior |
|---|---|
disabled |
Skip stored index data and read from Unity when the command needs project state. |
allowStale |
Use stored index data even when it is stale, and fall back when it is unavailable. |
requireFresh |
Use stored index data only when it is fresh; otherwise refresh from Unity when the command supports it. |
ucli ready --for readIndex --readIndexMode requireFresh checks the stored operation catalog, asset search, and GUID/path lookup data used by public read commands.
Validate a scene snapshot with ucli query scene tree --path <scene> --readIndexMode requireFresh.
โ Assurance Workflow for Writes
Use the assurance path for normal automated writes:
ucli status
ucli ready --for mutation
ucli ops describe ucli.scene.open
ucli query scene tree --path Assets/Scenes/Main.unity --depth 2
ucli call --withPlan < request.json > result.json
ucli verify --profile built-in:mutation --from result.json
After C# script changes, compile is also available as a standalone gate before a broader verification profile:
ucli compile
ucli verify --profile built-in:script --from result.json
The built-in:default verify profile includes compile, so ucli verify --from result.json may trigger AssetDatabase refresh, script compilation, and domain reload. Use --profile built-in:mutation when you only need evidence from Unity after the mutation, and use --profile built-in:script for C# script changes. Inspect payload.verifiers[].effects[] when automation needs to know which verification steps may refresh, compile, or reload Unity.
payload.profile records the selected profile source, name, path, and digest. The digest includes profile identity as well as effective steps.
If ucli ready --mode auto resolves to a transient oneshot probe, the ready claim is diagnostic evidence only for that probe session. It does not guarantee that a later mutation command will reuse the same Unity process.
When a command fails, read code meanings and bounded logs instead of scraping free-form messages:
ucli codes describe IPC_TIMEOUT
ucli logs daemon read --tail 200 --level error
ucli logs unity read --tail 200 --level error
๐ ๏ธ Request Input and Planned Writes
IMPORTANT:
ucli call,ucli plan, anducli validateread JSON only from redirected standard input. Keep the request in your script or job and pipe it to uCLI.
Use call --withPlan for compact local automation where the same script or job plans and applies immediately.
ucli call --withPlan <<'JSON'
{
"steps": [
{
"kind": "op",
"op": "ucli.scene.open",
"args": {
"path": "Assets/Scenes/Main.unity"
}
},
{
"kind": "edit",
"on": {
"kind": "scene",
"path": "Assets/Scenes/Main.unity"
},
"select": {
"kind": "gameObject",
"path": "Root/Enemies/Spawner",
"component": "Game.EnemySpawner, Assembly-CSharp",
"cardinality": "one"
},
"actions": [
{
"kind": "set",
"values": {
"spawnInterval": 3.0,
"maxCount": 10
}
}
],
"commit": "context"
}
]
}
JSON
This example opens Assets/Scenes/Main.unity, selects Root/Enemies/Spawner, edits the Game.EnemySpawner component, and saves the scene through commit: "context".
Use ucli plan and ucli call --planToken only when a review step or CI gate must inspect and approve a plan before mutation.
IMPORTANT: A timeout or disconnect does not prove that nothing was applied. Inspect the JSON result,
opResults, touched units, Unity logs, and daemon logs before retrying.
Use ucli eval when a local operator intentionally needs to run ad hoc C# inside the Unity Editor process without hand-writing a JSON request. It wraps ucli.cs.eval, returns the standard JSON envelope with payload.plan and payload.opResults, and still requires the dangerous-operation guards.
ucli eval --allowDangerous \
--source 'return UnityEngine.Application.unityVersion;'
ucli eval --mode daemon --allowDangerous \
--file ./eval.cs
โ ๏ธ Dangerous Operations
WARNING:
ucli callanducli evalblock operations whose policy isdangerousunless every guard allows them: project policy, operation allowlist, and the explicit--allowDangerousflag. Prefer the normaleditflow and non-dangerous operations.
๐๏ธ Building Player Artifacts
Use ucli build run to run Unity BuildPipeline from a build profile and collect machine-readable build results:
ucli build run --profilePath .ucli/build/player.json
The build profile defines inputs.buildTarget, scenes, options, and output policy. The CLI does not override the profile build target for one run. ucli build run writes the final JSON result to standard output, and may write progress entries to standard error before that final result.
Build artifacts are written under .ucli/local/build-runs/<runStorageKey>/artifacts/, while Unity writes its working output under the sibling work/output/ directory.
The run storage key is a lowercase Base32hex path segment derived from the full run ID; command output and JSON retain the UUID value. Project identity remains part of the command and IPC contracts rather than the build-run path.
| Artifact | Use it for |
|---|---|
build.json |
uCLI build run metadata, resolved inputs, generation validity, summary, and artifact references. |
build-report.json |
Normalized Unity BuildReport data. |
build.log |
Unity log entries for the build execution window. |
output-manifest.json |
File sizes and digests for generated player output files. |
output/ |
Generated player output files. |
๐งช Unity Test Runs
Run Unity tests after applying edits:
ucli test run \
--testPlatform editmode \
--assemblyName MyGame.Tests.EditMode
Use --unityEditorPath <path> when the job must use a specific Unity executable or .app directory, or when Unity is not installed in a standard searchable location.
For repeated test settings, generate a profile with ucli test profile init --outputPath test.profile.json and pass it to ucli test run with --profilePath test.profile.json.
When the normalized result set and required artifacts are complete, the command returns
status=ok with payload.state=completed, payload.verdict, payload.runId,
payload.artifactsDir, and payload.summaryJsonPath.
Execution, cancellation, and artifact failures remain status=error; if an artifact
session was already created, payload.run carries its runId and artifactsDir only
as recovery context and does not claim a terminal state or verdict.
Test artifacts are written under .ucli/local/projects/<projectStorageKey>/artifacts/test/<runStorageKey>/.
The storage keys are lowercase Base32hex path segments derived from the full project fingerprint and run ID; command output and JSON retain the original values.
| Artifact | Use it for |
|---|---|
summary.json |
The established pass, fail, or incomplete verdict, result counts, and top failures. |
results.json |
Normalized per-test results for automation. |
results.xml |
Raw Unity Test Framework output from -testResults. |
editor.log |
Unity Editor diagnostics for setup failures, compiler errors, and runtime exceptions. |
meta.json |
The resolved test run configuration and timestamps. |
TIP: When a command or test fails, read Unity and daemon logs before retrying:
ucli logs unity read --tail 200 --level error
ucli logs daemon read --tail 200
Stop the daemon at the end of an interactive automation session:
ucli daemon stop
๐งฉ Request DSL Core
This section covers the core request shape used by common automation. Operation-specific arguments and policies come from the operation catalog exposed by ucli ops list and ucli ops describe.
A request is one ordered unit of work:
{
"steps": []
}
| Field | Meaning |
|---|---|
steps |
Ordered steps. uCLI runs them in array order. |
protocolVersion is generated by the CLI and must not be included in user-authored request JSON. The request root accepts only steps; every other property is rejected.
Each step is either kind: "op" or kind: "edit".
โ๏ธ Direct Operation Step
Use op when the operation you need is already in the operation catalog.
{
"kind": "op",
"op": "ucli.scene.open",
"args": {
"path": "Assets/Scenes/Main.unity"
}
}
| Field | Meaning |
|---|---|
id |
Step identifier used in results and diagnostics. |
op |
Operation name, such as ucli.scene.open. |
args |
Operation-specific argument object. |
โ๏ธ Edit Step
Use edit for common Unity edits where you want to name a context, select targets, apply actions, and choose the save boundary.
{
"kind": "edit",
"on": {
"kind": "scene",
"path": "Assets/Scenes/Main.unity"
},
"select": {
"kind": "gameObject",
"path": "Root/Enemies/Spawner",
"component": "Game.EnemySpawner, Assembly-CSharp",
"cardinality": "one"
},
"actions": [
{
"kind": "set",
"values": {
"spawnInterval": 3.0,
"weights.Array.data[0]": 0.25
}
}
],
"commit": "context"
}
| Field | Meaning |
|---|---|
on |
The edit context and persistence boundary. |
select |
The object or objects to edit inside the context. |
actions |
One or more edits to apply to the selected target. |
commit |
Save behavior. Use none, context, or project. |
IMPORTANT:
sceneandprefabedits that mutate or usecommit: "context"need that context open. Putucli.scene.openorucli.prefab.openbefore the edit step when the request has not opened it already.
๐ Edit Contexts
| Context | JSON | Use it for |
|---|---|---|
| Scene | { "kind": "scene", "path": "Assets/Scenes/Main.unity" } |
GameObjects and components in a scene. |
| Prefab | { "kind": "prefab", "path": "Assets/Prefabs/Enemy.prefab" } |
GameObjects and components in a prefab stage. |
| Asset | { "kind": "asset", "path": "Assets/Data/GameBalance.asset" } |
A main asset such as a ScriptableObject. |
| Project | { "kind": "project" } |
Project-scoped assets such as ProjectSettings/TagManager.asset. |
๐ฏ Selectors
For scene and prefab contexts, select a GameObject by hierarchy path. Add component when the action should target a component on that GameObject.
{
"kind": "gameObject",
"path": "Root/Enemies/Spawner",
"component": "Game.EnemySpawner, Assembly-CSharp",
"cardinality": "one"
}
For an asset context, select the asset itself:
{
"kind": "self",
"cardinality": "one"
}
For project-scoped settings, select the project asset path:
{
"kind": "projectAsset",
"path": "ProjectSettings/TagManager.asset",
"cardinality": "one"
}
For a scene context, select a set produced by ucli.scene.query:
{
"kind": "from",
"op": "ucli.scene.query",
"args": {
"pathPrefix": "Root/Enemies",
"componentType": "Game.EnemySpawner, Assembly-CSharp"
},
"cardinality": "all"
}
cardinality is required:
| Value | Meaning |
|---|---|
one |
Exactly one target must match. |
first |
Use the first target from the selector's deterministic match order. |
all |
Apply the same action to every matched target. |
atMostOne |
Allow zero or one target. |
IMPORTANT:
allruns the same action list for every selected target in deterministic order. Actions that create a new global resource, such ascreateAssetandcreatePrefab, require the selection to resolve to at most one target.
โถ๏ธ Actions
NOTE: If
targetis omitted, the action uses the current selected target.createPrefabis the exception: it always requires an explicittargetandpath. An action that creates or ensures an object can expose it withas, and later actions in the same step can refer to it with"$name".
{
"kind": "edit",
"on": {
"kind": "scene",
"path": "Assets/Scenes/Main.unity"
},
"select": {
"kind": "gameObject",
"path": "Root/Enemies/Spawner",
"cardinality": "one"
},
"actions": [
{
"kind": "ensureComponent",
"type": "Game.EnemySpawner, Assembly-CSharp",
"as": "spawner"
},
{
"kind": "set",
"target": "$spawner",
"values": {
"spawnInterval": 3.0,
"maxCount": 10
}
}
],
"commit": "context"
}
| Action | Required fields | Use it for |
|---|---|---|
set |
values |
Set serialized properties on the selected object, component, or asset. |
ensureComponent |
type |
Add a component when it is missing and reuse it when it exists. |
createObject |
name |
Create a GameObject in the selected context. |
createAsset |
type, path |
Create a ScriptableObject main asset under Assets/. |
createPrefab |
target, path |
Create a prefab asset from a GameObject. |
delete |
none | Delete the current target or the explicit target. |
reparent |
parent |
Move a GameObject under another parent. |
set.values uses Unity serialized property paths:
{
"kind": "set",
"values": {
"spawnInterval": 3.0,
"weights.Array.data[0]": 0.25
}
}
๐พ Commit
| Value | Behavior |
|---|---|
none |
Apply the edit in memory and do not save from this step. |
context |
Save the current scene, prefab, asset, or project context. |
project |
Save project-scoped changes and request-attributed open scene or prefab contexts. |
IMPORTANT: uCLI does not implicitly save an edit step. Choose
commitintentionally.
๐งท Direct Operation Target Selectors
Direct operation steps that take a target use one of these selector shapes:
{ "kind": "globalObjectId", "globalObjectId": "GlobalObjectId_V1-..." }
{ "kind": "assetGuid", "assetGuid": "01234567-89ab-cdef-0123-456789abcdef" }
{ "kind": "assetPath", "assetPath": "Assets/Data/GameBalance.asset" }
{ "kind": "projectAssetPath", "projectAssetPath": "ProjectSettings/TagManager.asset" }
{ "kind": "sceneHierarchy", "scene": "Assets/Scenes/Main.unity", "hierarchyPath": "Root/Enemies/Spawner" }
{ "kind": "sceneComponent", "scene": "Assets/Scenes/Main.unity", "hierarchyPath": "Root/Enemies/Spawner", "componentType": "Game.EnemySpawner, Assembly-CSharp" }
{ "kind": "prefabHierarchy", "prefab": "Assets/Prefabs/Enemy.prefab", "hierarchyPath": "Root/Visual" }
Asset GUIDs use the standard hyphenated JSON representation emitted by System.Text.Json.
Do not put { "var": "..." } or "var": null in direct op args.
To name a value produced by an edit action, use the edit action as field and refer to that name through the edit DSL form, such as $createdObject.
ops describe omits the var selector branch, and direct op execution rejects it.
Direct set operations use sets, while edit steps use the shorter values form:
{
"kind": "op",
"op": "ucli.comp.set",
"args": {
"target": {
"kind": "sceneComponent",
"scene": "Assets/Scenes/Main.unity",
"hierarchyPath": "Root/Enemies/Spawner",
"componentType": "Game.EnemySpawner, Assembly-CSharp"
},
"sets": [
{
"path": "spawnInterval",
"value": 3.0
}
]
}
}
๐ Operation Catalog
The installed Unity plugin exposes the operations available to requests.
ucli ops list
ucli ops describe ucli.comp.set
ops list returns operations that can be used directly in kind:"op" request steps. Operations used only by higher-level edit flows are not listed for direct selection.
Use ops describe to check:
- operation kind and policy
- generated argument/result contracts, uCLI semantic annotations, and their shared contract digests
readIndexsource and freshness metadata
README examples show common operations only. The installed Unity plugin's operation catalog is the authoritative list for that project.
| Operation | Type | Args | Use it for |
|---|---|---|---|
ucli.scene.open |
command | { path } |
Ensure a scene is loaded. |
ucli.scene.save |
mutation | { path } |
Save a loaded scene. |
ucli.prefab.open |
command | { path } |
Open a prefab editing context. |
ucli.prefab.save |
mutation | { path } |
Save the opened prefab context. |
ucli.project.save |
mutation | {} |
Save project assets, project settings, and tracked open contexts. |
Common operation groups include:
ucli.scene.*- open, inspect, and save scenes.ucli.prefab.*- open, edit, save, and create prefabs.ucli.assets.*/ucli.asset.*- find assets, inspect asset data, and update asset values.ucli.go.*- create, describe, delete, and reparent GameObjects.ucli.comp.*- inspect, ensure, and set components.ucli.project.*- save project-scoped state.
๐งฐ Command Guide
| Command | Use it when you need to |
|---|---|
ucli init |
Create optional project-local uCLI configuration. |
ucli status |
Check Unity project resolution and daemon lifecycle state. |
ucli ready |
Wait until Unity is ready for reading or mutation. |
ucli refresh |
Refresh Unity project state. |
ucli compile |
Verify Unity script compilation and domain reload readiness. |
ucli query |
Read project data without writing changes. |
ucli resolve |
Resolve a selector to a Unity object identifier. |
ucli ops |
List and inspect available operations. |
ucli codes |
List and describe machine-readable code values used in JSON output. |
ucli call |
Apply a request; use --withPlan for the normal planned write path. |
ucli eval |
Run ad hoc C# through ucli.cs.eval without authoring a JSON request. |
ucli plan |
Prepare a separated review gate and receive a planToken. |
ucli validate |
Diagnose static request validation without running plan or call. |
ucli verify |
Return a JSON verification result for Unity-side checks. |
ucli build |
Run Unity BuildPipeline from a build profile and return build evidence. |
ucli screenshot |
Capture GameView or SceneView presentation surfaces from an existing GUI Editor daemon. |
ucli logs |
Read Unity or daemon logs. |
ucli daemon |
Manage daemon sessions. |
ucli test |
Run Unity Test Framework tests. |
ucli screenshot game and ucli screenshot scene capture their presentation surfaces in stable Edit Mode or Play Mode. Screenshot capture waits for waitable lifecycle work within the command timeout and does not commit an artifact if the Editor state changes during capture.
ucli logs unity clear clears only the visible Unity Editor Console. It does not remove entries retained by ucli logs unity read.
Common options:
| Option | Applies to | Meaning |
|---|---|---|
--projectPath <path> |
Unity-backed commands | Target Unity project path. Overrides UCLI_PROJECT_PATH and current-directory resolution. |
--mode auto\|daemon\|oneshot |
Commands that expose --mode |
Choose daemon reuse or one-shot batchmode. |
--timeout <milliseconds> |
Unity-backed commands | Override the command timeout. |
--readIndexMode disabled\|allowStale\|requireFresh, --read-index-mode disabled\|allowStale\|requireFresh |
Query-like commands | Control read-index use. |
--failFast |
Unity-backed commands | Fail when the Unity editor lifecycle is not ready instead of waiting. |
--withPlan |
ucli call |
Run a plan pass inside call and include it in the result. |
--planToken <token> |
ucli call |
Apply a request using a token returned by ucli plan. |
--allowDangerous |
ucli call, ucli eval |
Allow operations whose catalog policy is dangerous. |
--allowPlayMode |
ucli plan, ucli call, ucli eval |
Allow guarded Play Mode mutation in a GUI Editor session. |
Use --mode daemon when CI must fail specifically because no daemon is running. With --mode auto, a missing daemon may start a one-shot Unity process; if startup fails, inspect payload.startup, payload.diagnosis, and retryDisposition.
NOTE: Project path resolution uses
--projectPath, thenUCLI_PROJECT_PATH, then the command default. The default is usually the current working directory.
Lifecycle Lock Location
Lifecycle lock files are stored in the current user's OS local application data directory, not under repo-local .ucli state:
| OS | Local application data root | Lifecycle lock path |
|---|---|---|
| Windows | %LOCALAPPDATA% |
%LOCALAPPDATA%\MackySoft\ucli\lifecycle-locks\unity-projects\<sha256>\lifecycle.lock |
| macOS | $HOME/Library/Application Support |
$HOME/Library/Application Support/MackySoft/ucli/lifecycle-locks/unity-projects/<sha256>/lifecycle.lock |
| Linux | $XDG_DATA_HOME when absolute, otherwise $HOME/.local/share |
${XDG_DATA_HOME:-$HOME/.local/share}/MackySoft/ucli/lifecycle-locks/unity-projects/<sha256>/lifecycle.lock |
<sha256> is derived from the normalized physical UnityProjectRoot, so paths that resolve to the same physical Unity project share one launch lock.
Unity's project-local Temp/UnityLockfile is treated as a Unity-owned marker for editors opened outside uCLI. uCLI does not clean it on a timer or in the background; it attempts cleanup only before starting a new Unity process and after a uCLI-launched Unity process exits. uCLI deletes the marker only when those checks can prove it is stale. Active ownership returns UNITY_PROJECT_ALREADY_OPEN, unsafe ownership checks return UNITY_PROJECT_LOCK_AMBIGUOUS, and stale-lock deletion failures return UNITY_PROJECT_LOCK_CLEANUP_FAILED.
๐งฑ Authoring Project-Specific Operations
Extensions can expose operations under names such as myorg.navmesh.bake.
Custom operations are not hidden shortcuts. Once they are in the catalog, they follow the same policy, JSON output, and discovery rules as built-in operations, so scripts, agents, and CI can find them with ucli ops list and inspect them with ucli ops describe.
Skip this section if you only run the built-in Unity operations. Use it when your Unity project needs to expose project-specific operations through uCLI.
Custom operations are Unity Editor code. Put the implementation in an Editor assembly that references MackySoft.Ucli.Unity. If another tool needs to compile against the same Args/Result types, put those types in a shared assembly. The published operation details are available through ucli ops describe.
An operation has three parts:
- Define a typed Args type and, when needed, a typed Result type.
- Add descriptions and operation-specific constraints to Args properties; keep reusable semantic value types limited to intrinsic format constraints.
- Implement
UcliOperation<TArgs,TResult>and mark the class with[UcliOperation].
Use UcliNoResult for operations that do not emit opResults[].result.
The Args and Result types define what callers send and receive. UcliOperationMetadata.CreateWithoutVerdict<TArgs,TResult> publishes a non-judging operation; CreateJudgingQuery<TArgs,TResult> additionally binds a declared condition and its typed result evaluator. Both APIs publish operation inputs, result data, JSON shapes, public kind, and policy from the actual types and their attributes. Do not hand-write JSON Schema for a normal operation.
Request/result rules:
| Rule | Why it matters |
|---|---|
Put [Description] on every Args/Result type and every public property. |
The same provider result supplies descriptions to JSON Schema and operation type metadata. |
Express required members with the effective System.Text.Json contract, normally [JsonRequired] or C# required. |
Contract generation reads JsonPropertyInfo.IsRequired; there is no second requiredness annotation. |
| Express optional and nullable values in the CLR type and converter contract. | Contract generation reads the same nullability used by runtime serialization. |
Use [JsonConstructor] when the type has a non-default constructor. |
uCLI deserializes steps[].args with System.Text.Json before validation. |
Use [JsonPropertyName] when the JSON member name must differ from the C# property name. |
ops describe and JSON validation use the JSON name. |
Use [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] for optional result or selector properties that should be omitted when absent. |
Omitted and explicit null have different meanings in the published JSON shape. |
Use JsonElement, JsonNode, or object only for an intentional arbitrary JSON slot. |
The actual runtime type, not a schema-only annotation, declares that the value is unconstrained. |
Use a [VocabularyDefinition] type with VocabularyJsonConverterFactory for finite strings. |
Runtime serialization and generated enum or const values share one typed vocabulary. |
Use JsonTypeInfo.PolymorphismOptions, [JsonPolymorphic], or [JsonDerivedType] for tagged unions. |
Runtime deserialization and generated oneOf branches share one discriminator contract. |
Use existing semantic value types before adding new plain strings:
SceneAssetPathPrefabAssetPathUnityAssetPathProjectSettingsAssetPathUnityAssetPathPrefixUnityHierarchyPathUnityGlobalObjectIdUnityTypeIdUnityComponentTypeIdSerializedPropertyPath
User-defined semantic value objects are supported for string-shaped values that remain JSON strings in requests and results.
Create one only when the same meaning appears in multiple Args/Result types or when the meaning is important enough to name for callers.
For one-off meaning, keep a normal property and put [Description] and, when applicable, one concrete uCLI semantic annotation on that property.
If you need a new string-shaped semantic value:
- Derive from
UcliStringValue. - Define a public
stringconstructor. - Add
[JsonConverter(typeof(UcliStringValueJsonConverterFactory))]. - Put
[Description]on every Args/Result property that uses the value type. The property description is authoritative for that use. - Put intrinsic constraints such as path syntax on the value type.
- Put project-state constraints such as
AssetExistsorAssetCreatableon the Args property whose operation requires them.
Do not use arbitrary custom scalar wrappers unless uCLI has a supported base type for that JSON shape.
using System.Text.Json.Serialization;
using MackySoft.JsonSchema.Generation.Annotations;
using MackySoft.Ucli.Contracts.Ipc;
using MackySoft.Ucli.Contracts.Json;
[JsonConverter(typeof(UcliStringValueJsonConverterFactory))]
[Length(1, int.MaxValue)]
public sealed class AddressableKey : UcliStringValue
{
[JsonConstructor]
public AddressableKey (string value)
: base(value)
{
}
}
[Description("Arguments for setting an Addressables label.")]
public sealed record SetAddressableLabelArgs
{
[JsonConstructor]
public SetAddressableLabelArgs (
AddressableKey key,
string label)
{
Key = key;
Label = label;
}
[JsonRequired]
[Description("Addressable key to update.")]
public AddressableKey Key { get; init; }
[JsonRequired]
[Description("Label to assign.")]
[Length(1, int.MaxValue)]
public string Label { get; init; }
}
AddressableKey remains a JSON string in steps[].args.
Because Key has its own [Description], the generated contract uses the property description for that input.
Every public Args/Result property must declare its own description; do not rely on a mapped UcliStringValue subtype to provide a fallback description.
Length is structural metadata in the same provider Contract Model that emits Schema and operation type metadata.
uCLI semantic annotations describe Unity-specific meaning that is not present in the JSON shape. Each attribute represents one valid meaning and uses a typed vocabulary parameter when it needs a finite value.
| Annotation | Typed parameter | Use it for |
|---|---|---|
[UcliProjectRelativePath] |
none | Paths relative to the Unity project. |
[UcliAssetExists] |
UcliOperationAssetKind |
Existing asset, scene, prefab, or project settings paths. |
[UcliAssetCreatable] |
UcliOperationAssetKind |
Asset or prefab paths that an operation may create. |
[UcliGlobalObjectId] |
none | Unity GlobalObjectId strings. |
[UcliAssetGuid] |
none | Unity Asset GUID values. |
[UcliHierarchyPath] |
none | Unity scene or prefab hierarchy paths. |
[UcliReferenceResolvable] |
UcliOperationReferenceTargetKind |
References that must resolve to an asset, GameObject, or component. |
[UcliTypeExists] |
none | Unity type identifiers that must resolve in the project. |
[UcliTypeAssignableTo] |
UcliOperationTypeKind |
Unity type identifiers assignable to a supported Unity kind. |
[UcliSerializedProperty] |
UcliOperationSerializedPropertyAccess |
SerializedProperty paths that require a supported access capability. |
[UcliCursor] |
none | Opaque bounded-window cursors returned by read operations. |
Use [Length], [ItemCount], [PropertyCount], [UcliInt32Minimum], or [UcliInt32Range] for structural bounds supported by the current contract types. Do not repeat those facts as uCLI semantic annotations.
For object references and selectors, prefer existing reference types such as AssetReferenceArgs, GameObjectReferenceArgs, SceneGameObjectReferenceArgs, ComponentReferenceArgs, and ResolveSelectorArgs.
If an operation needs a new tagged union, make the actual System.Text.Json contract polymorphic. Do not maintain a separate branch list or serialized-property-name annotation for Schema generation.
Declare operation behavior deliberately:
| Behavior field | Values | Use it for |
|---|---|---|
kind |
Query, Command, Mutation |
The operation's public intent. |
UcliOperationAssuranceContract |
side effects, dirty/persist flags, touched kinds, plan mode | Behavior facts that help callers decide whether an operation is acceptable. |
UcliOperationPlanMode |
ValidationOnly, ObservesLiveUnity, MayCreatePreviewState |
How much the Plan phase may do before Call; preview state is limited to edit-lowering operations. |
UcliOperationCodeContract |
source forms, entry point, source-visible API, return constraints | Required for operations that accept source code. Arbitrary source execution is dangerous. |
UcliOperationExposure |
Public, EditLoweringOnly, Internal |
Whether callers can select the operation directly or only through higher-level edit flows. |
Do not choose policy manually.
uCLI publishes it from the operation's declared intent, side effects, persistence behavior, touched targets, source-code execution, exposure, destructive scope, and external process or filesystem access.
Safe operations are bounded observations that cannot dirty, persist, or change Editor state, and do not execute arbitrary code or external processes. Advanced operations include deterministic Unity Editor API writes, Editor state changes, dirty or persisted Unity content, AssetDatabase refresh/import/compile effects, and broader project effects. Dangerous operations are escape hatches. Examples include arbitrary C# execution, arbitrary shell/process/filesystem writes, unbounded destructive operations, or operations whose touched/save boundary cannot be sufficiently guaranteed.
Keep phase behavior consistent:
Validatechecks typed args and cheap preconditions.Planmay inspect Unity state according toplanMode, but must not persist content.Callperforms the operation.- Query operations must report
applied:false,changed:false, andtouched:[]. applied,changed, andtouchedbelong to the operation result envelope. Do not put those fields inTResult.TResultshould contain only the operation-specific main data. UseUcliNoResultwhen there is no main data.
using System;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MackySoft.JsonSchema.Generation.Annotations;
using MackySoft.Ucli.Contracts;
using MackySoft.Ucli.Contracts.Configuration;
using MackySoft.Ucli.Contracts.Ipc;
using MackySoft.Ucli.Contracts.Operations;
using MackySoft.Ucli.Unity.Execution.Phases;
using MackySoft.Ucli.Unity.Execution.Requests;
[Description("Arguments for counting GameObjects in a scene.")]
public sealed record CountSceneObjectsArgs
{
[JsonConstructor]
public CountSceneObjectsArgs (SceneAssetPath path)
{
Path = path;
}
[JsonRequired]
[Description("Scene asset path to inspect.")]
[UcliAssetExists(UcliOperationAssetKind.Scene)]
public SceneAssetPath Path { get; init; }
}
[Description("GameObject count result.")]
public sealed record CountSceneObjectsResult
{
[JsonConstructor]
public CountSceneObjectsResult (int count)
{
Count = count;
}
[JsonRequired]
[Description("Number of GameObjects found in the scene.")]
public int Count { get; init; }
}
[UcliOperation]
internal sealed class CountSceneObjectsOperation : UcliOperation<CountSceneObjectsArgs, CountSceneObjectsResult>
{
public override UcliOperationMetadata Metadata { get; } =
UcliOperationMetadata.CreateWithoutVerdict<CountSceneObjectsArgs, CountSceneObjectsResult>(
operationName: "game.scene.countGameObjects",
kind: UcliOperationKind.Query,
description: "Counts GameObjects in a Unity scene.",
assurance: new UcliOperationAssuranceContract(
sideEffects: new[] { UcliOperationSideEffect.ObservesUnityState },
touchedKinds: Array.Empty<UcliTouchedResourceKind>(),
planMode: UcliOperationPlanMode.ObservesLiveUnity,
planSemantics: "Validate the scene selector and count the matching GameObjects without applying mutation.",
callSemantics: "Count matching GameObjects from live Unity state without applying mutation.",
touchedContract: "Returns no touched resources because the count is observation data.",
readPostconditionContract: "Does not stale read surfaces by itself.",
failureSemantics: "Timeout, cancellation, or read failure means the count was not fully produced.",
dangerousNotes: Array.Empty<string>()),
requiresPreCallPlanReplay: false,
exposure: UcliOperationExposure.Public,
playModeSupport: UcliOperationPlayModeSupport.Disallowed,
codeContract: null);
protected override Task<OperationPhaseStepResult> ValidateAsync (
NormalizedOperation operation,
CountSceneObjectsArgs args,
OperationExecutionContext executionContext,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.FromResult(OperationPhaseStepResult.Success(
applied: false,
changed: false,
touched: Array.Empty<OperationTouch>()));
}
protected override Task<OperationPhaseStepResult> PlanAsync (
NormalizedOperation operation,
CountSceneObjectsArgs args,
OperationExecutionContext executionContext,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.FromResult(SuccessWithResult(
new CountSceneObjectsResult(0),
applied: false,
changed: false,
touched: Array.Empty<OperationTouch>()));
}
protected override Task<OperationPhaseStepResult> CallAsync (
NormalizedOperation operation,
CountSceneObjectsArgs args,
OperationExecutionContext executionContext,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.FromResult(SuccessWithResult(
new CountSceneObjectsResult(0),
applied: false,
changed: false,
touched: Array.Empty<OperationTouch>()));
}
}
The example leaves the Unity scene traversal out of the snippet so the request/result shape is visible.
In a real operation, keep Unity object resolution and mutation inside Validate, Plan, or Call, and keep JsonElement out of the operation body.
Use existing semantic value types such as SceneAssetPath, PrefabAssetPath, UnityHierarchyPath, UnityGlobalObjectId, and UnityTypeId before introducing a new value type. Use Guid directly for Unity asset GUIDs.
After Unity recompiles the Editor assembly, confirm that the operation is discoverable from the CLI:
ucli ops describe game.scene.countGameObjects --projectPath ./UnityProject
๐ฆ Packages
uCLI consumes MackySoft.FileSystem 0.1.0 as an independently released, exact-version dependency. The uCLI repository does not own generation, versioning, or publication of the standalone provider package. The CLI tool publication may include the provider assembly in its runtime dependency closure; the Unity plugin does not bundle that assembly and instead declares the exact package dependency.
๐ฌ Support
Use GitHub Issues for bugs, feature requests, usage questions, and README problems.
For bug reports, include:
ucli --version- Unity version
- Operating system
- The command you ran
๐ Sponsor
If uCLI helps your Unity automation workflow, please support MackySoft through GitHub Sponsors:
https://github.com/sponsors/mackysoft
๐ค Author
Hiroya Aramaki is an indie game developer in Japan.
- Website: https://mackysoft.net/
- GitHub: https://github.com/mackysoft
๐ License
uCLI is under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.9.0 | 130 | 8/3/2026 |
| 0.8.3 | 118 | 7/26/2026 |
| 0.8.2 | 124 | 7/23/2026 |
| 0.8.1 | 131 | 7/17/2026 |
| 0.8.0 | 111 | 7/16/2026 |
| 0.7.2 | 125 | 7/9/2026 |
| 0.7.1 | 116 | 7/9/2026 |
| 0.7.0 | 128 | 7/8/2026 |
| 0.6.0 | 135 | 6/23/2026 |
| 0.5.0 | 118 | 6/23/2026 |
| 0.4.0 | 132 | 6/23/2026 |
| 0.3.0 | 131 | 6/22/2026 |
| 0.2.1 | 133 | 6/14/2026 |
| 0.2.0 | 126 | 6/14/2026 |
| 0.1.0 | 140 | 6/4/2026 |