About unity-cli
A skill for using the `cs` CLI to operate on Unity projects through documented command routes, snippets, and C# execution. It emphasizes scoped command discovery, file-based inputs, and careful handling of project changes.
Key features
- 01Routes tasks through built-in and custom commands
- 02Discovers commands by domain and tier
- 03Passes C# and JSON through files
- 04Supports isolated, reusable REPL sessions
- 05Requires approval before setup writes
Use cases
- 01Find a Unity command for a requested operation
- 02Run C# inspection or project actions
- 03Batch structured Unity commands
- 04Manage session state across related executions
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ niqibiao/ unity-cli-skill --skill unity-cliAfter installing, type/unity-cliin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/catalog.mdcommands.mdexec-code.mdlogs.mdplayer.mdrefresh.mdsetup.mdsnippets-audit.mdsnippets.mdstatus.mdtests.mdscripts/cli/__init__.py__main__.pycatalog_store.pycommand_discovery.pycommand_preflight.pycore_bridge.pycs.pypaths.pyregistry_cache.pyregistry_protocol.pyregistry_resolver.pyreliability.pyrouting_overlay.jsonVERSIONscripts/cli/snippets/__init__.pyrender.pystats.pystore.pyvalidate.pyDetails
From the project READMEUnity CLI
One CLI (cs) drives the Unity Editor/Player. The default authoring surface is
grouped into six agent-facing domains so only the relevant schema enters context;
control-plane and project custom commands use explicit views.
cs list-commands is a control-plane CLI discovery operation, not the canonical
package contract command/list. Its CLI flags are never command/list arguments,
and discovery invocations must not be represented as canonical command routes.
Only use command/list when the user explicitly requests that package contract.
The records returned by discovery carry authoring, custom, or control-plane tiers.
Running cs
cs below means:
python "<SKILL_DIR>/scripts/cli/cs.py"
<SKILL_DIR> is this skill's absolute base directory, supplied when the skill
loads. Expand cs to that absolute command on every call and run it without
changing directory.
Do not enumerate other skill installations or run cs --help to rediscover this
documented surface; this file and its returned Route Cards are complete for
routing.
Do not pass --project during normal use. The CLI locates the Unity project by
walking up from both the working directory and its own committed location.
--project <path> is only for deliberately targeting a different project.
The CLI runs in place; there is no bootstrap or copy step. cs setup is a
convenience, not a gate. If the package is absent or --update is used, setup
writes the shared Packages/manifest.json. Before any write-producing setup,
state the exact source it will add and obtain the user's approval. See
references/setup.md.
Passing inputs
Never pass C# code or structured command parameters inline through the shell. Write a file, then pass its absolute path:
- Raw C# for
execgoes in a.csfile via--file. - Structured parameters for
commandandbatchgo in a JSON file via--input.
The mandatory scratch directory is:
<project-root>/Temp/CSharpConsole/AgentScratch/
This is inside Unity's managed Temp/, is not imported, and is normally ignored
by git. Always use the full absolute path. Name files for the semantic task and
overwrite the same file when revising it serially; use a random suffix only when
another agent is known to work on the same Unity project concurrently.
Scratch payloads are one-shot. Put reusable C# in the snippet library. Clean only
AgentScratch/; never delete Temp/CSharpConsole/, which contains service state.
Never write REPL payloads under Assets/: importing them can break project
compilation and prevent the service from restarting.
cs exec --file "<project-root>/Temp/CSharpConsole/AgentScratch/inspect-camera.cs"
cs command --json --input "<project-root>/Temp/CSharpConsole/AgentScratch/req-create-cube.json"
cs batch --json --input "<project-root>/Temp/CSharpConsole/AgentScratch/req-batch.json"
REPL sessions
cs exec starts a fresh session by default. Keep that default for self-contained
code. When later submissions intentionally depend on variables, usings, types,
or helpers created earlier, generate one opaque task-specific id and reuse it:
cs exec --session agent-a1b2c3 --file "<project-root>/Temp/CSharpConsole/AgentScratch/inspect-camera.cs"
cs exec --session agent-a1b2c3 --file "<project-root>/Temp/CSharpConsole/AgentScratch/use-camera.cs"
Never share a session between unrelated tasks or agents. A domain reload clears
session state, so after cs refresh rebuild the context or use a new id. See
references/exec-code.md for lifecycle and reset details.
Routing
Use this canonical order for every task: built-in command → project custom command → snippet → raw exec. Do not skip a matching built-in in favor of C#. The package registry is the executable contract authority; the committed custom catalog is only a shared shortlist.
For a structured Unity operation, finish discovery before loading execution details:
The routine built-in authoring discovery budget is one Route Card call per
non-empty tier, then one exact-ID call total. Only a genuinely new requirement
after closure permits one narrow built-in lookup, with its reason recorded.
Keep a discovery ledger. Scope that ledger to the current agent session and
resolved project target. Its key includes selector kind (domain or id),
normalized view, tier, sorted selectors, and resolution mode (live, offline,
or explicit refresh), plus projection/detail mode (compact or verbose).
Mark a signature in flight before launch and completed after success; a completed
or in-flight signature must be reused or awaited. Never use an offline entry to
satisfy the required live comparison. A stored live or refresh entry may satisfy
the otherwise identical offline request only for the same project, ledger
generation, and detail mode after cacheStored:true; no other cross-mode or
cross-detail reuse is valid. The ledger is in-memory agent bookkeeping; do not
query wall-clock time or create a ledger file.
Refresh is an exclusive barrier: wait for every in-flight lookup for that project,
then launch refresh alone. Only a refresh result with source:"live" and
cacheStored:true creates a new ledger generation; record that refresh as its
first entry and invalidate the older completed entries. A stale-cache or fallback
refresh never advances the generation or satisfies a later offline lookup; discard
any late result from an older ledger generation instead of recording or using it.
Do not retry a live or refresh lookup after timeout or an unknown terminal state;
stop and report its state as uncertain. Only an offline lookup with a confirmed
non-zero exit and no result may be retried once, under the same ledger entry and
with its reason recorded. A successful lookup is never resent.
- Close domains before calling. Build the complete Core and Advanced domain
sets from every requested mutation, prerequisite, explicit invariant,
independent proof, and fallback check. The table below is the routine Domain
Index; skip an unfiltered index call. The Advanced-only list is conditional,
not a checklist: direct
prefab/asset_*editing,prefab/unpack, asset-folder creation, and Scene View capture are Advanced.prefab/createandprefab/instantiatestay inprefabs/core; generic prefab enumeration stays inassets/core. The same domain may belong to both tiers. A folder plus material workflow putsassetsin both sets: folder creation is Advanced, while material creation, inspection, and assignment are Core. Never let an Advanced intent replace another requested Core operation in that domain. When prefab-domain intents exist, omitprefabs/coreonly if all of them are directprefab/asset_*operations orprefab/unpack. Every domain/tier selection must map to an affirmative requested operation; exclusions, examples, and parallelism never widen the query set. An explicitly named read or proof selects its tier, while a generic read-back for a mutation does not because the exact bundle supplies its direct relation. A possible later drill-down never widens the initial domain sets; wait until an upstream read makes it necessary. A domain whose only possible use is such a deferred drill-down is empty and must be omitted. For example, a hierarchy-wide scene scan does not selectobjectsmerely because one returned object might later need inspection; the scan/report intent is the hierarchy read, not an implicit per-object or per-component read. For a scan of every prefab-asset hierarchy, enumeration isassets/coreand hierarchy inspection isprefabs/advanced; omitprefabs/core. - Run the Route Card round. Use exactly one multi-domain query for each
non-empty tier. The compact result retains
cacheStoredbecause it guards generation continuity. The first live call performs the session's one fingerprint comparison. When both tiers are non-empty in a live workflow, Core is the first live call; Advanced may be first only when Core is empty. Finish that live call before any later offline tier call. If it reportssource:"live"withcacheStored:false, stop and report the persistence failure instead of mixing registry generations. A generated fallback may continue planning-only. In an explicit offline/no-project dry-run, add--offlineimmediately and, when both tiers are non-empty, launch the Core and Advanced queries together in one tool round. Do not probestatus, the filesystem, or the working directory to reconfirm supplied context. If Core alone reveals that a requested operation needs Advanced, make the one Advanced call for all such unresolved operations. A stale cache or generated fallback is planning-only; execution requires a registry verified live during that invocation. - Run the exact round once. Wait for every Route Card call to finish before
building the one exact-ID command. Take the union of IDs selected from every
tier and branch, closing it over every mutation, producer, consumer, loop
branch, invariant, and proof; copy IDs only from returned cards.
Issue one multi-ID exact query in exactly one shell invocation. The domain-query
parallelism rule never applies to exact discovery: do not split the union into
parallel or later calls, and do not add a later exact lookup for any original
task requirement. Do not exact-load a conditional drill-down whose necessity
depends on a future read. Omit deferred drill-downs from both the exact selector
set and the invocation plan; record only their trigger in prose until it fires.
Invocation-plan routes are executable in the current phase, so never add a
deferred route with an "if" or "when" purpose. In a broad scan, a per-item
getter whose contract does not name the required diagnostic signal cannot close
that gap; record the capability gap instead of selecting the getter.
Runtime selector binding alone does not make a known consumer conditional;
include its contract in the original closure. After a read justifies genuinely
new drill-down IDs, combine them into at most one narrow follow-up lookup.
Treat
selected,related, anddeniedas one closed result:selectedandrelatedare complete executable contracts, whiledeniedentries are non-executable policy records, not contracts. Remove every returned ID from the unresolved set and never query one merely to promote it toselected.relatedis the complete direct, deduplicated prepare/read-back layer, not a recursive expansion. A denied requested intent is terminal: stop as blocked and do not enter fallback. Contract evidence is closed-world for task-specific signals: a generic result container or summary does not promise an unnamed marker or field. If the executable bundle lacks a required capability or proof, or a limitation blocks one, record a capability gap instead of probing for a stronger built-in. Selected and related contract entries are capabilities, not mandatory invocations; use one only for a requested mutation, prerequisite, or independent proof. - Plan concrete invocations. Assign each postcondition to the smallest
independent proof and remove fully redundant reads. Deduplicate contracts, not
invocations. An explicit verifier limitation is authoritative; another generic
read closes it only when that read's own contract names the required
postcondition, otherwise record a proof gap. Do not collapse materially
different argument values or user-named selectors into prose: retain one
template for each distinct argument branch and one invocation for each explicit
read-back target; a uniform generated series may remain one counted loop. A
counted uniform series uses its first generated item as the concrete template.
When a producer can create an object under its requested known parent, create
it at that parent directly; do not add a later reparent mutation. When argument
behavior branches by a predicate such as parity, use the first member of every
branch as its template; do not substitute arbitrary later samples. One contract
supports all of them without more discovery. Do not add speculative existence,
conflict, or overwrite prechecks unless the task or selected contract requires
them. A default may satisfy an exact invariant without making the invariant
optional:
a newly created Cube already has exactly one
BoxCollider; use it instead of adding another, but retain “exactly one” as an explicit requirement and proof obligation. Never use a current or default value to drop an explicitly requested assignment: for example, still plancomponent/modifywhen the user asks to set that existing collider'sm_IsTrigger, even when its default already matches. - Separate planning from execution. A dry-run still plans every supported requested mutation; state separately that nothing was executed. Static batches may use caller-defined deterministic paths. When Unity chooses an identity, execute the producer separately and bind consumers to its returned selector instead of predicting a name. Discover a repair only when it may execute; missing rules keep diagnosis read-only and exclude repair commands from domain selection, exact lookup, and the invocation plan. A transport failure after mutation leaves state uncertain: read back first and never blindly repeat a non-idempotent create, duplicate, destroy, import, or equivalent mutation.
cs list-commands [--offline] --domain scene --domain objects --tier core --json
cs list-commands --offline --id gameobject/create --id gameobject/get --json
In a returned contract, arguments: [] means there are no named parameters; do
not invent an argument named args. Only an actual command request uses the
outer "args": {} wrapper.
Do not mix --domain with --id; use --tier only with domains. Preserve
--view control or --view custom when exact-loading an ID from that view;
authoring IDs use the default view.
For planning or discovery only, do not load references/commands.md; these
routing rules plus the returned Route Cards and Contract Bundle are complete.
Before invoking command or batch, read references/commands.md completely
once for the request, preflight, and retry protocol. Then write exactly
{"id":"<canonical-id>","args":{...}} to the mandatory scratch JSON file and run
cs command --json --input <file>; args is required even when empty.
Discover contracts only through list-commands; never read or search
scripts/cli, generated registry data, routing metadata, or Python
implementation files to recover command details. Those files are
non-agent-facing implementation data and may be much larger than the selected
contract bundle.
Enter fallback only after the complete built-in plan has a concrete gap. Run
cs catalog list, load a suggested exact custom contract, then try
cs snippets search "<intent>", and finally raw exec. Do not return to
built-in discovery for a requirement knowable before fallback; a genuinely new
requirement permits one narrow exact lookup with the reason recorded. If no Unity
project is available, mark custom and snippet stages unavailable without probing
them. An unavailable fallback stage is not selected; do not load its reference.
Merely describing the fallback order does not select a stage. Load its reference
only immediately before performing that stage's documented operation. The
selected custom-contract lookup is the fallback-stage exception to the built-in
one-exact budget. A dry-run that stops before authoring raw C# records the exec
decision without reading references/exec-code.md.
Use cs list-commands --refresh … only when the user explicitly asks to update
the command list. Do not load or print the unfiltered 62-command package registry
during routine work.
| Domain | Positive intent | Exclude / route elsewhere |
|---|---|---|
editor | Editor state, play mode, test runs, console maintenance | Scene contents, object selection, asset files |
scene | Scene listing/open/save and hierarchy-wide reads, including component summaries | Properties of an identified GameObject/component, prefab contents |
objects | Operations on an explicitly identified scene GameObject, component, transform, or selection | Hierarchy-wide scans, unknown future targets, project assets, prefab-file editing |
assets | Asset search/import/CRUD, materials, and ScriptableObjects | Scene hierarchy, prefab contents |
prefabs | Create/instantiate/unpack or directly inspect/edit prefab contents | Generic prefab enumeration (assets/core) and unrelated asset paths |
capture | Scene/Game screenshots and Profiler recording | Structured state inspection |
The five control-plane contracts are outside the default authoring view. Inspect
them only when the user's requested capability is itself control-plane, using
--view control --domain control --tier control-plane. Merely using
cs list-commands for discovery does not justify querying the control view or
loading or planning the command/list contract.
editor/menu.open and editor/window.open are deny-policy intents, not
executable contracts. An explicit list-commands --id lookup reports them under
denied with invoke=false; classify that result as blocked and stop. Never
route those intents to snippets or raw exec as an automatic fallback.
Debugging a running player
When the question is about what a player actually does, open the runtime channel
before investigating anything else. cs health --mode runtime names the process
that answers. Follow the command-first routing order for state inspection; use
cs exec --mode runtime --file <file> for state the available commands cannot read.
Use the running process as the primary source for runtime state. If execution is unavailable or the question concerns which code a build contains, inspect logs, source and build artifacts as supporting evidence. Match build GUIDs where available, distinguish current source from shipped code, and label inferences. A substring hit or file timestamp alone does not establish which code executed.
A runtime submission is refused with [REPL ALIGNMENT REQUIRED] when the editor
holds no compile set for the build the player reports. Ask the user for that
build's compile-set ZIP at once, naming the build. By default it is exported as
CSharpConsoleCompileSet.zip beside the build output on the build machine;
Project Settings > C# Console can change its path or disable export. Ask whoever
built it for the matching ZIP. If a candidate is already available, checking its
build GUID is useful evidence; do not register it or choose skip without the
user's decision. File inspection does not bypass the runtime execution gate.
Read a player log by collecting it first: cs logs --mode runtime --background
returns the path of the file it writes, which can then be searched locally.
A foreground follow streams the whole log, shader chatter included.
Other subcommands
| Task | Subcommand | Detail |
|---|---|---|
| Raw C# fallback | cs exec --file | references/exec-code.md |
| Reusable C# | cs snippets … | references/snippets.md |
| Snippet audit | cs snippets doctor / stats | references/snippets-audit.md |
| Refresh and compile | cs refresh | references/refresh.md |
| Readiness diagnosis / wait | cs doctor / cs wait-ready | references/refresh.md |
| Unity Test Framework runs | cs test | references/tests.md |
| Editor log inspection | editor/console.mark + local read | references/logs.md |
| A running player, and its files | --mode runtime / cs pull | references/player.md |
| Compiling for a player's build | cs compile-set | references/player.md |
| Watching a log as it is written | cs logs | references/player.md |
| Custom-command catalog | cs catalog sync / list | references/catalog.md |
| Package / connection state | cs status / cs health | references/status.md |
| Package setup | cs setup | references/setup.md |
Output conventions
- Use
--jsononcommand,list-commands, andbatch. In the default compact form, checkokand the CLI process exit status. Add--verboseonly for diagnostics; its full envelope also exposesexitCode, which must agree with the process status. - Other subcommands print a cheaper text form. Add
--jsontoexeconly when its structured envelope is specifically needed. - A version mismatch means the installed package and CLI are on different
major.minorlines, and every command that drives Unity is refused (exit 1) until they agree.cs setupandcs statusare exempt so the mismatch can be fixed and seen; commands that never reach Unity —cs doctor,cs wait-ready, offline listings — are unaffected. Followreferences/setup.md. - Expanded CLI commands and JSON payloads are agent-internal. When Unity must be
opened or focused, tell the user what action is needed in plain language, then
run
cs statusyourself to verify the result.