Loading...
Loading...
Use when adding, removing, upgrading, or discovering Unity (UPM) packages programmatically from outside the Editor — headless or CI package installs via the C# UnityEditor.PackageManager.Client API, verifying package ids/versions against the Unity registry, or choosing which packages a game needs by genre, platform, and monetization. The Unity CLI does not manage UPM packages, so this skill covers that gap. Triggers on "install a Unity package", "add com.unity.*", "set up packages headless/CI", "which packages for a <genre> game".
npx skill4agent add unity-technologies/skills unity-package-managementUnityEditor.PackageManager.ClientPackages/manifest.jsonunity-cli-quitunity runClient.AddClient.AddAndRemoveRequestEditorApplication.updatewhile (!req.IsCompleted)-executeMethodunity run-quitunity-cli-quit-batchmode-quitEditorApplication.updateEditorApplication.Exit(code)Assets/Editor/ProjectBootstrap/PackageInstaller.csEditor/UnityEditorusing System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace ProjectBootstrap
{
// Installs (and optionally removes) a fixed set of packages via the PackageManager
// Client API, headless-safe.
public static class PackageInstaller
{
// EDIT this list to match the package selection (see references/select-packages.md).
static readonly string[] PackagesToAdd =
{
"com.unity.inputsystem",
"com.unity.cinemachine",
"com.unity.render-pipelines.universal",
// "com.unity.package@1.2.3" // pin a version with @ when a minimum is required
};
// Optionally drop packages in the same resolution pass (e.g. a template default you don't want).
static readonly string[] PackagesToRemove = { };
const double TimeoutSeconds = 600; // UPM resolution + downloads can be slow
static AddAndRemoveRequest _request;
static double _deadline;
// Invoke with: -executeMethod ProjectBootstrap.PackageInstaller.Install (NO -quit)
public static void Install()
{
if (PackagesToAdd.Length == 0 && PackagesToRemove.Length == 0)
{
Debug.Log("[PackageInstaller] Nothing to do.");
EditorApplication.Exit(0);
return;
}
Debug.Log($"[PackageInstaller] Adding: {string.Join(", ", PackagesToAdd)}");
_request = Client.AddAndRemove(packagesToAdd: PackagesToAdd, packagesToRemove: PackagesToRemove);
_deadline = EditorApplication.timeSinceStartup + TimeoutSeconds;
EditorApplication.update += Poll;
}
static void Poll()
{
if (_request == null) return;
if (!_request.IsCompleted)
{
if (EditorApplication.timeSinceStartup > _deadline)
{
EditorApplication.update -= Poll;
Debug.LogError("[PackageInstaller] Timed out waiting for UPM.");
EditorApplication.Exit(2);
}
return;
}
EditorApplication.update -= Poll;
if (_request.Status == StatusCode.Success)
{
var names = _request.Result.Select(p => $"{p.name}@{p.version}");
Debug.Log($"[PackageInstaller] Resolved: {string.Join(", ", names)}");
EditorApplication.Exit(0);
}
else
{
Debug.LogError($"[PackageInstaller] Failed: {_request.Error?.message}");
EditorApplication.Exit(1);
}
}
}
}AddAndRemoveClient.AddPackagesToAddPackagesToRemove@<version>com.unity.cinemachine@2.9.7Client.SearchAll()Client.Search("<id>")ExitAssets/Editor/ProjectBootstrap/PackageSearch.csusing System.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace ProjectBootstrap
{
public static class PackageSearch
{
const double TimeoutSeconds = 120;
static SearchRequest _request;
static double _deadline;
// Invoke with: -executeMethod ProjectBootstrap.PackageSearch.SearchAll (NO -quit)
public static void SearchAll()
{
_request = Client.SearchAll(); // or Client.Search("com.unity.cinemachine")
_deadline = EditorApplication.timeSinceStartup + TimeoutSeconds;
EditorApplication.update += Poll;
}
static void Poll()
{
if (_request == null) return;
if (!_request.IsCompleted)
{
if (EditorApplication.timeSinceStartup > _deadline)
{
EditorApplication.update -= Poll;
Debug.LogError("[PackageSearch] Timed out.");
EditorApplication.Exit(2);
}
return;
}
EditorApplication.update -= Poll;
if (_request.Status == StatusCode.Success)
{
foreach (var p in _request.Result.OrderBy(p => p.name))
Debug.Log($"[PackageSearch] {p.name}@{p.versions.latestCompatible} {p.displayName}");
Debug.Log($"[PackageSearch] {_request.Result.Length} packages found.");
EditorApplication.Exit(0);
}
else
{
Debug.LogError($"[PackageSearch] Failed: {_request.Error?.message}");
EditorApplication.Exit(1);
}
}
}
}_request.ResultPackageInfo[]namedisplayNamedescriptionversions.latest.latestCompatible.all-quitEditorApplication.Exit-quitVERSION="<version>" # e.g. 6000.0.47f1 (or an installed version)
PROJECT="<project-path>"
METHOD="ProjectBootstrap.PackageInstaller.Install" # or ...PackageSearch.SearchAll
# Install directory of that editor (Hub layout), via the unity CLI
ED=$(unity editors path "$VERSION" --format json | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['path'])")
# Resolve the executable per-OS (handles both "dir containing Unity.app" and the ".app" itself)
case "$(uname)" in
Darwin) if [ -d "$ED/Unity.app" ]; then UNITY_BIN="$ED/Unity.app/Contents/MacOS/Unity";
elif [[ "$ED" == *.app ]]; then UNITY_BIN="$ED/Contents/MacOS/Unity";
else UNITY_BIN="$ED/Unity"; fi ;;
Linux) UNITY_BIN="$ED/Unity" ;;
*) UNITY_BIN="$ED/Unity.exe" ;; # Windows (Git Bash / MSYS); use Unity.exe in PowerShell
esac
"$UNITY_BIN" -batchmode -projectPath "$PROJECT" -executeMethod "$METHOD" -logFile -
echo "Exit code: $?" # 0 = success, 1 = UPM error, 2 = timeout-logFile -[PackageInstaller][PackageSearch]unity editors pathunity editors --installed --format json# Every requested id should appear as a dependency
cat "<project-path>/Packages/manifest.json"0manifest.json_request.Error.message[PackageInstaller]-logFile -unity logs.meta.cs.meta.cs.metaunity open "<project-path>"unity run-quitEditorApplication.ExitAssets/Editor/ProjectBootstrap/ProjectSaver.csusing UnityEditor;
using UnityEngine;
namespace ProjectBootstrap
{
public static class ProjectSaver
{
// Invoke with: -executeMethod ProjectBootstrap.ProjectSaver.SaveAll
public static void SaveAll()
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
AssetDatabase.SaveAssets();
Debug.Log("[ProjectSaver] Assets imported and saved.");
EditorApplication.Exit(0);
}
}
}unity run "<project-path>" --editor-version <version> \
-- -executeMethod ProjectBootstrap.ProjectSaver.SaveAllAssets/Editor/ProjectBootstrap/Editor/UnityEditorcom.unity.purchasingcom.unity.services.levelplay