diff --git a/skills/new-unity-project/SECURITY.md b/skills/new-unity-project/SECURITY.md index 26f3a3a..f4a022d 100644 --- a/skills/new-unity-project/SECURITY.md +++ b/skills/new-unity-project/SECURITY.md @@ -12,7 +12,7 @@ of these as powerful capabilities. They are intentional, and limited by the safe | `SEC_POWER_CAP` | Runs C# in the user's Editor | It only reaches the Editor the user already has open, on their own machine, as that user; the commands sit behind the Pipeline capability gate. No code fetched from a remote source is run — the agent runs C# it writes from this skill's and its delegated skills' own recipes. | | `SEC_FILE_WRITE` | Writes one bootstrap script (Step 6) and appends one line to CLAUDE.md/AGENTS.md (Step 9) | Both writes land inside the project the skill just created and both are surfaced before they run: in paved mode as notice with an opt-out on the assumptions manifest ("say the word to change any of these"), in guided mode as per-step asks that are true consent. Both are reversible on a word, and the scope section caps the skill at exactly one bootstrap script. | | `SEC_INSTALL` | Installs Editors, modules, packages, and (with the user's yes) agent skills | Installs go through the Unity CLI's own commands and the PackageManager API — never `curl \| sh`, never hand-edited manifests. Skill installs always ask first (Before Step 1). Versions and the package list are declared before installing. | -| `SEC_CLOUD` | Creates a cloud-linked Unity project with diagnostics per the CLI's defaults | The link is `projects create`'s own default behavior, narrated to the user from the create result. Sign-in is never automated — it is a human gate. `--bare` and the link opt-outs are honored on the user's word. | +| `SEC_CLOUD` | Creates a cloud-linked Unity project with diagnostics per the CLI's defaults | The link is `projects create`'s own default behavior, narrated to the user from the create result. Sign-in is a human gate: it runs only after the user's yes, and the user completes it in the browser. `--bare` and the link opt-outs are honored on the user's word. | ## Mitigations @@ -20,7 +20,7 @@ of these as powerful capabilities. They are intentional, and limited by the safe writes and the cloud-link narration included — prints in one block before the create launches, and any line reverses on a word. Consent in the strict sense comes from guided mode's per-step asks; paved mode's bar is that nothing runs unannounced and nothing is irreversible. -- **Human gates are never consumed.** Sign-in and publishing always ask the user. +- **Human gates are never consumed.** Sign-in, license activation (including accepting the Unity Personal EULA) and publishing each run only after the user says yes to a question the agent asks and waits on. - **Only the user's own Editor and account.** Everything runs on this machine as this user. - **No remote code.** All executed C# comes from this skill's text and its delegated skills. - **Named commands first.** Where a dedicated `unity` command covers a step, the skill uses it. diff --git a/skills/new-unity-project/SKILL.md b/skills/new-unity-project/SKILL.md index dfa4d9c..0635e20 100644 --- a/skills/new-unity-project/SKILL.md +++ b/skills/new-unity-project/SKILL.md @@ -1,6 +1,6 @@ --- name: new-unity-project -description: Guides creating a new Unity project, gathering concept, platforms, and monetization before setting up the project, source control, and packages. Use when the user asks to start a new game or prototype. +description: Guides creating a new Unity project, gathering concept, platforms, and monetization before setting up the project, source control, and packages. Use when the user asks to start a new game or prototype. Runs an opinionated paved path by default; full interview on request. allowed-tools: - Bash - Write @@ -28,12 +28,41 @@ Run **paved** unless the user asks for the interview — in words ("ask me quest or by pushing back on the manifest; mode is decided once per session. The paved contract: **parse the prompt first** · **fill gaps from the Defaults table** · **print the manifest** before the create launches · **proceed**, asking only on pushback or an unruled fork · -**never consume a human gate** — sign-in and publishing always ask. A **bare/minimal** ask gets -`--bare`, skips the floors, and states the cost (identity, saves, ranks unwired). +**never consume a human gate** — sign-in, license activation and publishing each need the user's +yes, asked as a question you wait on (see Sign-in and license). A **bare/minimal** ask gets +`--bare`, skips the floors, and states the cost (identity, saves, ranks unwired). Bare runs Steps +1–4, installs only the packages the user named in Step 5, skips Steps 6 and 7, then commits (Step 8) +and hands off monetization (Step 9) without the forward baton. In **guided** mode, ask only the current step's questions and wait. The paved additions (identity, floors, baton) are asked at their steps there, never assumed. +## Sign-in and license (before your first question) + +Run `unity auth status --format json` and `unity license status --format json` first. If either +shows the user signed out or without an active license, ask **one** question that covers both, +with your client's multiple-choice question tool (plain text only without one), and wait for the +answer. Send it together with the delegated-skills offer (below) and any Step 1 question, so the +user answers once. Offer the free +**Unity Personal** license as the default choice, with the other license types (a subscription the +account already has, a serial, a floating server) as alternatives. The Personal choice itself says +that picking it accepts the Unity Personal EULA +(https://unity.com/legal/editor-terms-of-service/software), so the user sees it before answering. + +- Never tell the user to sign in or activate a license themselves, and never hand them commands to + run. You run them, after they say yes. +- On yes, sign in only where the choice needs an account: Personal and an existing subscription + activate the signed-in user's licenses, and a cloud-linked project needs one too. Run + `unity auth login` then (it opens the browser; the user only completes the sign-in page, so tell + them that page is coming), in the background or with its output unfiltered, and pass on the + sign-in link it prints in case the browser doesn't open: a pipe through `tail` holds the link + back until sign-in ends. Then `unity license activate --personal --accept-eula` for Personal. + Serial and floating activation need no account: `unity license activate --floating`, or the + serial through `UNITY_LICENSE_SERIAL` and a bare `unity license activate`, so it stays off argv. +- Declining stops the run before the create only when nothing can license the Editor. A user who + declines sign-in but has, or activates, a serial or floating license still gets a local project: + create with `--no-cloud` and say the cloud link and player accounts are off. + ## The Defaults table Every row reverses on a word; change a row only with a run-earned receipt. @@ -60,7 +89,7 @@ create will do (the URP template per `--template 3d|2d`, never a guessed id): > anonymous player accounts (create's, else one bootstrap script) · · > cloud-linked with diagnostics (create's defaults, narrated from the result) · floor packages > (Cinemachine; identity + live services when cloud-linked) · craft floors on via `craft-game-floors` · -> a one-line handoff note appended to the project's CLAUDE.md. +> a one-line handoff note for the project's CLAUDE.md, written only on your yes. > Say the word to change any of these; otherwise I'm proceeding. One block, every assumption present, each reversible, nothing implied; Step 3 reconciles the @@ -70,7 +99,8 @@ result against it. Look for `unity-cli`, `unity-package-management`, `urp-postprocessing`, `ui`, `build-live-game` and `craft-game-floors` in your client's skills. If any is missing, say which and what it does, and offer the install routes -in [references/skill-setup.md](references/skill-setup.md) — never install without a yes. Don't +in [references/skill-setup.md](references/skill-setup.md), in the same question as sign-in and +license when that one goes out — never install without a yes. Don't stall: a decline gets the fallback at its step, gap named in the report. `unity skill show` prints `unity-cli` without installing; without `unity-package-management`, Step 5 can't run — say so before Step 2. @@ -115,9 +145,7 @@ Confirm prerequisites, then **start the create as a background task** (`unity-cl names and auth / license setup): ```bash -unity --version -unity auth status --format json # if signed out: unity auth login -unity license status --format json # if none active: unity license activate +unity --version # sign-in and license: settled before your first question (see above) # Start in the BACKGROUND. Module names per platform are in the unity-cli skill. # --bare only on the user's ask (see Mode). @@ -163,10 +191,15 @@ commit is Step 8. Exact flags: the `unity-cli` workflow. Map the brief to a package list and install via **`unity-package-management`** (**never** hand-edit `manifest.json`): `package add` once the Editor is open with `com.unity.pipeline` (Step 7); its C# installer script when packages must land pre-open (the `-quit` gotcha applies). -That skill has the genre/platform/monetization → package mapping. Paved +That skill has the genre/platform/monetization → package mapping. A `--bare` run installs only the +packages the user named, through the C# installer script (bare has no `com.unity.pipeline`, so +`package add` can't run; never `unity pipeline install` to get it, which undoes the bare ask), and +stops there. Otherwise paved adds the floors' needs: Cinemachine for any gameplay camera (2D rigs included); `com.unity.services.authentication` when Step 6 wires identity; and the live floor's services set -(cloud save, leaderboards, remote config) on cloud-linked projects — in this pass, not at shell time. The template already provides the +on cloud-linked projects — in this pass, not at shell time. Cloud save and remote config come from +create: when its `steps` report `cloudSave` and `remoteConfig` as `done` or `present`, skip them and +add only what is left (leaderboards). The template already provides the render pipeline — never add `com.unity.render-pipelines.universal` on a Built-in template (materials go pink) or vice versa. Read the list back before installing (paved: print it now); verify `manifest.json` after. @@ -194,7 +227,19 @@ and declined → fallbacks, gap named in the report. ## Step 7 — Visual baseline A fresh template renders correctly but looks like a default; left there, agents reach for `OnGUI` -and guess shader names. Apply this floor **before** any gameplay work. +and guess shader names. Apply this floor **before** any gameplay work. A `--bare` run skips this +whole step (no Pipeline install, no baseline): go to Step 8, which opens the project. + +**On Unity 6000.5.0 to 6000.5.2, stop here.** The Pipeline package can't start its server on those +Editors (create reports `PIPELINE_EDITOR_UNSUPPORTED`), so nothing below can connect, and reopening +won't help. Create also left the package out on those Editors. Ask the user to update to +6000.5.3f1 or later, then continue below with the project still closed: the Pipeline install comes +first, then the open names the updated Editor, `unity open "" --editor-version +` — a plain `unity open` reopens with the project's saved 6000.5.x Editor. If the +update is 6000.7 or later, `unity open` refuses that move (`PROJECT_UPGRADE_REQUIRED`): run +`unity projects upgrade "" --to --yes` instead. It asks +nothing: add `--no-cloud` when the user declined the cloud link at create, since an unlinked +project otherwise exits `CLOUD_CHOICE_REQUIRED`, and no cloud flag when create linked it. **First, `unity pipeline install --project-path ""`, before opening the Editor.** Running C# in the Editor and `unity command capture screenshot` below both need the @@ -285,11 +330,13 @@ Based on Step 2 monetization, invoke the matching skill for the actual integrati If the matching skill isn't loaded, point the next steps at the skill-setup install route rather than integrating from memory. -**Write the forward baton** (declared on the manifest): one idempotent line appended to the -project's `CLAUDE.md` or `AGENTS.md` — "When building gameplay, feel, audio, shell, or live +**Offer the forward baton** (declared on the manifest; not on a `--bare` run): one idempotent line +for the project's `CLAUDE.md` or `AGENTS.md` — "When building gameplay, feel, audio, shell, or live features, load the `craft-game-floors` skill; if it isn't installed, ask the user whether to run -`unity skill install ` (ask: never run it without a yes)." Commit the note — -appended after Step 8, it otherwise never reaches clones. Later sessions reach the floors through +`unity skill install ` (ask: never run it without a yes)." That file steers every later +agent session in the project, so **ask before writing it**, even in paved mode, and show the line. +On a yes, append it and commit the note — appended after Step 8, it otherwise never reaches clones. +On a no, put the line in the report for the user to add. Later sessions reach the floors through it without this skill re-triggering. Report the project path, Editor version, pipeline and template, packages, the manifest with its @@ -318,9 +365,9 @@ as the build's finish line, and next steps. - [ ] Identity confirmed from the result or wired in Step 6 (stated cost on a failed link) - [ ] `craft-game-floors` loaded (or its absence named); `unity-pipeline` read once the Editor was ready - [ ] Baseline applied: Volume + camera post on, quality tier, UI stack (no `OnGUI`), URP shaders, - capture checked + capture checked (skipped on `--bare`; on 6000.5.0–6000.5.2, Editor updated first) - [ ] `.meta` files exist; first commit made; `Library/` excluded -- [ ] Forward baton appended; handoff done if applicable +- [ ] Forward baton offered and written only on a yes (none on `--bare`); handoff done if applicable ## Common mistakes diff --git a/skills/new-unity-project/references/skill-setup.md b/skills/new-unity-project/references/skill-setup.md index be9cd99..ae8a564 100644 --- a/skills/new-unity-project/references/skill-setup.md +++ b/skills/new-unity-project/references/skill-setup.md @@ -58,7 +58,9 @@ session. | `PIPELINE_MANIFEST_WRITE_FAILED` on `unity pipeline install` | the project is open in an Editor | ask the user to close the Editor, re-run the install | | `STATUS_PIPELINE_LOAD_PENDING` right after an install against an open project | the open Editor loads the package when its window regains focus | ask the user to switch to the Editor window | | `STATUS_PIPELINE_LOAD_PENDING` that never clears after an open | the Pipeline server can run while its port file never lands (package/editor pairing issues) — focus is not the cause | read `Logs/Editor.log` for `Pipeline:` lines to confirm, then recover below | +| Pipeline never connects on Unity 6000.5.0 to 6000.5.2 (create reported `PIPELINE_EDITOR_UNSUPPORTED`) | the package can't start its server on those Editors | don't recover below, reopening won't help: ask the user to update to 6000.5.3f1 or later, run `unity pipeline install` with the project closed (create left the package out), then open it with `unity open "" --editor-version ` (a plain `unity open` uses the saved 6000.5.x Editor); on 6000.7 or later `unity open` refuses the move, so run `unity projects upgrade "" --to --yes`, adding `--no-cloud` when the user declined the cloud link at create | -**Stuck-Editor recovery:** ask the user first — unsaved work is lost — then +**Stuck-Editor recovery** (check the project's Editor version first; never on 6000.5.0 to +6000.5.2, see the table): ask the user first — unsaved work is lost — then `unity close --force`, which terminates by PID and works without Pipeline connectivity, and reopen the project. diff --git a/skills/unity-cli/CHANGELOG.md b/skills/unity-cli/CHANGELOG.md index 03b0452..7872fa1 100644 --- a/skills/unity-cli/CHANGELOG.md +++ b/skills/unity-cli/CHANGELOG.md @@ -12,6 +12,24 @@ release's own dated section once the CLI actually ships it. ## [Unreleased] +## CLI `1.0.0-beta.16` (2026-11-03) + +Aligned to the CLI's `1.0.0-beta.16` release. Everything below was documented ahead of the release and ships in it. + +### Added + +- **`skill install --all`**: `integration-advanced.md` documents installing the Unity skills into every agent client on the machine, which clients it maps each plugin agent to, that it runs no agent CLI and needs no git, its per-client lines, exit codes and json records, and that the install scripts run it. +- **Administrator prompt needs a terminal**: `editors-install.md` says `uninstall`, `editors prune --remove` and `editors upgrade --replace` open the administrator password dialog for a folder this user can't delete only on a terminal, after one line saying why, and otherwise stop before removing anything with exit 2 and `UNINSTALL_ELEVATION_NEEDS_TERMINAL`. +- **`projects upgrade` on an open project**: `projects-templates.md` says the upgrade exits `6` with "already open in a running Editor" and launches nothing when an Editor already holds the project, and to ask the user to close it first. +- **Setting up a Build Automation build target**: `SKILL.md` and `cloud-automation.md` document `pipeline cloud-build targets create`, `update` and `auditlog` (one option per field, or `--file` with a JSON target, `-` for stdin), `connections` and `connections create`, `scm check-url`, `credentials upload` and `org free-tier`, the order to run them in, and that passwords and tokens come from stdin or an environment variable, never an argument. +- **Streamed `unity command` output**: `integration-advanced.md` says a newer Editor formats and streams a named command's output, in the format `--plain` or `--json` picks, narrowed by `--fields`, that piped stdin and Ctrl-C reach the command, and that the CLI exits with the command's code. +- **`unity auth broker ensure`**: `auth-license-cloud.md` documents starting the sign-in service for a plugin, its five states, exit codes and error codes. +- **Remote Config kill switch and Cloud Save slot in `projects create`**: `projects-templates.md` says a create that links the project to Unity Cloud sets `liveops_enabled` to `true` on the server (add-only, never in a `.rc` file, and only by creating the `settings` config: an existing one is never written and the `notAdded` flag asks for the key to be added in the dashboard), adds `com.unity.remote-config` and `Assets/LiveOps/`, adds `com.unity.services.cloudsave` and `Assets/PlayerSave/` with one save slot, and that `--bare`, `--no-cloud` and a failed link leave both out. The `steps` report gains `remoteConfig` and `cloudSave`. + +### Fixed + +- **Commit ids in `projects clone --ref`**: `projects-templates.md` says a git `--ref` is a branch, a tag, or a commit id, full or abbreviated, that is checked out after the clone. + ## CLI `1.0.0-beta.15` (2026-10-29) Aligned to the CLI's `1.0.0-beta.15` release. Everything below was documented ahead of the release and ships in it. @@ -37,6 +55,10 @@ Aligned to the CLI's `1.0.0-beta.15` release. Everything below was documented ah ### Changed +- **Local package docs in `docs --package`**: `diagnostics-maintenance.md` and `docs-workflow.md` say `unity docs --package` opens the project's own `Documentation~` page when it has one, offline, that `--online` skips it and `--url` still prints the online manual, and that JSON reports `resolvedBy: local`, `source: local` and `path`. Pending the change shipping in a CLI release. +- **Busy and dialog-blocked Editors**: `SKILL.md` lists exit `17` (`UNAVAILABLE`) for an Editor that is loading, compiling, reloading or in Safe Mode, and `18` (`AWAITING_INPUT`) for one a modal dialog blocks, with what to do for each. Pending the change shipping in a CLI release. +- **`plugin install` and `plugin upgrade` install a plugin's skill**: `integration-advanced.md` says they write the skill a plugin ships into every AI client that already has the Unity skill, and leave other clients alone. Pending the change shipping in a CLI release. +- **`job wait` timeout parameter**: `playmode-verification-loop.md` uses `--timeout` (seconds, default 30, clamped 0 to 600) for `unity command job wait`, the name `com.unity.pipeline` 0.9.1-exp.1 uses in place of `--timeout_s`, and notes that it needs the `--` fence like `script eval`'s `timeout`. Pending the change shipping in a CLI release. - **`plugin install` and `plugin upgrade` install a plugin's skill**: `integration-advanced.md` says they write the skill a plugin ships into every AI client that already has the Unity skill, and leave other clients alone. - **`job wait` timeout parameter**: `playmode-verification-loop.md` uses `--timeout` (seconds, default 30, clamped 0 to 600) for `unity command job wait`, the name `com.unity.pipeline` 0.9.1-exp.1 uses in place of `--timeout_s`, and notes that it needs the `--` fence like `script eval`'s `timeout`. - **Parse stdout alone**: `SKILL.md` says never to merge stderr into stdout (`2>&1`) when parsing `--format json` or `ndjson`, since stderr carries human diagnostics and the Claude Code install hint line, which trail the JSON and break a parser. It says to capture the streams separately or discard stderr. diff --git a/skills/unity-cli/SECURITY.md b/skills/unity-cli/SECURITY.md index 9c2a134..6854f76 100644 --- a/skills/unity-cli/SECURITY.md +++ b/skills/unity-cli/SECURITY.md @@ -20,6 +20,10 @@ These capabilities are accepted by design. Each is documented in full in the sec The repository skills are downloaded, so the download is fenced too. The source repository and its download address are compiled into the CLI: no flag, setting or environment variable redirects them, and the download uses the CLI's normal TLS checks. The CLI pins a commit and a SHA-256 for each skill's file tree, and checks every skill against its pin before writing anything, so a changed or substituted archive writes nothing. Archive entries that would escape the skill folder, links, oversized files or archives, and non-text files are refused. Offline, only the skills embedded in the CLI are installed. +### Syncing skills when an agent session starts + +`unity skill sync` is the one skill write that runs without the user typing it: Unity's agent plugin runs `unity skill sync --hook` when a session starts, which scanners flag as an agent-persistence pattern. The user consents by installing the plugin, whose description says it keeps the Unity skills current, and it is fenced more tightly than `skill install`. It writes only what a non-interactive `unity skill install` would: the embedded skills and the skills pinned and checked as above, into the one client the agent is. It never prompts, and has no `--yes` or `--force`, so it never overwrites a copy the user edited, a directory this CLI did not write, or a skill a plugin ships, and it leaves a disabled skill disabled. It runs no other command and installs no plugin or MCP server. After a run that reached the pinned repository it downloads nothing until the CLI or its pin changes. Under `--hook` its output is one plain line of context naming the skills it changed, or nothing. + ### Turning an installed skill off and back on `unity skill disable ` and `unity skill enable ` change whether a skill an AI client has already installed still loads. Disabling renames that skill's `SKILL.md` to `SKILL.md.disabled` inside its own directory and records the choice; enabling renames it back. Automated scanners flag this as skill suppression — a skill that can switch other skills off is a recognized malicious pattern, and reading it that way is correct as a default. Here it is the advertised purpose of a user-run command, and it is fenced: @@ -31,9 +35,9 @@ The repository skills are downloaded, so the download is fenced too. The source The capability exists because a skill costs context in every session of the client that discovered it, whether or not it is ever used, so a user who installed several needs a way to stop paying for the ones a given project does not need — without deleting files they would otherwise have to reinstall. -### Installing Unity’s agent plugin (Claude Code, Codex, Grok Build, Antigravity, GitHub Copilot, Qwen Code) +### Installing Unity’s agent plugin (Claude Code, Codex, Grok Build, Antigravity, GitHub Copilot, Qwen Code, VS Code, OpenCode) -`unity setup claude` installs a plugin into Claude Code, which automated scanners flag as an agent-persistence pattern. It is fenced as follows. It runs only when the user runs the command, and this skill tells an agent to run it only when the user asks for the plugin or agrees to install it, never on its own initiative. The only source it can name is Anthropic’s official Claude Code marketplace, `anthropics/claude-plugins-official`, which lists Unity’s plugin and is compiled into the CLI: no argument, file, or environment variable can point it anywhere else. Claude Code accepts that marketplace name only from a repository under `github.com/anthropics/` ([Claude Code plugin security](https://code.claude.com/docs/en/plugins/security#identify-anthropics-marketplaces-by-name)), so a look-alike marketplace can’t take its place. It writes no files itself; it runs Claude Code’s own `claude plugin marketplace add` and `claude plugin install`, the documented install path, with fixed arguments, so Claude Code applies its own install handling. `--dry-run` prints both commands without running them, and an existing install is reported and never reinstalled or changed. It does not also copy a skill or register an MCP server of its own. +`unity setup claude` installs a plugin into Claude Code, which automated scanners flag as an agent-persistence pattern. It is fenced as follows. It runs only when the user runs the command, and this skill tells an agent to run it only when the user asks for the plugin or agrees to install it, never on its own initiative. The only source it can name is Anthropic’s official Claude Code marketplace, `anthropics/claude-plugins-official`, which lists Unity’s plugin and is compiled into the CLI: no argument, file, or environment variable can point it anywhere else. Claude Code accepts that marketplace name only from a repository under `github.com/anthropics/` ([Claude Code plugin security](https://code.claude.com/docs/en/plugins/security#identify-anthropics-marketplaces-by-name)), so a look-alike marketplace can’t take its place. It writes no files itself; it runs Claude Code’s own `claude plugin marketplace add` and `claude plugin install`, the documented install path, with fixed arguments, so Claude Code applies its own install handling. `--dry-run` prints both commands without running them, and an existing install is reported and never reinstalled or changed. It registers no MCP server of its own. After the plugin is installed or found already installed, it installs the Unity skills into the agent’s user-global skills folder, the same quiet run as `unity skill install ` that downloads from Unity’s pinned public skills repository with the same verification and the same refusal to overwrite an edited skill. A skill an older plugin still provides is skipped, and a failed skills step is a warning, not a failure. Every `unity setup ` command below does the same for its own client. `unity setup codex` and `unity setup grok` install the same plugin into Codex and Grok Build under the same fence: they run only when the user runs them, and this skill tells an agent to run them only when the user asks for the plugin or agrees to install it. Their source is Unity’s own first-party marketplace, `Unity-Technologies/unity-agent-plugin`, compiled into the CLI, and nothing can point either command elsewhere. Each writes no files itself and runs only the agent's own documented install commands with fixed arguments: `codex plugin marketplace add` and `codex plugin add`, or `grok plugin install … --trust`. `--trust` is Grok Build's consent flag for a non-interactive install, and it is passed only because the user ran the command. `--dry-run` prints the commands without running them, and an existing, enabled install is reported and never reinstalled or changed. A Codex plugin the user disabled (`enabled = false` in Codex’s `config.toml`) is the one exception: `unity setup codex` treats it as not installed and runs the same two `codex` commands again, which re-enables it. That only happens when the user runs the command. @@ -43,6 +47,8 @@ The capability exists because a skill costs context in every session of the clie `unity setup qwen` installs the same plugin into Qwen Code under the same fence. It runs only when the user runs it, names only the compiled-in first-party repository, writes no files itself, and runs only Qwen Code's own documented commands with fixed arguments: `qwen extensions install Unity-Technologies/unity-agent-plugin:unity --consent`, then `qwen extensions enable unity`. `--consent` is Qwen Code's flag for a non-interactive install, and it is passed only because the user ran the command. `--dry-run` prints the commands without running them, and an install Qwen Code has enabled for the current directory is reported and never reinstalled or changed. A disabled one counts as not installed, so the same two commands run again: the install fails because the extension exists, the command treats only that message, and only when the existing extension is Unity's own, as done, and the enable step turns it back on for the user's home directory. If it is still disabled for the current directory (a folder outside home), one more fixed command, `qwen extensions enable unity --scope workspace`, turns it on for that folder alone. Any other failure stops it. +`unity setup vscode` and `unity setup opencode` have no plugin CLI to run. Each installs the Unity skills for its client, as above, and runs no other command. `setup vscode` only prints how to add Unity’s plugin from the compiled-in first-party repository in VS Code and edits no VS Code file. `setup opencode` writes exactly one file, `~/.config/opencode/plugins/unity.js`, whose body is compiled into the CLI and whose path nothing can change. It runs only when the user runs the command. The file is written through a temporary file and a rename, its first line records a SHA-256 of the body the CLI wrote, and a later run replaces only a copy that still matches that digest. A file the user edited, or wrote themselves, is left alone with a warning unless the user passes `--yes`. `--dry-run` writes nothing. When OpenCode starts a session, the plugin runs one fixed command, `unity skill sync opencode --hook --format json`, in the background, ignores any failure, and shows only what that command returns as a toast and a context message. It takes no input from the session, and the skills it updates come from the same pinned repository and verification as `unity skill install`. + ### Local Editor control and C# evaluation `unity command`, `unity command script eval`, and `unity shell --protocol ndjson` can drive a Unity Editor that is already open on the same machine and run C# through the project's `com.unity.pipeline` package. This executes **entirely on the local machine, in the current user's account, against the user's own Editor** — it is not remote access and grants no privilege the user does not already have at their own terminal. It is the CLI's core value for AI-assisted and automated Editor workflows. diff --git a/skills/unity-cli/SKILL.md b/skills/unity-cli/SKILL.md index f508bfe..ec29f68 100644 --- a/skills/unity-cli/SKILL.md +++ b/skills/unity-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: unity-cli -description: Controls the Unity Editor and projects from the command line, driving a running Editor to edit scenes and assets or run C#. Use when asked to install or manage Editors, licenses, or projects, set up version control, build, test, configure the Unity MCP server, or run any unity command. +description: Controls the Unity Editor and projects from the command line, driving a running Editor to edit scenes and assets or run C#. Use when asked to install or manage Editors, licenses, or projects, set up version control, build, test, or run any unity command. allowed-tools: - Bash --- @@ -12,6 +12,8 @@ specific version.** Newer releases regularly add commands and output fields these docs describe, so an outdated CLI fails in ways that read like the docs being wrong. Check with `unity self-update --check --format json`. +**When you can run shell commands, use `unity command` and the rest of this CLI, not the Unity MCP server.** The CLI reaches more than the server's tools. MCP is for clients that have no shell. + ## Find a command: search, don't grep help **Never filter `unity --help` or a group's help with `grep`.** Help is long, and a grep over it drops the match or the context around it. Use the CLI's own search: @@ -113,7 +115,7 @@ These work on every command: | `--no-header` | Leave the header row out of `tsv` tables. The header row is the column names, which are the same keys the `json` rows use (`version`, `upgradeTo`, ...). An empty `tsv` result is the header row alone, so with `--no-header` it is no output at all. | | `--no-pager` | Turn off paging. Governs both pagers: the external one over the long listings (`unity command`, `releases`, `editors`, `changelog`, `logs`) and the interactive one in `unity projects`. Also via `UNITY_NO_PAGER` (presence-based — any value, including `0`, disables it). | | `--non-interactive` | Disable all interactive prompts — use in CI | -| `--quiet` | Hide notices, warnings and progress in human and tsv output. Errors and the exit code stay. json output never changes, and ndjson output does not either, except that a few listings still drop their rows (see [Notes](#notes)). | +| `--quiet` | Hide notices, warnings and progress in human and tsv output. Errors, the exit code and the `tsv` rows stay, so a script that pipes with `--quiet` still gets its data. json output never changes, and ndjson output does not either, except that a few listings still drop their rows (see [Notes](#notes)). | | `--verbose` | Print full error details (stack trace + cause chain) on failure. Also via `UNITY_VERBOSE`. | | `--proxy ` | HTTP/HTTPS/SOCKS/PAC proxy URL for this invocation. Also via `UNITY_PROXY`. Takes precedence over standard `HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY` env vars and the persisted `proxy.json` setting. | | `--proxy-disable` | Disable proxy for this invocation, ignoring all sources (env vars, persisted config, system settings). | @@ -162,7 +164,7 @@ All CLI env vars use the `UNITY_` prefix. A CLI flag always overrides the corres | `UNITY_PROXY` | `--proxy` | HTTP/HTTPS/SOCKS/PAC proxy URL. Takes precedence over `HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY` and the persisted `proxy.json` setting. | | `UNITY_NO_UPDATE_CHECK` | — | Disable the background "update available" check (see `unity config update-check`). | | `UNITY_CLI_ROLLBACK_KEEP` | — | How many previous CLI versions `unity self-update` keeps for `--rollback`, from 1 to 10 (default 3). | -| `UNITY_NO_AGENT_HINTS` | — | Stop suggesting Unity's agent plugin or skill when an AI agent runs `unity`: Claude Code's install prompt, the `unity setup codex` / `grok` / `antigravity` / `copilot` tip, and the `unity skill install` tip (an `AGENT_PLUGIN_AVAILABLE` entry in the envelope's `notifications` under `--format json`). Presence-based: any value counts. A tip asks you to get the user's approval before running its command. | +| `UNITY_NO_AGENT_HINTS` | — | Stop suggesting Unity's agent plugin or skill when an AI agent runs `unity`: Claude Code's install prompt, the `unity setup codex` / `grok` / `antigravity` / `copilot` tip, and the `unity skill install` tip (an `AGENT_PLUGIN_AVAILABLE` entry in the envelope's `notifications` under `--format json`), plus the once-a-day `unity skill sync` notice when installed Unity skills are older than the CLI's pin (`AGENT_SKILLS_OUTDATED`). Presence-based: any value counts. A tip asks you to get the user's approval before running its command. | | `UNITY_NO_CONSENT_PROMPT` | — | Suppress the one-time first-run analytics consent prompt *without* recording a choice — for wrapper scripts on an interactive terminal that must never absorb the prompt. Analytics stay off until you run `unity analytics opt-in`. Unlike `UNITY_NON_INTERACTIVE`, it changes nothing else about command behavior. | | `UNITY_NO_CRASH_REPORT` | — | Disable anonymous crash/error reporting (Sentry) entirely. | | `UNITY_NO_CLI_INVOKED_TELEMETRY` | — | Stop the anonymous once-per-run `cli telemetry` usage ping. Presence-based: any value counts. | @@ -192,7 +194,12 @@ whether running the same command again, unchanged, can succeed. A command that s again, so once its request may have been sent, never re-run it: follow what it started instead. `unity commands --json` carries this table as `exitCodes`, with the error codes that pick each exit. `6` covers every failure no more specific code names; a missing project, an Editor that -isn't running, an unknown Editor command and a timeout have their own codes (`11` to `14`). +isn't running, an unknown Editor command and a timeout have their own codes (`11` to `14`). A +request that gets no answer (offline, a failed DNS lookup, a refused connection, a dead proxy) +exits `7`; a timeout is `14`, and a reset connection or an unreadable body is +`6`. A TLS or certificate failure is `ERR_NETWORK` too, so retrying a `7` does not always help, +and a request already sent may have reached the server: retry only a command that is safe to +repeat. | Code | Name | Retry | Meaning | @@ -213,6 +220,8 @@ isn't running, an unknown Editor command and a timeout have their own codes (`11 | 14 | `TIMEOUT` | yes | An operation ran out of time: an Editor request, an HTTP request, a batch run. | | 15 | `RUN_FAILED` | no | A build or job the command waited on finished unsuccessfully. | | 16 | `JOB_SUSPENDED` | yes | A job the command watched waits on a person to resume or approve it. | +| 17 | `UNAVAILABLE` | yes | The target answered but can't do this now: it is busy. | +| 18 | `AWAITING_INPUT` | yes | The target answered but waits on a person first: a dialog to dismiss, a prompt to answer. | | 130 | `INTERRUPTED` | yes | Interrupted by Ctrl-C (SIGINT). | | 141 | `OUTPUT_CLOSED` | no | The reader of stdout closed the pipe. | | 143 | `TERMINATED` | yes | Terminated by SIGTERM. | @@ -221,7 +230,9 @@ isn't running, an unknown Editor command and a timeout have their own codes (`11 `8` is `unity test` only: the tests ran and some failed. A run that never completed exits `6` and a timed-out one `14`, so CI can retry those and never blindly re-run a failing test. `9`: earlier items of the same install may already be in place (`data.completedUids`). `10` is -`unity open --until-ready` only; the Editor is still running. +`unity open --until-ready` only; the Editor is still running. `17` is an Editor that is loading, +compiling, reloading or in Safe Mode: retry later. `18` is a modal dialog blocking it: list and +answer it with `unity command editor dialog`, then retry. ## Commands @@ -233,13 +244,13 @@ flags, environment variables, and exit codes above apply throughout. Every comma | Commands | Reference file | |---|---| | `auth` (login / logout / status / list / switch / default / consumers / revoke), `license` (activate / return / server), `cloud` (org / project) | [auth-license-cloud.md](references/auth-license-cloud.md) | -| `pipeline cloud-build` (targets incl. groups / builds incl. steps, failures, artifacts, log, download, start, cancel / project / tooling), `pipeline automation` (apps / pipelines incl. trigger / jobs incl. watch, logs, terminate, resume, approve, reject / automations / bots / profiles / templates, plus `apps versions`, `pipelines versions`, `jobs stats`) | [cloud-automation.md](references/cloud-automation.md) | +| `pipeline cloud-build` (targets incl. groups, create, update, auditlog / builds incl. steps, failures, artifacts, log, download, start, cancel / credentials upload / connections / scm check-url / org free-tier / project / tooling), `pipeline automation` (apps / pipelines incl. trigger / jobs incl. watch, logs, terminate, resume, approve, reject / automations / bots / profiles / templates, plus `apps versions`, `pipelines versions`, `jobs stats`) | [cloud-automation.md](references/cloud-automation.md) | | `editors` (list / running / add / default / path / install-path / info / upgrade / prune / verify / module), `install`, `uninstall`, `modules`, `install-modules`, `terms` (accept / status) | [editors-install.md](references/editors-install.md) | | `projects` (list / create / clone / open / link / require / upgrade / export / import / pin / size / clean / exec; its help reads "Create, open, upgrade, and link local Unity projects (Unity Cloud, GitHub/GitLab)", so linking a folder to Unity Cloud is `projects link cloud`, not `cloud`), `open`, `close`, `releases`, `templates` (list / info / create / pack / delete), `assets` (`inspect` / `export`) | [projects-templates.md](references/projects-templates.md) | | `config` (proxy / update-check / accelerator / get / set / list / unset / resolve), `context` (save / use / list / current / delete), `hub install` | [config-hub.md](references/config-hub.md) | | `run`, `test`, `build` (+ `build run`), `recompile`, `watch` (`test`) | [build-run-test.md](references/build-run-test.md) | | `logs`, `doctor`, `env`, `version`, `cache`, `ci init`, `analytics`, `changelog`, `docs` (+ `toc`), `language`, `completion`, `bug`, `self-update`, `self-uninstall`, `diagnose proxy`, `diagnose accelerator`, `diagnose update` | [diagnostics-maintenance.md](references/diagnostics-maintenance.md). To find, cite and version-match Unity docs: [docs-workflow.md](references/docs-workflow.md) | -| `mcp` (+ `configure`), `setup claude` / `codex` / `grok` / `antigravity` / `copilot` / `qwen`, `skill` (install / refresh / show), `plugin` (install / remove / upgrade / list / changelog), local `pipeline` (install / upgrade / list / list-versions), `command` / `commands` / `status` / `list`, `job` (status / wait / cancel), answering an open modal dialog (`command editor.dialog list` / `answer` / `dismiss`), `shell` | [integration-advanced.md](references/integration-advanced.md) | +| `mcp` (+ `configure`), `setup claude` / `codex` / `grok` / `antigravity` / `copilot` / `qwen` / `vscode` / `opencode`, `skill` (install / refresh / show), `plugin` (install / remove / upgrade / list / changelog), local `pipeline` (install / upgrade / list / list-versions), `command` / `commands` / `status` / `list`, `job` (status / wait / cancel), answering an open modal dialog (`command editor dialog` / `editor dialog answer`, or `editor.dialog list` / `answer` / `dismiss`), `shell` | [integration-advanced.md](references/integration-advanced.md) | | `vcs` — `setup` / `status` / `sync` / `switch` / `doctor` / `providers` / `merge-setup` / `conflicts` / `explain` / `resolve` / `diff` / `blame` / `summarize` / `affected` / `hooks`, `vcs git` (`migrate-lfs` / `worktree`), `vcs uvcs` (`locks` / `changesets` / `review`) | [version-control.md](references/version-control.md) | | `collaboration` (alias `collab`) — `annotations` / `attachments` / `thumbnail` / `reactions` / `read` / `subscribe` / `jira` | [collaboration.md](references/collaboration.md) | @@ -697,6 +708,7 @@ yourself.** Surface the notification and the command, and let the user decide. - `--timeout` takes a whole number of seconds written as digits (`--timeout 600`) on every command, and so do `UNITY_BUILD_TIMEOUT`, `UNITY_RUN_TIMEOUT` and `UNITY_TEST_TIMEOUT`. A unit, fraction or exponent (`30s`, `5m`, `1.5`, `1e3`) is a usage error (exit `2`), so convert minutes to seconds yourself. - `--format json` always produces machine-readable output; prefer it over parsing human text. Error envelopes are pretty-printed with the same 2-space indent as success envelopes. The envelope's `command` is the full command path (`projects list`, `cloud org list`), the same in json and ndjson and on success and failure; it is `""` when no command matched, such as an unknown one. - **`success` is `true` exactly when the exit code is `0`.** A status command whose answer is no (`unity auth status` signed out, `unity license status` with no license, `unity status` with no ready Editor) is a failure: `success: false`, an `errors[0].code` naming the answer, and its report still in `data`. A listing that finds nothing (`unity editors running` with no Editor) succeeds with an empty list. +- **A streamed named command has no CLI envelope.** When the project's `com.unity.pipeline` formats a named command's output itself (see `references/integration-advanced.md`), `unity command --format json` (or `--json`) prints the command's own result, one JSON value per line, with no `success`, `errors` or `data` wrapper, so its rows are not under `.data.items[]`. Branch on the exit code, which is the command's own, not one from the exit-code table above: `0` means it succeeded and any other code that it did not, such as `1` for a failed command, `102` when the Editor was busy and refused it (retry), or `106` when it was cancelled. A cancel that follows your Ctrl-C or a SIGTERM to the CLI exits `130` or `143` instead. With an older `com.unity.pipeline` the envelope and every rule in this list apply. - **Read failures from stdout, not stderr.** A failed command still writes a complete document to stdout: under `--format json` an envelope with `success: false` and a populated `errors` array (`errors[0].code` is the stable token to branch on); under `--format ndjson` the usual terminal `{"type":"result","success":false,…}` frame. **Every ndjson stream ends with exactly one terminal `{"type":"result"}` frame, on success too**, so a stream without one was truncated. Table-shaped listings (`unity editors`, `unity editors upgrade --check`, `unity templates`, `unity releases`, `unity projects`, `unity contexts`, `unity auth accounts`, `unity modules`, `unity command`, `unity skills`, `unity logs`, `unity cloud orgs`, `unity cloud projects` and the like) write one row per line first, each starting with `"type":"row"` so you can tell a row from a `progress` or `result` frame, then a terminal frame whose `data` is `{count, total, nextPage|nextCursor, partial}` (some listings keep older keys there too). A `unity templates` row keeps its own `type` (the template type) with no `"row"` marker. A single-record answer (`unity version`, `unity language`, `unity env`, `unity doctor`, `unity config proxy`, `unity cache info`, `unity context current`, `unity editors default`, `unity install-path`, `unity diagnose update` and the like) is that frame alone, its `data` the same as `--format json`. `--quiet` never changes json output. It never changes ndjson output either, except that `unity editors upgrade`, `unity build --list-targets` and `unity build --list-profiles` still drop their rows under `--quiet` (the terminal frame stays), a known bug; leave `--quiet` off when you need those rows. A warning in either format appears only in `warnings`, never on stderr. A `--watch` or `--follow` stream, which only ends on Ctrl-C, writes its rows and no terminal frame. **Branch on `success`, never on `data`** — `data` is usually `null` on a failure, but not always: a partial `unity editors add` failure carries a row per path, and an ambiguous `unity auth switch` carries `data.candidates` for you to disambiguate with. Check `success` and the exit code — never treat empty stdout as a failure signal, and do not parse stderr, which carries only human diagnostics in these formats. If stdout is empty on a non-zero exit, that is a known bug in that command rather than a shape you should code against. - **Never merge stderr into stdout when you parse (`2>&1`, or a tool that captures both as one stream).** Stdout alone is a complete, valid document under `--format json` and `--format ndjson`, but stderr carries human diagnostics and, for some agents, a one-line install hint (Claude Code gets a `` tag there). Merged in, those lines trail the JSON, and a parser fails with `Extra data` or a decoder needs `raw_decode`. Capture the two streams separately (`unity … --format json 2>err.txt >out.json`) and parse stdout only, or discard stderr with `2>/dev/null` (`2>NUL` on Windows cmd). `UNITY_NO_AGENT_HINTS=1` removes the hint line, but separate streams are the fix. - `unity [path]` is a shorthand for `unity open [path] --editor-version `. Works with `lts`, `latest`, or a full version string like `6000.0.47f1`. diff --git a/skills/unity-cli/references/auth-license-cloud.md b/skills/unity-cli/references/auth-license-cloud.md index 40d3dda..7cbf5bb 100644 --- a/skills/unity-cli/references/auth-license-cloud.md +++ b/skills/unity-cli/references/auth-license-cloud.md @@ -96,6 +96,22 @@ unity auth token --json - **`--audience` and `--scope` are refused** (exit `2`, `AUTH_TOKEN_NARROWING_UNSUPPORTED`) until Unity's token service can mint a narrowed token, rather than handing out a broader one than asked for. - The token is short-lived: fetch it again rather than storing it. The CLI never writes it to its logs; keep it out of yours. +#### Start the sign-in service for a plugin + +`unity auth broker ensure` makes sure the CLI's sign-in service (the auth broker) is running, starting it when nothing answers. It then connects as a sign-in would, so exit `0` means the CLI would use that service, not only that something answers. A plugin that reads the signed-in session through the broker runs it before it needs a token. It reads no credential and needs no sign-in. + +```bash +unity auth broker ensure +# {"state": "already-running" | "started" | "disabled" | "unavailable" | "refused", "address": } +unity auth broker ensure --json +``` + +- `already-running` and `started` exit `0`. +- `disabled` exits `6` with `AUTH_BROKER_DISABLED`: `UNITY_NO_AUTH_BROKER` is set. +- `unavailable` exits `6` with `AUTH_BROKER_UNAVAILABLE`: no service answers and none could start. +- `refused` exits `6` with `AUTH_BROKER_REFUSED`: a service answers, but the CLI will not use it (it could not be verified as genuine Unity software, or is too old). The message names the fix. +- Every failure's envelope still carries `data`. + #### Consumers — see who's used your sign-in, and mark one revoked Some products use your Unity sign-in without holding your refresh token themselves, brokering through the CLI's own auth broker instead. `unity auth consumers` lists every application in the broker's audit trail on this machine, plus any in the local revocation list, and `unity auth revoke` records one in that list. Read the Granted and Denied columns before concluding an application used your sign-in: one with only denials never received a token, and a revoked application with no audit history appears with no activity at all. @@ -277,6 +293,8 @@ saved default, and inside a Unity project folder linked to a cloud project it al project. Setting a default never changes which project a folder is linked to (`projects link cloud` does that). Under `--format json`, `activeCloudProjectId` still holds the default, with `defaultCloudProjectId` and `linkedCloudProjectId` beside it (`null` outside a linked project folder). +Under `--format tsv`, which is the default when output is piped, it prints the same fields as +`keyvalue` lines after a `key`/`value` header row (an empty value where `json` has `null`). **What consumes it.** Commands that need a Unity Cloud project but were not given one fall back to the stored default. The order is the explicit flag (`--cloud-project`), then `UNITY_CLOUD_PROJECT`, diff --git a/skills/unity-cli/references/build-run-test.md b/skills/unity-cli/references/build-run-test.md index 50cb413..c7f5c02 100644 --- a/skills/unity-cli/references/build-run-test.md +++ b/skills/unity-cli/references/build-run-test.md @@ -469,6 +469,8 @@ Keystore flags are validated together. Secrets passed as command-line flags surf **Interrupt exit codes** — interrupting `unity build` exits with the conventional signal code (`130` for Ctrl-C / SIGINT, `143` for SIGTERM) rather than a generic `1`, so callers and CI can tell an aborted build apart from a failed one. The temporary Android keystore is scrubbed before exit. +**Building through an open Editor** — when a Unity Editor already has the project open, `unity build --target` builds through it (the result carries `viaRunningEditor: true`) instead of refusing. Without `--timeout` the wait has no bound: if that Editor stops answering, the command keeps polling until the Editor exits, so set `--timeout` in CI (exit **14**, and the build keeps running in the Editor; check it with `unity command build status`). If the connection drops after the build request went out, the command exits **6** saying the Editor may have queued the build; run `unity command build status` before building again. + **Stall heartbeat** — a long build prints a periodic heartbeat (`Still building — 4m30s elapsed, last log output 3m10s ago`) tracking both total elapsed time and time since the Editor log last grew, so silence in the log no longer looks the same as a hang. Detection itself reads the Editor log's size directly, so it keeps working regardless of output mode — but whether the heartbeat is *printed* depends on the mode: on the human path it goes to stderr (so the streamed log stays clean) and is unaffected by `--no-tail`, but **`--quiet` suppresses it entirely** in human mode. Under `--format json`/`--format ndjson` it appears as periodic progress frames (on stderr under json, on stdout under ndjson) and is emitted regardless of `--quiet` — quiet only silences the human path. ```bash diff --git a/skills/unity-cli/references/cloud-automation.md b/skills/unity-cli/references/cloud-automation.md index a2c54cf..8472ebb 100644 --- a/skills/unity-cli/references/cloud-automation.md +++ b/skills/unity-cli/references/cloud-automation.md @@ -14,6 +14,7 @@ and to start, follow, and act on builds and jobs. - Read version history and job statistics - Filter and sort build targets - Request additional pages +- Set up a build target - Read results - Handle failures @@ -36,11 +37,18 @@ and to start, follow, and act on builds and jobs. | Run a pipeline, or follow a job to its end | `unity pipeline automation pipelines trigger` / `jobs watch` | | A job's logs, or one step's as it runs | `unity pipeline automation jobs logs` | | Stop a job, or answer a step that's waiting | `unity pipeline automation jobs terminate` / `resume` / `approve` / `reject` | +| Create or change a build target | `unity pipeline cloud-build targets create` / `update` | +| What changed in a target's configuration, and who changed it | `unity pipeline cloud-build targets auditlog` | +| Connect the project to a repository, or list its connections | `unity pipeline cloud-build connections` / `connections create` | +| Check that Build Automation can reach a repository | `unity pipeline cloud-build scm check-url` | +| Upload signing credentials | `unity pipeline cloud-build credentials upload` | +| Whether the organization has used up its free tier | `unity pipeline cloud-build org free-tier` | The inventory commands (the first nine rows) are read-only. The rest start, cancel, and change remote work: read [Confirm, wait, and -stop](#confirm-wait-and-stop) before running them for a user. None of them -change configuration. They don't require a running Editor or +stop](#confirm-wait-and-stop) before running them for a user. `targets create` +and `update`, `connections create` and `credentials upload` change the project's +configuration: read [Set up a build target](#set-up-a-build-target) first. They don't require a running Editor or the local Pipeline package. Local `unity pipeline install|upgrade|list|list-versions` commands manage the Editor package, not cloud resources. @@ -428,6 +436,101 @@ service answers the same way for a build that had already finished, so the result (`data: null`) and the human line say cancellation was requested, not that a build was cancelled. It asks first; see below. +## Set up a build target + +The order for a project with nothing set up yet: + +```sh +unity vcs setup --vcs uvcs # only when the project has no repository +unity pipeline cloud-build scm check-url --type git --url "" --user "" --password-from-stdin < pass.txt +unity pipeline cloud-build connections create --name game --type plastic --url "@cloud" --format json +unity pipeline cloud-build credentials android upload --label release --keystore release.keystore --alias key0 --keystore-password-from-stdin < store-pass.txt +unity pipeline cloud-build org free-tier --format json +unity pipeline cloud-build targets create --name webgl-main --platform webgl --repo "" --branch main --remote-cache-strategy library --format json +unity pipeline cloud-build builds start --build-target webgl-main --wait --format ndjson +``` + +**Connections.** The project's first connection becomes its default, and +`targets create` uses the default unless `--connection ` names another. +`--type plastic` (Unity Version Control) takes no secret: the CLI exchanges your +Unity sign-in for the repository token. A UVCS target also needs `--repo `, +the repository `vcs setup` created, or the service builds one named `main`. `--type oauth` needs `--git-provider` +(`github`, `gitlab` or `bitbucket`) and a token. The service checks that it can +reach the repository before it creates any connection, and refuses one it can't +(exit `6`, `Repository URL is not reachable`). So `--type git` with an SSH URL +works only once the organization's SSH key can read the repository; where the +organization has per-connection keys turned on, `--ssh-key-type` asks for a new +key, and the response carries its public key. Prefer `oauth` or `plastic` when +nothing is set up yet. `scm check-url` checks an HTTPS URL with a user and +password before creating its connection, but the service checks a GitHub HTTPS +URL over SSH; check an OAuth, SSH or Unity Version Control URL after creating +its connection, with `--connection `, since those checks use its stored token +or key. An unreachable URL fails with +`AUTOMATION_SCM_URL_INACCESSIBLE`, exit `6`, and keeps `isAccessible`, +`normalizedUrl` and `errorMessages` under `data`. + +**Targets.** Each option sets one field, and `targets update` changes only the +fields given. `--file ` reads a whole target as JSON (`-` reads stdin); +options override its fields. A saved `targets get --format json` result works as +it is: the CLI reads its `data` and drops the read-only fields, so `get`, edit, +`create --file` copies a target. The copy has no signing credentials, since +`get` never shows them: pass `--credential` again. Booleans +have a `--no-` form. `--scene` repeats, and on `update` replaces the whole list. +`--editor-version`, `--xcode-version` and `--machine-type` take the values the +`tooling` lookups list; prefer a machine type with `freeTierEligible: true`, and +choose a paid one, or `--remote-cache-strategy boostdisk`, only when the user asks. +Deprecated Unity, Xcode or OS versions come back in `data.notices` (human output +warns). A setting the service refuses fails with exit `6` and its details. + +**Target options.** `targets create` and `targets update` take the same options; +each sets one field of the target. + +| Option | Sets | +|---|---| +| `--name`, `--platform`, `--connection` | Name, platform (required on create), source control connection | +| `--branch`, `--subdirectory`, `--repo` | Branch to build, project folder in the repository, Unity Version Control repository (the service builds `main` without it) | +| `--editor-version`, `--auto-detect-editor-version` | Editor version, or the one the project uses | +| `--executable-name`, `--bundle-id`, `--build-profile-path`, `--xcode-version` | Executable, bundle ID, build profile, Xcode version | +| `--scene` (repeatable), `--pre-export-method`, `--post-export-method` | Scene list in build order, static methods run around the export | +| `--build-addressables`, `--run-unit-tests`, `--run-edit-mode-tests`, `--run-play-mode-tests` | Addressables content, tests before the build | +| `--development-build`, `--app-bundle` | Development build, Android App Bundle instead of APK | +| `--remote-cache-strategy`, `--artifact-compression-level` | Build cache, artifact compression | +| `--machine-type`, `--builder-os`, `--builder-os-version` | Build machine | +| `--credential` | Signing credentials | +| `--auto-build`, `--schedule`, `--schedule-start`, `--schedule-cron`, `--schedule-timezone` | Build on commit, build on a schedule | + +Each boolean has a `--no-` form to turn it off on `update`: +`--no-auto-detect-editor-version`, `--no-build-addressables`, `--no-run-unit-tests`, +`--no-run-edit-mode-tests`, `--no-run-play-mode-tests`, `--no-development-build`, +`--no-app-bundle` and `--no-auto-build`. + +**Credential options.** Every upload takes `--label`. +- `credentials android upload`: `--keystore`, `--alias`, `--keystore-password-from-stdin`, `--key-password-from-stdin`. +- `credentials ios upload`: `--certificate` (.p12), `--provisioning-profile`, `--certificate-password-from-stdin` (optional). +- `credentials osx upload`: `--certificate`, `--apple-id`, `--apple-id-password-from-stdin`, `--provider-name`, `--entitlements`, `--certificate-password-from-stdin`. +- `credentials uwp upload`: `--certificate`, `--certificate-password-from-stdin`. +- `connections create --type oauth` takes its token with `--token-from-stdin`, and `--password-from-stdin` carries a git password. + +**Credentials.** Upload once, then pass the returned `credentialid` as +`--credential`. iOS returns the profile's `bundleId`: compare it with the target's +`--bundle-id`. Each password comes from its `--…-from-stdin` option or its variable +(`UNITY_KEYSTORE_PASSWORD`, `UNITY_KEY_PASSWORD`, `UNITY_CERTIFICATE_PASSWORD`, +`UNITY_APPLE_ID_PASSWORD`, `UNITY_SCM_PASSWORD`, `UNITY_SCM_TOKEN`); never put one in +an argument. Stdin carries one value, so a second secret, or a `--file -` body, +takes the variable (`AUTOMATION_STDIN_CONFLICT`, exit `2`, otherwise). Output never +shows a secret. + +**Audit log.** `targets auditlog ` lists the target's configuration +changes and its project's, newest first, paged with `--page` and `--limit`. Each +entry has `updated`, `updatingUserEmail`, `scope` and `lines` of `field`, `from` +and `to`; the table has one row per changed field. Environment variable changes +are not recorded, a signing change shows only as `changed`, and secret values +show as `***`. Compare an entry's `updated` with a build's `created` to see which +setting changed between a passing and a failing build. + +These commands don't ask before they act and take no `--yes`: confirm the +summary with the user first, since every one changes the real project. + ## Trigger a pipeline and watch a job ```sh @@ -505,7 +608,7 @@ They can only ask on an interactive terminal in human format, without `--non-interactive`, `--quiet`, or a CI system. Anywhere else they fail with `AUTOMATION_CONFIRMATION_REQUIRED`, exit `2`, unless `--yes` (`-y`) is given; a declined question is `AUTOMATION_CANCELLED_BY_USER`, exit `1`. No other command -asks or accepts `--yes`. Pass `--yes` only when the user has asked for that exact +asks or accepts `--yes`; the target setup commands don't ask either. Pass `--yes` only when the user has asked for that exact operation on that exact build or job. **Local cancellation.** A `SIGINT` (Ctrl-C) or `SIGTERM` during diff --git a/skills/unity-cli/references/collaboration.md b/skills/unity-cli/references/collaboration.md index decdc4c..cac896b 100644 --- a/skills/unity-cli/references/collaboration.md +++ b/skills/unity-cli/references/collaboration.md @@ -232,7 +232,7 @@ Annotation objects returned by `get`/`list`/`replies` (`--format json`) carry: ` | `annotations create` | `` | `--text`, `--reply-to `, `--status Active\|Draft`, `--metadata k=v`…, `--target-context k=v`…, `--camera`, `--local`, `--time`, `--attachments`, `--unresolve-root-annotation` | | `annotations delete` | `` | (confirmation — see Shared behavior) | | `annotations delete-fields` | ` ` | removes metadata fields; variadic; confirmation | -| `annotations export` | — | `--target ` — **defaults to `assets/projects//**` only**, see below; `--out ` (else stdout), `--resolve-users`; the service returns `assetId` + `assetName` here that `list` does not — the CLI copies the response page verbatim, so treat those as service behavior | +| `annotations export` | — | `--target ` — omitted, no target is sent and the export covers the whole project, see below; `--out ` (else stdout), `--resolve-users`; the service returns `assetId` + `assetName` here that `list` does not — the CLI copies the response page verbatim, so treat those as service behavior | | `annotations get` | `` | `--fields a,b,c` or `--fields all` (table output only), `--resolve-users` | | `annotations` | — | `--query ` (optional — defaults to root threads only), `--next `, `--limit 1-100` (default 10), `--all`, `--sort Ascending\|Descending`, `--sort-field annotationId\|latestReply`, `--include-fields a,b`, `--fields a,b` or `--fields all` (table output only), `--full`, `--resolve-users` | | `annotations replies` | `` | `--next`, `--limit 1-100`, `--all`, `--sort`, `--status-filter All\|Active\|Sending\|Draft` (repeat flag), `--fields a,b` or `--fields all` (table output only), `--full`, `--resolve-users` | @@ -259,20 +259,23 @@ unity collaboration annotations --include-fields replyCount,latestReply --format unity collaboration annotations --cloud-project $PROJ \ --query '[{"type":"hasNot","field":"annotationParentId"}]' --all --format ndjson -# Export for offline analysis — ONE prefix tree per run; the bare command covers -# only assets/**, so an Editor-annotated project needs both invocations. +# Export for offline analysis: the bare command sends no target (the whole project) +unity collaboration annotations export --cloud-project $PROJ --out annotations.json +# Restrict it to one asset unity collaboration annotations export --cloud-project $PROJ \ - --target "assets/projects/$PROJ/**" --out annotations-assets.json -unity collaboration annotations export --cloud-project $PROJ \ - --target "unity/projects/$PROJ/**" --out annotations-editor.json + --target "unity/projects/$PROJ/assets//**" --out annotations-asset.json ``` -**`export` is not a whole-project export.** With no `--target` the command defaults to -`assets/projects//**`, so annotations under the separate `unity/projects//**` -tree are silently absent from the archive — and nothing in the output says so. Always pass `--target` -explicitly, once per prefix, when completeness matters. (Note the default differs from -`annotations count`, which defaults to the `unity/**` tree instead.) The CLI's own `--target` help -text says "the whole project", which contradicts the actual default — trust the prefix above. +**`export` with no `--target` sends no target.** The request carries only the page size, which asks +the service for the whole project; no path prefix decides what is included. Compare its `count` with +`annotations count --grouped` per prefix when completeness matters. A `--target` the service finds no +assets for fails with HTTP 500 "No assets found for the provided ids"; the error then names the target +and suggests running without `--target`. + +When the service answers an export with HTTP 500 "No assets found for the provided ids", an annotation points +at an asset that no longer exists and the service refuses the whole export without naming it. The CLI +appends a hint: list the targets with `annotations count "" --grouped` (the target the export asked for; `count` defaults to another tree), then export the ones that still exist +with `--target`. **`--query` shape.** A JSON *array* of clauses (a bare object is rejected: `The --query value must be a JSON array of query clauses.`). Clause vocabulary is the @@ -327,7 +330,7 @@ over a camera view), **spatial** (labeled 3D anchor) — payload shapes in | `attachments download` | ` ` | `--out ` (default: the attachment's original filename in CWD, falling back to `` when it has no file path), `--force` (overwrite), `--width ` (resize image) | | `attachments upload` | ` ` | `--name` (display name), `--content-type` (override inferred MIME) | | `attachments add file` | ` ` | same options and **same handler** as `upload`; only the reported command label, the success message, and the JSON error code (`COLLAB_ATTACHMENTS_ADD_ERROR`) differ — use either | -| `attachments add sketch` | `` | `--sketch-data ` **(required)**, `--camera ` **(required)**, `--time `, `--preview `, `--sketch-image ` | +| `attachments add sketch` | `` | `--sketch-data ` **(required)**, `--camera ` **(required)**, `--time `, `--preview `, `--sketch-image `: pass **both files or neither** (one alone exits 2, `COLLAB_SKETCH_FILES_INCOMPLETE`, before any request) | | `attachments add spatial` | `` | `--label` **(required)**, `--position ` **(required)**, `--camera ` **(required)**, `--time `, `--local ` | | `attachments update [file]` | ` ` | `--content-type`, `--metadata k=v`… — **at least one required**; `file` is the **default variant**: `update ` without a subcommand means `update file` | | `attachments update sketch` | ` ` | `--sketch-data`, `--camera`, `--time`, `--metadata k=v`… — each individually optional, but **at least one required** | @@ -349,11 +352,15 @@ unity collaboration attachments download $ANNOTATION_ID $ATTACHMENT_ID --cloud-p Notes: +- Under `--format ndjson` each attachment is one row line carrying its own `type` (`file`, `sketch`, + `spatial-3d`) and no `"type":"row"` marker, then the `{"type":"result",…}` frame closes the stream. - `--sketch-data` is passed through as a raw string, not parsed as JSON — only `--camera`/`--time`/ `--position`/`--local` get JSON validation at the CLI layer. - Options required on `add sketch`/`add spatial` become *individually* optional on the matching `update` variant — but every variant, `file` included, rejects a flagless invocation with a `NO_FIELDS` error. Pass at least one change flag. +- `update sketch` without `--sketch-data` reads the annotation first and resends the attachment's + stored sketch data, since the service rejects a sketch update that lacks it. - The local-space anchor is `--local` here and on annotations (same JSON shape). --- @@ -367,7 +374,7 @@ else inferred from the current project (see [Shared behavior](#shared-behavior)) |---|---|---| | `reactions add` / `reactions remove` | ` ` | emoji is a single Unicode emoji, e.g. `👍` | | `thumbnail upload` | ` ` | image file; MIME inferred from extension (jpg/png/gif/webp) | -| `thumbnail download` | `` | `--out ` (default `./thumbnail`), `--width `; **no `--force`** — errors if the file exists ("Delete it first") | +| `thumbnail download` | `` | `--out ` (default `./thumbnail`), `--width ` (an integer of at least 50); **no `--force`** — errors if the file exists ("Delete it first") | | `read` | `` | `--timestamp ` (default now) — marks the thread read up to that time (per-user read receipt) | | `subscribe` / `unsubscribe` | `` | per-thread notification subscription for the current user | @@ -456,7 +463,7 @@ usage error. |---|---|---| | `jira project add` | `` | `--cloud-org`, `--jira-project-id`, `--default-reporter-id` — all required, though `--help` doesn't say so (fallback reporter when an annotation author has no Jira match) | | `jira project delete` | `` | `--cloud-org` (required, not marked in `--help`); confirmation | -| `jira project link` / `jira project unlink` | ` ` | — (Unity project id is positional here, not a flag) | +| `jira project link` / `jira project unlink` | ` ` | — (Unity project id is positional here, not a flag). `unlink` fails with HTTP 404 (exit 6) when the project isn't linked to that configuration | | `jira project update` | `` | `--cloud-org` (required, not marked in `--help`), `--default-reporter-id`, `--linked-unity-project-id ` (repeatable — **replaces** the whole linked list), `--clear-linked-unity-projects` (mutually exclusive with the previous flag); at least one change flag required | #### `jira issues` @@ -465,7 +472,7 @@ usage error. |---|---|---| | `jira issues create` | `` | `--jira-project-config-id`, `--summary`, `--type ` — required; `--cloud-project`/`--project-path` optional (inferred); `--description`, `--assignee-user-id`, `--reporter-user-id`, `--parent-issue-id` (sub-task) | | `jira issues get` | `` | `--jira-project-config-id` required; `--cloud-project`/`--project-path` optional (inferred) | -| `jira issues link` / `jira issues unlink` | ` ` | `--cloud-project`/`--project-path` optional (inferred); `link` also takes optional `--jira-project-config-id`. `unlink` does **not** delete the issue in Jira | +| `jira issues link` / `jira issues unlink` | ` ` | `--cloud-project`/`--project-path` optional (inferred); `link` also takes optional `--jira-project-config-id`. `unlink` does **not** delete the issue in Jira, and fails with HTTP 404 (exit 6) for an issue the annotation's thread doesn't have linked | | `jira issues` | — | `--cloud-org` **(required, org-scoped — no `--cloud-project`/`--project-path` here)**, `--profile all\|active\|resolved\|unresolved\|draft\|sending` (repeat flag), `--next `, `--limit 1-100` (default 10), `--all`, `--sort Ascending\|Descending` | | `jira issues search` | — | `--jira-project-config-id` required; `--cloud-project`/`--project-path` optional (inferred); `--query ` (plain text, **not JQL**), `--include-subtasks` | | `jira issues types` | — | `--jira-project-config-id` required; `--cloud-project`/`--project-path` optional (inferred); lists issue type ids for `create --type` | diff --git a/skills/unity-cli/references/config-hub.md b/skills/unity-cli/references/config-hub.md index aaa5001..f950d9f 100644 --- a/skills/unity-cli/references/config-hub.md +++ b/skills/unity-cli/references/config-hub.md @@ -24,7 +24,8 @@ The `config` command group manages settings that persist across invocations. View or change the configured HTTP/HTTPS/SOCKS/PAC proxy. The persisted value is read by every CLI command that issues outbound HTTP (releases, install, auth, telemetry, etc.). ```bash -# Show the effective proxy configuration (resolution source + auth source) +# Show the effective proxy configuration (resolution source + auth source). +# With the global --proxy , it shows that URL with source "flag". unity config proxy unity config proxy --json diff --git a/skills/unity-cli/references/diagnostics-maintenance.md b/skills/unity-cli/references/diagnostics-maintenance.md index d30f355..8bf536c 100644 --- a/skills/unity-cli/references/diagnostics-maintenance.md +++ b/skills/unity-cli/references/diagnostics-maintenance.md @@ -460,6 +460,14 @@ can't be combined with another documentation set, `--search` or `packageVersion` (the full version, `null` for a built-in package outside a project) and `resolvedBy`: `project`, `editor` or `registry`. +When the project has the package's own docs (`Packages//Documentation~`, +else the cache entry under `Library/PackageCache` at the locked version, else +the newest one), `docs --package` opens that local Markdown page instead, offline +and without asking the registry. `--online` skips it, and `--url` always prints +the online manual. JSON for a local page says `resolvedBy: local`, +`source: local` and the file's `path`, with `packageVersion` from the package's +`package.json`. + #### Documentation sets outside the Engine docs `--cli`, `--hub` and `--cloud` look the topic up in another documentation set diff --git a/skills/unity-cli/references/docs-workflow.md b/skills/unity-cli/references/docs-workflow.md index a32a09f..412c291 100644 --- a/skills/unity-cli/references/docs-workflow.md +++ b/skills/unity-cli/references/docs-workflow.md @@ -96,6 +96,10 @@ Package manuals live on docs.unity3d.com, not docs.unity.com. The version is the with `DOCS_PACKAGE_UNKNOWN`, and a registry that can't be reached with `NETWORK_UNREACHABLE`. `--package` takes no topic and can't be combined with another documentation set, `--search` or `--editor-version`. +Without `--url`, a project that has the package's own `Documentation~` page opens that local Markdown +first, offline, with no registry call; `--online` skips it. JSON then says `resolvedBy: local`, +`source: local` and gives the file's `path`. `--url` always prints the online manual, so use it for links. + ### 4. Work offline: `docs cache` ```bash diff --git a/skills/unity-cli/references/editors-install.md b/skills/unity-cli/references/editors-install.md index c18b313..e37420c 100644 --- a/skills/unity-cli/references/editors-install.md +++ b/skills/unity-cli/references/editors-install.md @@ -160,6 +160,8 @@ unity editors prune --format json The report lists version, architecture, path, size, and status, then the total reclaimable size. With `--remove` in a non-interactive shell and no `-y, --yes`, it refuses rather than deleting unprompted. "Unused" is judged against the **project registry** (`unity projects`), so an editor used only by a project you never registered counts as unused — register it first, or verify with `unity editors prune` before adding `--remove`. +When an Editor folder holds files this user can't delete (installed with `sudo` or by a package), `prune --remove`, `uninstall` and `editors upgrade --replace` remove it through an administrator password dialog, and only on a terminal: one line says why first. With no terminal on stdin, under `--non-interactive`, in CI, or with `--format json`/`ndjson`/`tsv`, they stop before anything is removed with exit 2 and code `UNINSTALL_ELEVATION_NEEDS_TERMINAL`. Run the command from a terminal or as root; `UNITY_NO_ELEVATE=1` skips the dialog but refuses the removal too. + **`--remove-missing` is a separate, safer candidate class:** editors registered with `unity editors add` (typically internal branch builds) whose recorded install path no longer resolves on disk at all. ```bash @@ -263,7 +265,7 @@ unity install 6000.0.47f1 # Install with specific modules unity install 6000.0.47f1 --module windows-mono --module android -# Install a specific changeset by hash +# Install a specific changeset by hash (a changeset that is not that version's build exits 11, RELEASE_NOT_FOUND) unity install 6000.0.47f1 --changeset abc123def456 # Include child modules @@ -404,6 +406,9 @@ unity uninstall 6000.0.47f1 --yes unity uninstall 6000.0.47f1 --architecture arm64 --yes ``` +- The json result (and the one ndjson result frame) carries the removed Editor as `data.version` and `data.architecture`. Under `--format tsv` it is a `keyvalue` table of the same two members. A human run prints the completion line. +- With no version, the stored default Editor is used and announced on stderr (`Using default editor version: ...`), so stdout stays the result alone under every format. + --- ### Modules — add/list per editor diff --git a/skills/unity-cli/references/integration-advanced.md b/skills/unity-cli/references/integration-advanced.md index 9758708..0744888 100644 --- a/skills/unity-cli/references/integration-advanced.md +++ b/skills/unity-cli/references/integration-advanced.md @@ -14,6 +14,7 @@ Relative links in this file resolve to the skill directory's `references/` folde - Setup — install the Unity plugin into Claude Code - Setup — install the Unity plugin into Codex or Grok Build - Setup — install the Unity plugin into Antigravity + - Setup — Unity skills for VS Code and OpenCode - Skill — install this skill into an AI client - Plugin — manage optional CLI-adjacent tools - Connected Editors — pipeline / command / status @@ -84,6 +85,8 @@ All four are present only when the installed `com.unity.pipeline` publishes them ### MCP — Model Context Protocol server (AI agent integration) +**Prefer the CLI when you can run shell commands.** Use `unity command` and the other commands in this skill instead of the Unity MCP server: the CLI exposes more than the server's tools do. MCP is for clients that have no shell, such as Claude Desktop, and `unity setup all` configures it only for those. + New in `0.1.0-beta.8`. `unity mcp` starts a Model Context Protocol server, built into the `unity` binary, that exposes the commands of a connected Unity Editor as MCP tools. AI agent clients connect over stdio, list those tools, and run them. The server starts even when no Editor is running and reports that it isn't connected; commands that a connected Editor adds show up as tools automatically. ```bash @@ -146,7 +149,7 @@ unity mcp configure cursor --replace-legacy ### Setup — install the Unity plugin into Claude Code -`unity setup claude` installs Unity's Claude Code plugin, which carries the Unity skills (this one included), in one step. It runs the two `claude` commands for you — `claude plugin marketplace add anthropics/claude-plugins-official` (Anthropic's official marketplace, which Claude Code usually has already) and then `claude plugin install unity@claude-plugins-official`, both at user scope — and reports what you now have. A plugin installed earlier from Unity's own marketplace (`unity@unity-agent-plugin`) is the same plugin, so it's reported as installed under that id. It changes the user's Claude Code configuration, so run it only when the user asks for the Unity plugin, or after they agree to install it; don't run it on your own initiative. +`unity setup claude` installs Unity's Claude Code plugin, then the Unity skills (this one included) into `~/.claude/skills`, in one step. The plugin itself is thin: a bootstrap skill and a session hook that keeps the skills current. It runs the two `claude` commands for you — `claude plugin marketplace add anthropics/claude-plugins-official` (Anthropic's official marketplace, which Claude Code usually has already) and then `claude plugin install unity@claude-plugins-official`, both at user scope — and reports what you now have. A plugin installed earlier from Unity's own marketplace (`unity@unity-agent-plugin`) is the same plugin, so it's reported as installed under that id. It changes the user's Claude Code configuration, so run it only when the user asks for the Unity plugin, or after they agree to install it; don't run it on your own initiative. ```bash # Install the plugin (safe to re-run: an existing install is reported, not reinstalled) @@ -165,7 +168,7 @@ The json `data` carries `pluginId`, `version`, `scope`, `alreadyInstalled`, `tak - **A `claude` step fails or times out** — fails with `CLAUDE_PLUGIN_INSTALL_FAILED` (exit 6), quoting the last line `claude` printed. Nothing is retried. - It reads `CLAUDE_CONFIG_DIR` when set, the same place `claude` writes. -Because the plugin already carries the `unity-cli` skill, don't also run `unity skill install claude-code` — Claude Code would load two copies. This command never writes a skill copy or an MCP registration of its own. +The skills come from the same quiet run as `unity skill install claude-code`, after the plugin is installed or found already installed, so don't also run that command. A skill an older plugin still provides stays skipped, so Claude Code never loads two copies. A failed skills step is a warning that names the command to retry, not a failure. In `--format json`, `installedVia` is `"plugin"` and `skillsToLoad` names the skills installed, and `skillsPath` is the skills folder. `--dry-run` adds `unity skill install claude-code` to `commands` and installs nothing. This command never registers an MCP server. ### Setup — install the Unity plugin into Codex or Grok Build @@ -179,7 +182,7 @@ unity setup codex --dry-run unity setup grok --format json ``` -Both take `--dry-run` and every output format, and report the same json `data` as `setup claude`, except `reloadCommand` is `null`: neither agent has a reload command, so a fresh install loads in the next session (`takesEffect: "nextSession"`). The plugin carries the `unity-cli` skill, so don't also run `unity skill install codex` (or the Grok equivalent). +Both take `--dry-run` and every output format, and report the same json `data` as `setup claude`, except `reloadCommand` is `null`: neither agent has a reload command, so a fresh install loads in the next session (`takesEffect: "nextSession"`). Like `setup claude`, each then installs the Unity skills for its client (`codex`, `grok`), so don't also run `unity skill install` for them. ### Setup — install the Unity plugin into Antigravity @@ -193,7 +196,7 @@ Both take `--dry-run` and every output format, and report the same json `data` a unity setup antigravity --dry-run ``` -It takes `--dry-run` and every output format, reports the same json `data` as `setup codex`, and a fresh install loads in the next Antigravity session. +It takes `--dry-run` and every output format, reports the same json `data` as `setup codex`, and a fresh install loads in the next Antigravity session. It also installs the Unity skills for the `antigravity` client (`~/.gemini/config/skills`). ### Setup — install the Unity plugin into GitHub Copilot @@ -208,7 +211,7 @@ It takes `--dry-run` and every output format, reports the same json `data` as `s unity setup copilot --dry-run ``` -It takes `--dry-run` and every output format, reports the same json `data` as `setup codex`, and a fresh install loads in the next Copilot session. The plugin carries the `unity-cli` skill, so don't also run `unity skill install` for Copilot CLI. +It takes `--dry-run` and every output format, reports the same json `data` as `setup codex`, and a fresh install loads in the next Copilot session. It also installs the Unity skills for the `vscode` client (`~/.copilot/skills`), so don't also run `unity skill install` for Copilot CLI. ### Setup — install the Unity plugin into Qwen Code @@ -222,11 +225,23 @@ It takes `--dry-run` and every output format, reports the same json `data` as `s unity setup qwen --dry-run ``` -It takes `--dry-run` and every output format, reports the same json `data` as `setup codex`, and a fresh install loads in the next Qwen Code session. The extension carries the `unity-cli` skill, so don't also install the skill into Qwen Code. +It takes `--dry-run` and every output format, reports the same json `data` as `setup codex`, and a fresh install loads in the next Qwen Code session. It also installs the Unity skills for the `qwen` client (`~/.qwen/skills`), so don't also install them yourself. + +### Setup — Unity skills for VS Code and OpenCode + +Neither editor has a plugin CLI to drive, so each has its own command. Run one only when the user asks to set up that editor, or after they agree. Both install the Unity skills the way `unity skill install ` does, take `--dry-run` and every output format, and fail if the skills can't be installed. + +- `unity setup vscode` installs the skills for the `vscode` client (`~/.copilot/skills`) and prints how to add Unity's agent plugin by hand: in VS Code run **Chat: Install Plugin from Source** and enter `https://github.com/Unity-Technologies/unity-agent-plugin` (the Agent Customizations editor works too). VS Code reads the plugin's `.claude-plugin/plugin.json`. It changes no VS Code settings file. The json `data` carries `client`, `dryRun`, `skillsPath`, `skillsToLoad`, `commands` and `pluginSource`. +- `unity setup opencode` installs the skills for the `opencode` client and writes one OpenCode plugin file, `~/.config/opencode/plugins/unity.js` (under `$XDG_CONFIG_HOME/opencode` when that is set, as OpenCode reads it). On `session.created` it runs `unity skill sync opencode --hook --format json` in the background, ignores any failure, and when a skill changed it shows a toast and adds the change to the session as context without asking the model to reply. The file's first line records a digest of what the CLI wrote. A rerun replaces an unedited copy, reports `unchanged` when nothing differs, and leaves a file the user edited, or wrote themselves, alone with a warning (`pluginFileAction: "skipped-edited"`). `--yes` replaces it anyway: use it only after the user agrees. The json `data` carries `client`, `dryRun`, `skillsPath`, `skillsToLoad`, `commands`, `pluginFile` and `pluginFileAction` (`created`, `updated`, `unchanged`, `skipped-edited` or `would-write`). + +```bash +unity setup vscode --dry-run +unity setup opencode --format json +``` ### Setup: install the Unity skills when the plugin can't be installed -`unity setup claude`, `unity setup codex`, `unity setup grok` and `unity setup copilot` fall back to the Unity skills when the agent's CLI isn't on your PATH (`*_NOT_FOUND`) or its plugin install fails (`*_PLUGIN_INSTALL_FAILED`). They install the same skill set as `unity skill install ` into that agent's user-global skills folder: `claude-code`, `codex`, `grok`, and `vscode` (which is `~/.copilot/skills`) for Copilot. `unity setup antigravity` has no skills client, so it fails as before. +`unity setup claude`, `unity setup codex`, `unity setup grok` and `unity setup copilot` install the Unity skills even when the plugin is in place (see above), and fall back to only the skills when the agent's CLI isn't on your PATH (`*_NOT_FOUND`) or its plugin install fails (`*_PLUGIN_INSTALL_FAILED`). They install the same skill set as `unity skill install ` into that agent's user-global skills folder: `claude-code`, `codex`, `grok`, and `vscode` (which is `~/.copilot/skills`) for Copilot. `unity setup antigravity` and `unity setup qwen` have no such fallback, so they fail as before. - **In a terminal**, it prints the plugin error as a warning and asks whether to install the skills instead (default yes). Declining exits with the plugin error. - **With `--yes`**, it installs the skills without asking. Run it only after the user agrees. @@ -236,11 +251,11 @@ It takes `--dry-run` and every output format, reports the same json `data` as `s unity setup codex --yes --format json ``` -The json `data` has the same keys as a plugin install, with `version`, `scope`, `reloadCommand` and `installedAt` set to `null`, plus `installedVia: "skills"` and `skillsToLoad`, the names of the skills it installed. `takesEffect` is `"nextSession"`: the skills load in the next session, so until then read them from `skillsPath`. `warnings` carries the plugin error. A skill an installed plugin already provides is skipped, and if nothing could be installed the run fails with the plugin error. `installedVia` and `skillsToLoad` appear only on this path. +The json `data` has the same keys as a plugin install, with `version`, `scope`, `reloadCommand` and `installedAt` set to `null`, plus `installedVia: "skills"` and `skillsToLoad`, the names of the skills it installed (`installedVia` is `"plugin"` when the skills went in beside the plugin). `takesEffect` is `"nextSession"`: the skills load in the next session, so until then read them from `skillsPath`. `warnings` carries the plugin error. A skill an installed plugin already provides is skipped, and if nothing could be installed the run fails with the plugin error. `installedVia` and `skillsToLoad` appear only when skills were installed. ### Setup — set up every coding agent on the machine -`unity setup all` finds each coding agent on the machine and sets each one up in turn, without prompting. Run it only when the user asks to set up their agents, or after they agree. A plugin agent (Claude Code, Codex, GitHub Copilot CLI, Grok Build, Antigravity, Qwen Code) gets the Unity plugin, as `unity setup claude` and its siblings do. A skills-only client (such as Cursor or Cline) gets the Unity skills, as `unity skill install ` does (offline, only the two embedded ones), and `unity mcp configure ` where that command supports the client. One client failing doesn't stop the rest. +`unity setup all` finds each coding agent on the machine and sets each one up in turn, without prompting. Run it only when the user asks to set up their agents, or after they agree. A plugin agent (Claude Code, Codex, GitHub Copilot CLI, Grok Build, Antigravity, Qwen Code) gets the Unity plugin and its skills, as `unity setup claude` and its siblings do. A skills-only client (such as Cursor, Cline or OpenCode) gets the Unity skills, as `unity skill install ` does (offline, only the two embedded ones). `unity mcp configure` runs only for a client with no shell, never for the others. One client failing doesn't stop the rest. ```bash unity setup all --dry-run @@ -250,7 +265,7 @@ unity setup all --format json It takes `--dry-run` and every output format. The json `data` is `clients`, one object per client with `client`, `displayName`, `kind` (`plugin` or `skills`), `outcome` (`installed`, `already_installed`, `skipped`, `failed` or `dry_run`), `plugin` (`id`, `version`, plugin agents only), `skills` (`name`, `version`, what was installed, module skills included), `warnings` (notes for that client, present only when there are any) and, on failure, `error` (`code`, `message`), plus `skillsToLoad`, the skills to read now. A machine with no agents reports an empty `clients` and exits 0. - **Edited skill.** A managed skill still matching what the CLI wrote is updated. One you edited is left alone: its outcome is `skipped`, `warnings` names `unity skill install --force` to overwrite it, and the run still exits 0. -- **MCP.** A skills client's `unity mcp configure --yes` step runs on every non-dry run, so a rerun repairs a failed one. A client that fails there has the error code `MCP_CONFIGURE_FAILED`; retry it with `unity mcp configure --yes`. `vscode` and `vscode-insiders` only have a project target, so they are not configured automatically: `warnings` says to run `unity mcp configure ` inside the project. +- **MCP.** A client that can run shell commands uses the CLI and its skills, so `setup all` registers no MCP server for it. Run `unity mcp configure ` yourself if the user wants one. Only a client with no shell is configured automatically, and a failure there has the error code `MCP_CONFIGURE_FAILED`; retry it with `unity mcp configure --yes`. - **Copilot and VS Code.** When `setup all` sets up the Copilot CLI plugin and also finds VS Code, it does not copy a second `unity-cli` skill into `~/.copilot/skills` (module skills still install). The `vscode` client reports `already_installed` with a warning saying so; if VS Code doesn't see the Unity skill, run `unity skill install vscode`. - **Failures.** The run exits 6 when any client failed (a skip is not a failure); retry a plugin agent with `unity setup ` and a skills client with `unity skill install `. - **State.** After the clients, a human run prints a block with the account, license, Editors and network state and the next command. With `--format json` or `ndjson`, anything blocking comes as `notifications` entries on the same envelope, each with a `remediation.command`, and `notifications` is absent when nothing blocks. @@ -268,6 +283,9 @@ unity skill install --list # Install into a client's user-global skills directory unity skill install claude-code +# Install into every agent client found on this machine +unity skill install --all + # Install into the current project instead of the user-global location unity skill install cursor --local @@ -287,13 +305,15 @@ A default `unity skill install ` writes every Unity skill side by side: - **Offline.** When the download fails, the run installs the two embedded skills (`unity-cli` and `new-unity-project`), warns with the names of the pinned skills it couldn't install, and still exits `0`. Run it again once the machine is online. - **Records.** Under `--format json`, a record for an embedded or repo skill carries `source` (`embed` for a skill built into the CLI, `repo` for one from the pinned repository) and, for a repo skill, `commit`, the pinned commit it came from. A pinned skill the offline run skipped has `skipped: true` and `skipReason: "offline"`. -Supported clients: `claude-code`, `claude-desktop`, `grok`, `cursor`, `windsurf`, `vscode`, `cline`, `codex`, `opencode`. Each is written in the format that client expects, at its platform-correct location. Not every client supports both scopes — some are user-global only, others project-local only — and `--list` reports which, so check there rather than guessing. +`unity skill install --all` installs into every agent client found on this machine, the clients `unity setup all` finds. A plugin agent gets the skills client it reads from: Claude Code `claude-code`, Codex `codex`, Grok Build `grok`, GitHub Copilot CLI `vscode`. It leaves out Antigravity and Qwen Code: install their skills with `unity skill install antigravity` or `unity skill install qwen`, or with `unity setup antigravity` or `unity setup qwen`, which also installs the plugin. It never runs an agent's own CLI, installs no plugin and configures no MCP server, so it needs no git; `unity setup` does those. Each client's run is non-interactive: it updates a copy an older CLI wrote that nobody edited, and skips an edited copy or a skill a plugin already provides, unless you pass `--yes`. It prints one line per client (in a terminal, the machine-state block `unity setup all` prints, whose "Load skills" row names only `unity-cli` and `new-unity-project`), and one client failing doesn't stop the rest; the run then exits `6`, with a `SKILL_INSTALL_CLIENT_FAILED` entry per failed client in `errors`. `--format json` `data` holds every client's skill records, each carrying its `client`. With no client found it says so and exits `0`. It can't be combined with a client name or `--list` (exit `2`). The Unity CLI install scripts run `unity skill install --all --non-interactive` after installing the binary. + +Supported clients: `claude-code`, `claude-desktop`, `grok`, `cursor`, `windsurf`, `vscode`, `cline`, `codex`, `opencode`, `antigravity`, `qwen`. Each is written in the format that client expects, at its platform-correct location. Not every client supports both scopes — some are user-global only, others project-local only — and `--list` reports which, so check there rather than guessing. Without `--yes`, a run with no terminal (or with `--non-interactive`) cannot ask, so it skips an outdated copy, and a client whose plugin already provides the skill, and still exits `0`. That skip covers only the `unity-cli` skill, and the bundled `new-unity-project` skill when that plugin ships it too (its record says `plugin-provided` as well). A `new-unity-project` directory this CLI did not write is skipped the same way (`foreign-tree`) unless you name it with `--skill` or pass `--force`. The skills an installed plugin or the project's package ships are still installed beside it. With `--format json`, the run writes one envelope whose `data` holds a record per skill it considered, and each skipped skill's record carries `skipped: true` and a `skipReason` (`outdated`, `plugin-provided` or `foreign-tree`), so a script can tell a skip from an install. Pass `--yes` to overwrite. A `--local` install also picks up the skill the project's `com.unity.pipeline` package ships (`.claude/skills/unity-pipeline/` inside the package) and mirrors it beside `unity-cli` — e.g. into `.claude/skills/unity-pipeline/` for `claude-code`. A resolved package lives under `Library/PackageCache`, which no client's skill discovery reads, so this mirror is what makes the package's own skill loadable; a project without the package installs only the two embedded skills. `unity skill refresh` re-reads the mirrored copy from the project's package, and reports rather than deletes when the package is gone. The package skill never installs user-globally — it versions with the project's own package. -`opencode` installs into `~/.config/opencode/skills/unity-cli`, or `.opencode/skills/unity-cli` with `--local`, the folders OpenCode reads skills from. OpenCode also reads `~/.claude/skills`, `~/.agents/skills` and their project-local twins, so when one of those already holds the skill, `skill install opencode` warns and skips it the way it skips a plugin-provided copy (`skipReason` `plugin-provided`, with `conflict.source` `shared-directory`). Pass `--yes` to install a second copy anyway. +`opencode` installs into `~/.config/opencode/skills/unity-cli` (`$XDG_CONFIG_HOME/opencode/skills/unity-cli` when that is set), or `.opencode/skills/unity-cli` with `--local`, the folders OpenCode reads skills from. OpenCode also reads `~/.claude/skills`, `~/.agents/skills` and their project-local twins, so when one of those already holds the skill, `skill install opencode` warns and skips it the way it skips a plugin-provided copy (`skipReason` `plugin-provided`, with `conflict.source` `shared-directory`). Pass `--yes` to install a second copy anyway. `codex` installs a real skill directory (`~/.agents/skills/unity-cli`, or `.agents/skills/unity-cli` with `--local`), which is where Codex looks for skills. Earlier CLI versions instead merged the whole skill into a shared `AGENTS.md`, which Codex reads at the start of every session, so the entire skill was charged to sessions that had nothing to do with Unity. Installing or refreshing now removes that leftover block and reports the file it cleaned. If it finds more than one such block it leaves the file alone and says so, rather than guessing which block is Unity's. @@ -306,12 +326,31 @@ unity skill refresh --yes unity skill refresh --dry-run ``` -Every install is tracked, so `unity skill refresh` re-renders all of them at once and drops tracking for any whose location has since disappeared. **Run it after `unity self-update`** — the embedded skill ships with the binary, so an updated CLI leaves previously-installed copies stale until they're refreshed. +Every install is tracked, so `unity skill refresh` re-renders all of them at once and drops tracking for any whose location has since disappeared. **Run it after `unity self-update`** — the embedded skill ships with the binary, so an updated CLI leaves previously-installed copies stale until they're refreshed. For one client, without prompting, `unity skill sync ` does the same and is what the agent plugin runs. Two safety behaviors: writing through a symlink is refused rather than followed, and `--local` from your home directory warns first, since for most clients that either duplicates the global install or writes somewhere the client never reads. If you last installed the Codex skill with an older CLI, `unity skill refresh` migrates it: it writes the skill directory, strips the old `AGENTS.md` block, and replaces the tracking entry. +#### skill sync — keep one client's skills current, without prompting + +`unity skill sync [client]` installs or updates the Unity skills for one AI client and never asks. It is what Unity's agent plugin runs when a session starts, so the plugin itself ships no skills. + +```bash +unity skill sync # the client of the agent running the command +unity skill sync claude-code # name the client +unity skill sync opencode --format json +unity skill sync --hook # a session-start hook document; always exits 0 +unity skill sync --dry-run # which skills would change, nothing written +``` + +- **It only touches copies it manages.** A copy you edited, a disabled skill, and a skill a client plugin already ships are left alone, exactly as a non-interactive `unity skill install` leaves them. There is no `--yes` or `--force`. +- **It is cheap when nothing changed.** After a run that reached the pinned repository, it records this CLI's version and pin for the client. Until either changes, later runs answer from that record with no download. A run after `unity self-update` or a new pin syncs again. An offline run installs what it can and tries again next time. +- **The client.** With none named, it reads the agent from its environment: Claude Code, GitHub Copilot (the `vscode` client), Codex, Grok, OpenCode, Cursor, Windsurf, Cline, Antigravity and Qwen Code. When it can't tell, it exits `2` asking for a client name (`--hook` prints that as context and exits `0`). +- **Output.** `--format json` writes `data` with `client`, `changed` (the skill names it installed or updated), `upToDate`, `offline`, `dryRun` and `message`, the one line an agent should read, or `null`. `--hook` prints nothing when nothing changed, else a JSON document with that line under `additionalContext` and `hookSpecificOutput.additionalContext` (`hookEventName` `SessionStart`), the shapes Claude Code, VS Code and the GitHub Copilot CLI read. A failure under `--hook` becomes one line of context, never a failed session start. + +When an agent without the plugin's hook runs `unity` and the Unity skills it installed are older than this CLI's pin, the CLI says so once a day, naming `unity skill sync ` (an `AGENT_SKILLS_OUTDATED` entry in the envelope's `notifications` under `--format json`, with `remediation.requiresUserApproval: false`, since the sync never overwrites an edited copy). `UNITY_NO_AGENT_HINTS` turns it off with the other agent tips. + #### skill show — read the skill without installing it `unity skill show` prints the embedded skill straight to stdout — no prompt, no file written, no network call — for a client (or an agent already reading this skill) that only wants the content, not a filesystem write it may not be permitted to make. @@ -585,6 +624,13 @@ unity command script recompile_status --result-only In the human table, `script recompile`, `script recompile_status`, `test status` and `test run` results render as short readable text in the Result column instead of a JSON blob; `--format json` / `ndjson` output is unchanged. +**A newer Editor formats and streams a named command's output itself.** When the project's `com.unity.pipeline` supports it, `unity command ` prints each record as the Editor produces it and exits with the command's own exit code (`1` for a failed command). The default output is a table, or one `name: value` line per field for a single result. Pass `--plain` for one tab-separated line per record with no header, or `--json` for the command's result as JSON, without the CLI's `command`/`parameters`/`target` envelope. Pick fields with `--fields name,path`. `--format tsv` and `--format json` ask for the same two formats. The command runs like a remote process: piped stdin reaches it (`cat paths.txt | unity command `), for a command that reads its input, Ctrl-C asks it to stop and waits for it (a second Ctrl-C stops waiting), and a busy Editor's refusal comes back as its exit code (`102`) rather than being waited out. An empty list prints nothing. `--format ndjson`, `--result-only`, `--detach`, `--quiet` (except with json) and older Editors keep the rendering above. + +```bash +unity command editor status --fields playMode,compiling +unity command editor status --plain --fields playMode +``` + **`--caller [label]` and `--skill [name]` are analytics labels, not behavior.** `--caller` records what invoked the CLI and `--skill` records the agent skill driving the invocation; neither changes what the command does. A caller the CLI recognizes is recorded as given, anything else as `other`. They exist so an integration can identify itself — an agent running this skill has no reason to set them by hand, and both are inert when analytics are off. #### Querying the command list @@ -722,21 +768,30 @@ unity job wait --poll-interval 2000 #### Modal dialogs -Requires Unity 6000.7+ and a `com.unity.pipeline` that has the dialog commands. A native modal dialog (`Save changes?`, a build or import prompt, an `EditorUtility.DisplayDialog` call, …) blocks the Editor's main thread — which is exactly why `unity command` and `unity job` can be rejected or time out while one is open. Three ordinary pipeline commands see it and clear it without touching the keyboard or mouse, and they keep working while a dialog is open: +Needs an Editor that reports its dialogs to the CLI. A native modal dialog (`Save changes?`, a build or import prompt, an `EditorUtility.DisplayDialog` call, …) blocks the Editor's main thread — which is exactly why `unity command` and `unity job` can be rejected or time out while one is open. The Editor answers two commands itself, outside the main thread, so they work while a dialog is open, and in Safe Mode: ```bash -unity command editor.dialog list # see what's open: id, title, message, buttons -unity command editor.dialog answer