diff --git a/archive/yucchiy/UniCli.txt b/archive/yucchiy/UniCli.txt new file mode 100644 index 00000000..6a2d0f3d --- /dev/null +++ b/archive/yucchiy/UniCli.txt @@ -0,0 +1,45342 @@ +Project Path: arc_yucchiy_UniCli_5lhjb6g4 + +Source Tree: + +```txt +arc_yucchiy_UniCli_5lhjb6g4 +├── CLAUDE.md +├── LICENSE +├── README.md +├── UniCli.sln +├── doc +│ ├── command-naming-guidelines.md +│ └── commands.md +├── samples +│ ├── UniCli.Samples.Unity2022LTS +│ │ ├── Assets +│ │ │ ├── Scenes +│ │ │ │ ├── SampleScene.unity +│ │ │ │ └── SampleScene.unity.meta +│ │ │ └── Scenes.meta +│ │ ├── Packages +│ │ │ ├── manifest.json +│ │ │ └── packages-lock.json +│ │ └── ProjectSettings +│ │ ├── AudioManager.asset +│ │ ├── BuildMagic +│ │ │ ├── Dev.json +│ │ │ └── Prd.json +│ │ ├── ClusterInputManager.asset +│ │ ├── DynamicsManager.asset +│ │ ├── EditorBuildSettings.asset +│ │ ├── EditorSettings.asset +│ │ ├── GraphicsSettings.asset +│ │ ├── InputManager.asset +│ │ ├── MemorySettings.asset +│ │ ├── NavMeshAreas.asset +│ │ ├── PackageManagerSettings.asset +│ │ ├── Packages +│ │ │ └── com.unity.testtools.codecoverage +│ │ │ └── Settings.json +│ │ ├── Physics2DSettings.asset +│ │ ├── PresetManager.asset +│ │ ├── ProjectSettings.asset +│ │ ├── ProjectVersion.txt +│ │ ├── QualitySettings.asset +│ │ ├── TagManager.asset +│ │ ├── TimeManager.asset +│ │ ├── UnityConnectSettings.asset +│ │ ├── VFXManager.asset +│ │ ├── VersionControlSettings.asset +│ │ └── XRSettings.asset +│ └── UniCli.Samples.Unity6LTS +│ ├── Assets +│ │ ├── InputSystem_Actions.inputactions +│ │ ├── InputSystem_Actions.inputactions.meta +│ │ ├── Readme.asset +│ │ ├── Readme.asset.meta +│ │ ├── Scenes +│ │ │ ├── SampleScene.unity +│ │ │ └── SampleScene.unity.meta +│ │ ├── Scenes.meta +│ │ ├── Settings +│ │ │ ├── DefaultVolumeProfile.asset +│ │ │ ├── DefaultVolumeProfile.asset.meta +│ │ │ ├── Mobile_RPAsset.asset +│ │ │ ├── Mobile_RPAsset.asset.meta +│ │ │ ├── Mobile_Renderer.asset +│ │ │ ├── Mobile_Renderer.asset.meta +│ │ │ ├── PC_RPAsset.asset +│ │ │ ├── PC_RPAsset.asset.meta +│ │ │ ├── PC_Renderer.asset +│ │ │ ├── PC_Renderer.asset.meta +│ │ │ ├── SampleSceneProfile.asset +│ │ │ ├── SampleSceneProfile.asset.meta +│ │ │ ├── UniversalRenderPipelineGlobalSettings.asset +│ │ │ └── UniversalRenderPipelineGlobalSettings.asset.meta +│ │ ├── Settings.meta +│ │ ├── TutorialInfo +│ │ │ ├── Icons +│ │ │ │ └── URP.png.meta +│ │ │ ├── Icons.meta +│ │ │ ├── Layout.wlt +│ │ │ ├── Layout.wlt.meta +│ │ │ ├── Scripts +│ │ │ │ ├── Editor +│ │ │ │ │ ├── ReadmeEditor.cs +│ │ │ │ │ └── ReadmeEditor.cs.meta +│ │ │ │ ├── Editor.meta +│ │ │ │ ├── Readme.cs +│ │ │ │ └── Readme.cs.meta +│ │ │ └── Scripts.meta +│ │ └── TutorialInfo.meta +│ ├── Packages +│ │ ├── manifest.json +│ │ └── packages-lock.json +│ └── ProjectSettings +│ ├── AudioManager.asset +│ ├── ClusterInputManager.asset +│ ├── DynamicsManager.asset +│ ├── EditorBuildSettings.asset +│ ├── EditorSettings.asset +│ ├── GraphicsSettings.asset +│ ├── InputManager.asset +│ ├── MemorySettings.asset +│ ├── MultiplayerManager.asset +│ ├── NavMeshAreas.asset +│ ├── PackageManagerSettings.asset +│ ├── Physics2DSettings.asset +│ ├── PresetManager.asset +│ ├── ProjectSettings.asset +│ ├── ProjectVersion.txt +│ ├── QualitySettings.asset +│ ├── ShaderGraphSettings.asset +│ ├── TagManager.asset +│ ├── TimeManager.asset +│ ├── URPProjectSettings.asset +│ ├── UnityConnectSettings.asset +│ ├── VFXManager.asset +│ ├── VersionControlSettings.asset +│ └── XRSettings.asset +├── src +│ ├── UniCli.Client +│ │ ├── ArgumentParser.cs +│ │ ├── CliResult.cs +│ │ ├── CommandExecutor.cs +│ │ ├── Commands +│ │ │ ├── Commands.Check.cs +│ │ │ ├── Commands.Commands.cs +│ │ │ ├── Commands.Complete.cs +│ │ │ ├── Commands.Completions.cs +│ │ │ ├── Commands.Exec.cs +│ │ │ ├── Commands.Install.cs +│ │ │ └── Commands.Status.cs +│ │ ├── CompletionCache.cs +│ │ ├── FocusScope.cs +│ │ ├── IProcessActivator.cs +│ │ ├── MacOSProcessActivator.cs +│ │ ├── ManifestEditor.cs +│ │ ├── NullProcessActivator.cs +│ │ ├── OutputWriter.cs +│ │ ├── PipeClient.cs +│ │ ├── Program.cs +│ │ ├── ProjectIdentifier.cs +│ │ ├── Protocol +│ │ │ ├── CommandInfo.cs +│ │ │ ├── CommandRequest.cs +│ │ │ ├── CommandResponse.cs +│ │ │ ├── ProtocolConstants.cs +│ │ │ ├── Result.cs +│ │ │ └── Unit.cs +│ │ ├── ProtocolJsonContext.cs +│ │ ├── UniCli.Client.csproj +│ │ ├── UnityLauncher.cs +│ │ ├── UnityProcessActivator.cs +│ │ ├── VersionInfo.cs +│ │ └── WindowsProcessActivator.cs +│ ├── UniCli.Client.Tests +│ │ ├── ArgumentParserTests.cs +│ │ ├── CommandExecutorTests.cs +│ │ ├── ProjectIdentifierTests.cs +│ │ ├── UniCli.Client.Tests.csproj +│ │ └── UnityLauncherTests.cs +│ ├── UniCli.Protocol +│ │ ├── CommandInfo.cs +│ │ ├── CommandRequest.cs +│ │ ├── CommandResponse.cs +│ │ ├── ProtocolConstants.cs +│ │ ├── Result.cs +│ │ ├── UniCli.Protocol.csproj +│ │ └── Unit.cs +│ ├── UniCli.SourceGenerator +│ │ ├── Analysis +│ │ │ ├── InstanceTypeAnalyzer.cs +│ │ │ ├── InstanceTypeInfo.cs +│ │ │ ├── SettingsTypeAnalyzer.cs +│ │ │ └── TypeSerializabilityChecker.cs +│ │ ├── Emitters +│ │ │ ├── EmitHelper.cs +│ │ │ ├── GetCommandEmitter.cs +│ │ │ └── InstanceInspectCommandEmitter.cs +│ │ ├── SettingsCommandGenerator.cs +│ │ └── UniCli.SourceGenerator.csproj +│ └── UniCli.Unity +│ ├── Assets +│ │ ├── NuGet.config +│ │ ├── NuGet.config.meta +│ │ ├── Packages.meta +│ │ ├── Scenes +│ │ │ ├── SampleScene.unity +│ │ │ └── SampleScene.unity.meta +│ │ ├── Scenes.meta +│ │ ├── packages.config +│ │ └── packages.config.meta +│ ├── Packages +│ │ ├── com.yucchiy.unicli-server +│ │ │ ├── Editor +│ │ │ │ ├── AssemblyInfo.cs +│ │ │ │ ├── AssemblyInfo.cs.meta +│ │ │ │ ├── Build +│ │ │ │ │ ├── RemoteLinkerProcessor.cs +│ │ │ │ │ └── RemoteLinkerProcessor.cs.meta +│ │ │ │ ├── Build.meta +│ │ │ │ ├── CommandDispatcher.cs +│ │ │ │ ├── CommandDispatcher.cs.meta +│ │ │ │ ├── CoreServiceInstaller.cs +│ │ │ │ ├── CoreServiceInstaller.cs.meta +│ │ │ │ ├── EditorLogManager.cs +│ │ │ │ ├── EditorLogManager.cs.meta +│ │ │ │ ├── EditorStateGuard.cs +│ │ │ │ ├── EditorStateGuard.cs.meta +│ │ │ │ ├── GameObjectResolver.cs +│ │ │ │ ├── GameObjectResolver.cs.meta +│ │ │ │ ├── GenerateCommandsAttribute.cs +│ │ │ │ ├── GenerateCommandsAttribute.cs.meta +│ │ │ │ ├── Generators +│ │ │ │ │ └── UniCli.SourceGenerator.dll.meta +│ │ │ │ ├── Generators.meta +│ │ │ │ ├── Handlers +│ │ │ │ │ ├── Animator +│ │ │ │ │ │ ├── CrossFadeHandler.cs +│ │ │ │ │ │ ├── CrossFadeHandler.cs.meta +│ │ │ │ │ │ ├── InspectHandler.cs +│ │ │ │ │ │ ├── InspectHandler.cs.meta +│ │ │ │ │ │ ├── PlayHandler.cs +│ │ │ │ │ │ ├── PlayHandler.cs.meta +│ │ │ │ │ │ ├── SetControllerHandler.cs +│ │ │ │ │ │ ├── SetControllerHandler.cs.meta +│ │ │ │ │ │ ├── SetParameterHandler.cs +│ │ │ │ │ │ └── SetParameterHandler.cs.meta +│ │ │ │ │ ├── Animator.meta +│ │ │ │ │ ├── AnimatorController +│ │ │ │ │ │ ├── AddParameterHandler.cs +│ │ │ │ │ │ ├── AddParameterHandler.cs.meta +│ │ │ │ │ │ ├── AddStateHandler.cs +│ │ │ │ │ │ ├── AddStateHandler.cs.meta +│ │ │ │ │ │ ├── AddTransitionConditionHandler.cs +│ │ │ │ │ │ ├── AddTransitionConditionHandler.cs.meta +│ │ │ │ │ │ ├── AddTransitionHandler.cs +│ │ │ │ │ │ ├── AddTransitionHandler.cs.meta +│ │ │ │ │ │ ├── AnimatorControllerResolver.cs +│ │ │ │ │ │ ├── AnimatorControllerResolver.cs.meta +│ │ │ │ │ │ ├── CreateHandler.cs +│ │ │ │ │ │ ├── CreateHandler.cs.meta +│ │ │ │ │ │ ├── InspectHandler.cs +│ │ │ │ │ │ ├── InspectHandler.cs.meta +│ │ │ │ │ │ ├── RemoveParameterHandler.cs +│ │ │ │ │ │ └── RemoveParameterHandler.cs.meta +│ │ │ │ │ ├── AnimatorController.meta +│ │ │ │ │ ├── AssemblyDefinition +│ │ │ │ │ │ ├── AssemblyDefinitionAddReferenceHandler.cs +│ │ │ │ │ │ ├── AssemblyDefinitionAddReferenceHandler.cs.meta +│ │ │ │ │ │ ├── AssemblyDefinitionCreateHandler.cs +│ │ │ │ │ │ ├── AssemblyDefinitionCreateHandler.cs.meta +│ │ │ │ │ │ ├── AssemblyDefinitionData.cs +│ │ │ │ │ │ ├── AssemblyDefinitionData.cs.meta +│ │ │ │ │ │ ├── AssemblyDefinitionGetHandler.cs +│ │ │ │ │ │ ├── AssemblyDefinitionGetHandler.cs.meta +│ │ │ │ │ │ ├── AssemblyDefinitionListHandler.cs +│ │ │ │ │ │ ├── AssemblyDefinitionListHandler.cs.meta +│ │ │ │ │ │ ├── AssemblyDefinitionRemoveReferenceHandler.cs +│ │ │ │ │ │ └── AssemblyDefinitionRemoveReferenceHandler.cs.meta +│ │ │ │ │ ├── AssemblyDefinition.meta +│ │ │ │ │ ├── AssetDatabase +│ │ │ │ │ │ ├── AssetDeleteHandler.cs +│ │ │ │ │ │ ├── AssetDeleteHandler.cs.meta +│ │ │ │ │ │ ├── AssetFindHandler.cs +│ │ │ │ │ │ ├── AssetFindHandler.cs.meta +│ │ │ │ │ │ ├── AssetGetPathHandler.cs +│ │ │ │ │ │ ├── AssetGetPathHandler.cs.meta +│ │ │ │ │ │ ├── AssetImportHandler.cs +│ │ │ │ │ │ └── AssetImportHandler.cs.meta +│ │ │ │ │ ├── AssetDatabase.meta +│ │ │ │ │ ├── BuildHandler.cs +│ │ │ │ │ ├── BuildHandler.cs.meta +│ │ │ │ │ ├── BuildMagic +│ │ │ │ │ │ ├── BuildMagicApplyHandler.cs +│ │ │ │ │ │ ├── BuildMagicApplyHandler.cs.meta +│ │ │ │ │ │ ├── BuildMagicInspectHandler.cs +│ │ │ │ │ │ ├── BuildMagicInspectHandler.cs.meta +│ │ │ │ │ │ ├── BuildMagicListHandler.cs +│ │ │ │ │ │ ├── BuildMagicListHandler.cs.meta +│ │ │ │ │ │ ├── UniCli.Server.Editor.BuildMagic.asmdef +│ │ │ │ │ │ └── UniCli.Server.Editor.BuildMagic.asmdef.meta +│ │ │ │ │ ├── BuildMagic.meta +│ │ │ │ │ ├── BuildProfile +│ │ │ │ │ │ ├── BuildProfileGetActiveHandler.cs +│ │ │ │ │ │ ├── BuildProfileGetActiveHandler.cs.meta +│ │ │ │ │ │ ├── BuildProfileInspectHandler.cs +│ │ │ │ │ │ ├── BuildProfileInspectHandler.cs.meta +│ │ │ │ │ │ ├── BuildProfileListHandler.cs +│ │ │ │ │ │ ├── BuildProfileListHandler.cs.meta +│ │ │ │ │ │ ├── BuildProfileSetActiveHandler.cs +│ │ │ │ │ │ └── BuildProfileSetActiveHandler.cs.meta +│ │ │ │ │ ├── BuildProfile.meta +│ │ │ │ │ ├── BuildTargetGetActiveHandler.cs +│ │ │ │ │ ├── BuildTargetGetActiveHandler.cs.meta +│ │ │ │ │ ├── BuildTargetSwitchHandler.cs +│ │ │ │ │ ├── BuildTargetSwitchHandler.cs.meta +│ │ │ │ │ ├── CommandFailedException.cs +│ │ │ │ │ ├── CommandFailedException.cs.meta +│ │ │ │ │ ├── CommandListHandler.cs +│ │ │ │ │ ├── CommandListHandler.cs.meta +│ │ │ │ │ ├── CompileHandler.cs +│ │ │ │ │ ├── CompileHandler.cs.meta +│ │ │ │ │ ├── CompilePlayerHandler.cs +│ │ │ │ │ ├── CompilePlayerHandler.cs.meta +│ │ │ │ │ ├── Component +│ │ │ │ │ │ ├── SetPropertyHandler.cs +│ │ │ │ │ │ └── SetPropertyHandler.cs.meta +│ │ │ │ │ ├── Component.meta +│ │ │ │ │ ├── Connection +│ │ │ │ │ │ ├── ConnectionConnectHandler.cs +│ │ │ │ │ │ ├── ConnectionConnectHandler.cs.meta +│ │ │ │ │ │ ├── ConnectionListHandler.cs +│ │ │ │ │ │ ├── ConnectionListHandler.cs.meta +│ │ │ │ │ │ ├── ConnectionStatusHandler.cs +│ │ │ │ │ │ └── ConnectionStatusHandler.cs.meta +│ │ │ │ │ ├── Connection.meta +│ │ │ │ │ ├── Console +│ │ │ │ │ │ ├── ClearConsoleHandler.cs +│ │ │ │ │ │ ├── ClearConsoleHandler.cs.meta +│ │ │ │ │ │ ├── EditorLogHandler.cs +│ │ │ │ │ │ └── EditorLogHandler.cs.meta +│ │ │ │ │ ├── Console.meta +│ │ │ │ │ ├── EvalHandler.cs +│ │ │ │ │ ├── EvalHandler.cs.meta +│ │ │ │ │ ├── GameObject +│ │ │ │ │ │ ├── AddComponentHandler.cs +│ │ │ │ │ │ ├── AddComponentHandler.cs.meta +│ │ │ │ │ │ ├── CreateGameObjectHandler.cs +│ │ │ │ │ │ ├── CreateGameObjectHandler.cs.meta +│ │ │ │ │ │ ├── CreatePrimitiveHandler.cs +│ │ │ │ │ │ ├── CreatePrimitiveHandler.cs.meta +│ │ │ │ │ │ ├── DestroyGameObjectHandler.cs +│ │ │ │ │ │ ├── DestroyGameObjectHandler.cs.meta +│ │ │ │ │ │ ├── DuplicateGameObjectHandler.cs +│ │ │ │ │ │ ├── DuplicateGameObjectHandler.cs.meta +│ │ │ │ │ │ ├── FindGameObjectsHandler.cs +│ │ │ │ │ │ ├── FindGameObjectsHandler.cs.meta +│ │ │ │ │ │ ├── GetComponentsHandler.cs +│ │ │ │ │ │ ├── GetComponentsHandler.cs.meta +│ │ │ │ │ │ ├── GetHierarchyHandler.cs +│ │ │ │ │ │ ├── GetHierarchyHandler.cs.meta +│ │ │ │ │ │ ├── RemoveComponentHandler.cs +│ │ │ │ │ │ ├── RemoveComponentHandler.cs.meta +│ │ │ │ │ │ ├── RenameGameObjectHandler.cs +│ │ │ │ │ │ ├── RenameGameObjectHandler.cs.meta +│ │ │ │ │ │ ├── SetActiveHandler.cs +│ │ │ │ │ │ ├── SetActiveHandler.cs.meta +│ │ │ │ │ │ ├── SetParentHandler.cs +│ │ │ │ │ │ ├── SetParentHandler.cs.meta +│ │ │ │ │ │ ├── SetTransformHandler.cs +│ │ │ │ │ │ └── SetTransformHandler.cs.meta +│ │ │ │ │ ├── GameObject.meta +│ │ │ │ │ ├── ICommandHandler.cs +│ │ │ │ │ ├── ICommandHandler.cs.meta +│ │ │ │ │ ├── Material +│ │ │ │ │ │ ├── CreateMaterialHandler.cs +│ │ │ │ │ │ ├── CreateMaterialHandler.cs.meta +│ │ │ │ │ │ ├── MaterialGetColorHandler.cs +│ │ │ │ │ │ ├── MaterialGetColorHandler.cs.meta +│ │ │ │ │ │ ├── MaterialGetFloatHandler.cs +│ │ │ │ │ │ ├── MaterialGetFloatHandler.cs.meta +│ │ │ │ │ │ ├── MaterialSetColorHandler.cs +│ │ │ │ │ │ ├── MaterialSetColorHandler.cs.meta +│ │ │ │ │ │ ├── MaterialSetFloatHandler.cs +│ │ │ │ │ │ └── MaterialSetFloatHandler.cs.meta +│ │ │ │ │ ├── Material.meta +│ │ │ │ │ ├── Menu +│ │ │ │ │ │ ├── ExecuteMenuItemHandler.cs +│ │ │ │ │ │ ├── ExecuteMenuItemHandler.cs.meta +│ │ │ │ │ │ ├── GetMenuItemsHandler.cs +│ │ │ │ │ │ └── GetMenuItemsHandler.cs.meta +│ │ │ │ │ ├── Menu.meta +│ │ │ │ │ ├── Module +│ │ │ │ │ │ ├── ModuleDisableHandler.cs +│ │ │ │ │ │ ├── ModuleDisableHandler.cs.meta +│ │ │ │ │ │ ├── ModuleEnableHandler.cs +│ │ │ │ │ │ ├── ModuleEnableHandler.cs.meta +│ │ │ │ │ │ ├── ModuleListHandler.cs +│ │ │ │ │ │ └── ModuleListHandler.cs.meta +│ │ │ │ │ ├── Module.meta +│ │ │ │ │ ├── NuGetForUnity +│ │ │ │ │ │ ├── NuGetAddSourceHandler.cs +│ │ │ │ │ │ ├── NuGetAddSourceHandler.cs.meta +│ │ │ │ │ │ ├── NuGetConfigHelper.cs +│ │ │ │ │ │ ├── NuGetConfigHelper.cs.meta +│ │ │ │ │ │ ├── NuGetInstallHandler.cs +│ │ │ │ │ │ ├── NuGetInstallHandler.cs.meta +│ │ │ │ │ │ ├── NuGetListHandler.cs +│ │ │ │ │ │ ├── NuGetListHandler.cs.meta +│ │ │ │ │ │ ├── NuGetListSourcesHandler.cs +│ │ │ │ │ │ ├── NuGetListSourcesHandler.cs.meta +│ │ │ │ │ │ ├── NuGetRemoveSourceHandler.cs +│ │ │ │ │ │ ├── NuGetRemoveSourceHandler.cs.meta +│ │ │ │ │ │ ├── NuGetRestoreHandler.cs +│ │ │ │ │ │ ├── NuGetRestoreHandler.cs.meta +│ │ │ │ │ │ ├── NuGetUninstallHandler.cs +│ │ │ │ │ │ ├── NuGetUninstallHandler.cs.meta +│ │ │ │ │ │ ├── UniCli.Server.Editor.NuGetForUnity.asmdef +│ │ │ │ │ │ └── UniCli.Server.Editor.NuGetForUnity.asmdef.meta +│ │ │ │ │ ├── NuGetForUnity.meta +│ │ │ │ │ ├── PackageManager +│ │ │ │ │ │ ├── PackageManagerAddHandler.cs +│ │ │ │ │ │ ├── PackageManagerAddHandler.cs.meta +│ │ │ │ │ │ ├── PackageManagerGetInfoHandler.cs +│ │ │ │ │ │ ├── PackageManagerGetInfoHandler.cs.meta +│ │ │ │ │ │ ├── PackageManagerListHandler.cs +│ │ │ │ │ │ ├── PackageManagerListHandler.cs.meta +│ │ │ │ │ │ ├── PackageManagerRemoveHandler.cs +│ │ │ │ │ │ ├── PackageManagerRemoveHandler.cs.meta +│ │ │ │ │ │ ├── PackageManagerRequestHelper.cs +│ │ │ │ │ │ ├── PackageManagerRequestHelper.cs.meta +│ │ │ │ │ │ ├── PackageManagerSearchHandler.cs +│ │ │ │ │ │ ├── PackageManagerSearchHandler.cs.meta +│ │ │ │ │ │ ├── PackageManagerUpdateHandler.cs +│ │ │ │ │ │ └── PackageManagerUpdateHandler.cs.meta +│ │ │ │ │ ├── PackageManager.meta +│ │ │ │ │ ├── PlayMode +│ │ │ │ │ │ ├── PlayModeHandler.cs +│ │ │ │ │ │ └── PlayModeHandler.cs.meta +│ │ │ │ │ ├── PlayMode.meta +│ │ │ │ │ ├── Prefab +│ │ │ │ │ │ ├── PrefabApplyHandler.cs +│ │ │ │ │ │ ├── PrefabApplyHandler.cs.meta +│ │ │ │ │ │ ├── PrefabGetStatusHandler.cs +│ │ │ │ │ │ ├── PrefabGetStatusHandler.cs.meta +│ │ │ │ │ │ ├── PrefabInstantiateHandler.cs +│ │ │ │ │ │ ├── PrefabInstantiateHandler.cs.meta +│ │ │ │ │ │ ├── PrefabSaveHandler.cs +│ │ │ │ │ │ ├── PrefabSaveHandler.cs.meta +│ │ │ │ │ │ ├── PrefabUnpackHandler.cs +│ │ │ │ │ │ └── PrefabUnpackHandler.cs.meta +│ │ │ │ │ ├── Prefab.meta +│ │ │ │ │ ├── Profiler +│ │ │ │ │ │ ├── ProfilerAnalyzeFramesHandler.cs +│ │ │ │ │ │ ├── ProfilerAnalyzeFramesHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerFindSpikesHandler.cs +│ │ │ │ │ │ ├── ProfilerFindSpikesHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerFrameHelper.cs +│ │ │ │ │ │ ├── ProfilerFrameHelper.cs.meta +│ │ │ │ │ │ ├── ProfilerGetFrameDataHandler.cs +│ │ │ │ │ │ ├── ProfilerGetFrameDataHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerInspectHandler.cs +│ │ │ │ │ │ ├── ProfilerInspectHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerLoadProfileHandler.cs +│ │ │ │ │ │ ├── ProfilerLoadProfileHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerSaveProfileHandler.cs +│ │ │ │ │ │ ├── ProfilerSaveProfileHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerStartRecordingHandler.cs +│ │ │ │ │ │ ├── ProfilerStartRecordingHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerStopRecordingHandler.cs +│ │ │ │ │ │ ├── ProfilerStopRecordingHandler.cs.meta +│ │ │ │ │ │ ├── ProfilerTakeSnapshotHandler.cs +│ │ │ │ │ │ └── ProfilerTakeSnapshotHandler.cs.meta +│ │ │ │ │ ├── Profiler.meta +│ │ │ │ │ ├── ProjectInfoHandler.cs +│ │ │ │ │ ├── ProjectInfoHandler.cs.meta +│ │ │ │ │ ├── Recorder +│ │ │ │ │ │ ├── RecorderStartRecordingHandler.cs +│ │ │ │ │ │ ├── RecorderStartRecordingHandler.cs.meta +│ │ │ │ │ │ ├── RecorderState.cs +│ │ │ │ │ │ ├── RecorderState.cs.meta +│ │ │ │ │ │ ├── RecorderStatusHandler.cs +│ │ │ │ │ │ ├── RecorderStatusHandler.cs.meta +│ │ │ │ │ │ ├── RecorderStopRecordingHandler.cs +│ │ │ │ │ │ └── RecorderStopRecordingHandler.cs.meta +│ │ │ │ │ ├── Recorder.meta +│ │ │ │ │ ├── Remote +│ │ │ │ │ │ ├── RemoteBridge.cs +│ │ │ │ │ │ ├── RemoteBridge.cs.meta +│ │ │ │ │ │ ├── RemoteHelper.cs +│ │ │ │ │ │ ├── RemoteHelper.cs.meta +│ │ │ │ │ │ ├── RemoteInvokeHandler.cs +│ │ │ │ │ │ ├── RemoteInvokeHandler.cs.meta +│ │ │ │ │ │ ├── RemoteListHandler.cs +│ │ │ │ │ │ ├── RemoteListHandler.cs.meta +│ │ │ │ │ │ ├── RemoteServiceInstaller.cs +│ │ │ │ │ │ └── RemoteServiceInstaller.cs.meta +│ │ │ │ │ ├── Remote.meta +│ │ │ │ │ ├── Scene +│ │ │ │ │ │ ├── SceneCloseHandler.cs +│ │ │ │ │ │ ├── SceneCloseHandler.cs.meta +│ │ │ │ │ │ ├── SceneGetActiveHandler.cs +│ │ │ │ │ │ ├── SceneGetActiveHandler.cs.meta +│ │ │ │ │ │ ├── SceneListHandler.cs +│ │ │ │ │ │ ├── SceneListHandler.cs.meta +│ │ │ │ │ │ ├── SceneNewHandler.cs +│ │ │ │ │ │ ├── SceneNewHandler.cs.meta +│ │ │ │ │ │ ├── SceneOpenHandler.cs +│ │ │ │ │ │ ├── SceneOpenHandler.cs.meta +│ │ │ │ │ │ ├── SceneResolver.cs +│ │ │ │ │ │ ├── SceneResolver.cs.meta +│ │ │ │ │ │ ├── SceneSaveHandler.cs +│ │ │ │ │ │ ├── SceneSaveHandler.cs.meta +│ │ │ │ │ │ ├── SceneSetActiveHandler.cs +│ │ │ │ │ │ └── SceneSetActiveHandler.cs.meta +│ │ │ │ │ ├── Scene.meta +│ │ │ │ │ ├── Screenshot +│ │ │ │ │ │ ├── ScreenshotCaptureHandler.cs +│ │ │ │ │ │ └── ScreenshotCaptureHandler.cs.meta +│ │ │ │ │ ├── Screenshot.meta +│ │ │ │ │ ├── Search +│ │ │ │ │ │ ├── UniCli.Server.Editor.Search.asmdef +│ │ │ │ │ │ ├── UniCli.Server.Editor.Search.asmdef.meta +│ │ │ │ │ │ ├── UnitySearchHandler.cs +│ │ │ │ │ │ └── UnitySearchHandler.cs.meta +│ │ │ │ │ ├── Search.meta +│ │ │ │ │ ├── Selection +│ │ │ │ │ │ ├── GetSelectionHandler.cs +│ │ │ │ │ │ ├── GetSelectionHandler.cs.meta +│ │ │ │ │ │ ├── SetAssetHandler.cs +│ │ │ │ │ │ ├── SetAssetHandler.cs.meta +│ │ │ │ │ │ ├── SetAssetsHandler.cs +│ │ │ │ │ │ ├── SetAssetsHandler.cs.meta +│ │ │ │ │ │ ├── SetGameObjectHandler.cs +│ │ │ │ │ │ ├── SetGameObjectHandler.cs.meta +│ │ │ │ │ │ ├── SetGameObjectsHandler.cs +│ │ │ │ │ │ └── SetGameObjectsHandler.cs.meta +│ │ │ │ │ ├── Selection.meta +│ │ │ │ │ ├── TestRunner +│ │ │ │ │ │ ├── TestRunnerHandler.cs +│ │ │ │ │ │ └── TestRunnerHandler.cs.meta +│ │ │ │ │ ├── TestRunner.meta +│ │ │ │ │ ├── Types +│ │ │ │ │ │ ├── TypeCacheListHandler.cs +│ │ │ │ │ │ ├── TypeCacheListHandler.cs.meta +│ │ │ │ │ │ ├── TypeInspectHandler.cs +│ │ │ │ │ │ └── TypeInspectHandler.cs.meta +│ │ │ │ │ ├── Types.meta +│ │ │ │ │ ├── Window +│ │ │ │ │ │ ├── CreateWindowHandler.cs +│ │ │ │ │ │ ├── CreateWindowHandler.cs.meta +│ │ │ │ │ │ ├── FocusWindowHandler.cs +│ │ │ │ │ │ ├── FocusWindowHandler.cs.meta +│ │ │ │ │ │ ├── ListWindowHandler.cs +│ │ │ │ │ │ ├── ListWindowHandler.cs.meta +│ │ │ │ │ │ ├── OpenWindowHandler.cs +│ │ │ │ │ │ ├── OpenWindowHandler.cs.meta +│ │ │ │ │ │ ├── WindowResolver.cs +│ │ │ │ │ │ └── WindowResolver.cs.meta +│ │ │ │ │ └── Window.meta +│ │ │ │ ├── Handlers.meta +│ │ │ │ ├── IDispatcherReloader.cs +│ │ │ │ ├── IDispatcherReloader.cs.meta +│ │ │ │ ├── IFormatWriter.cs +│ │ │ │ ├── IFormatWriter.cs.meta +│ │ │ │ ├── IServiceInstaller.cs +│ │ │ │ ├── IServiceInstaller.cs.meta +│ │ │ │ ├── Internal +│ │ │ │ │ ├── CommandFieldInfoExtractor.cs +│ │ │ │ │ └── CommandFieldInfoExtractor.cs.meta +│ │ │ │ ├── Internal.meta +│ │ │ │ ├── Modules +│ │ │ │ │ ├── ModuleAttribute.cs +│ │ │ │ │ ├── ModuleAttribute.cs.meta +│ │ │ │ │ ├── ModuleCommandScanner.cs +│ │ │ │ │ ├── ModuleCommandScanner.cs.meta +│ │ │ │ │ ├── ModuleDefinition.cs +│ │ │ │ │ ├── ModuleDefinition.cs.meta +│ │ │ │ │ ├── ModuleRegistry.cs +│ │ │ │ │ ├── ModuleRegistry.cs.meta +│ │ │ │ │ ├── UniCliSettings.cs +│ │ │ │ │ ├── UniCliSettings.cs.meta +│ │ │ │ │ ├── UniCliSettingsProvider.cs +│ │ │ │ │ └── UniCliSettingsProvider.cs.meta +│ │ │ │ ├── Modules.meta +│ │ │ │ ├── PipeServer.cs +│ │ │ │ ├── PipeServer.cs.meta +│ │ │ │ ├── ProjectIdentifier.cs +│ │ │ │ ├── ProjectIdentifier.cs.meta +│ │ │ │ ├── Protocol +│ │ │ │ │ ├── CommandInfo.cs +│ │ │ │ │ ├── CommandInfo.cs.meta +│ │ │ │ │ ├── CommandRequest.cs +│ │ │ │ │ ├── CommandRequest.cs.meta +│ │ │ │ │ ├── CommandResponse.cs +│ │ │ │ │ ├── CommandResponse.cs.meta +│ │ │ │ │ ├── ProtocolConstants.cs +│ │ │ │ │ ├── ProtocolConstants.cs.meta +│ │ │ │ │ ├── Result.cs +│ │ │ │ │ ├── Result.cs.meta +│ │ │ │ │ ├── TestRunnerResponse.cs +│ │ │ │ │ ├── TestRunnerResponse.cs.meta +│ │ │ │ │ ├── Unit.cs +│ │ │ │ │ └── Unit.cs.meta +│ │ │ │ ├── Protocol.meta +│ │ │ │ ├── ServerContext.cs +│ │ │ │ ├── ServerContext.cs.meta +│ │ │ │ ├── ServiceRegistry.cs +│ │ │ │ ├── ServiceRegistry.cs.meta +│ │ │ │ ├── StackTraceHelper.cs +│ │ │ │ ├── StackTraceHelper.cs.meta +│ │ │ │ ├── TaskExtensions.cs +│ │ │ │ ├── TaskExtensions.cs.meta +│ │ │ │ ├── UniCli.Server.Editor.asmdef +│ │ │ │ ├── UniCli.Server.Editor.asmdef.meta +│ │ │ │ ├── UniCliServer.cs +│ │ │ │ ├── UniCliServer.cs.meta +│ │ │ │ ├── UniCliServerBootstrap.cs +│ │ │ │ ├── UniCliServerBootstrap.cs.meta +│ │ │ │ ├── UniCliServerWindow.cs +│ │ │ │ └── UniCliServerWindow.cs.meta +│ │ │ ├── Editor.meta +│ │ │ ├── Runtime +│ │ │ │ ├── Remote +│ │ │ │ │ ├── Commands +│ │ │ │ │ │ ├── FindGameObjectsCommand.cs +│ │ │ │ │ │ ├── FindGameObjectsCommand.cs.meta +│ │ │ │ │ │ ├── GetHierarchyCommand.cs +│ │ │ │ │ │ ├── GetHierarchyCommand.cs.meta +│ │ │ │ │ │ ├── GetLogsCommand.cs +│ │ │ │ │ │ ├── GetLogsCommand.cs.meta +│ │ │ │ │ │ ├── GetPlayerPrefCommand.cs +│ │ │ │ │ │ ├── GetPlayerPrefCommand.cs.meta +│ │ │ │ │ │ ├── GetScenesCommand.cs +│ │ │ │ │ │ ├── GetScenesCommand.cs.meta +│ │ │ │ │ │ ├── StatsCommand.cs +│ │ │ │ │ │ ├── StatsCommand.cs.meta +│ │ │ │ │ │ ├── SystemInfoCommand.cs +│ │ │ │ │ │ └── SystemInfoCommand.cs.meta +│ │ │ │ │ ├── Commands.meta +│ │ │ │ │ ├── DebugCommand.cs +│ │ │ │ │ ├── DebugCommand.cs.meta +│ │ │ │ │ ├── DebugCommandRegistry.cs +│ │ │ │ │ ├── DebugCommandRegistry.cs.meta +│ │ │ │ │ ├── Protocol +│ │ │ │ │ │ ├── RuntimeChunkedMessage.cs +│ │ │ │ │ │ ├── RuntimeChunkedMessage.cs.meta +│ │ │ │ │ │ ├── RuntimeCommandInfo.cs +│ │ │ │ │ │ ├── RuntimeCommandInfo.cs.meta +│ │ │ │ │ │ ├── RuntimeCommandRequest.cs +│ │ │ │ │ │ ├── RuntimeCommandRequest.cs.meta +│ │ │ │ │ │ ├── RuntimeCommandResponse.cs +│ │ │ │ │ │ ├── RuntimeCommandResponse.cs.meta +│ │ │ │ │ │ ├── RuntimeMessageGuids.cs +│ │ │ │ │ │ └── RuntimeMessageGuids.cs.meta +│ │ │ │ │ ├── Protocol.meta +│ │ │ │ │ ├── RuntimeDebugReceiver.cs +│ │ │ │ │ ├── RuntimeDebugReceiver.cs.meta +│ │ │ │ │ ├── RuntimeLogCapture.cs +│ │ │ │ │ ├── RuntimeLogCapture.cs.meta +│ │ │ │ │ ├── UniCli.Remote.asmdef +│ │ │ │ │ ├── UniCli.Remote.asmdef.meta +│ │ │ │ │ ├── Unit.cs +│ │ │ │ │ └── Unit.cs.meta +│ │ │ │ └── Remote.meta +│ │ │ ├── Runtime.meta +│ │ │ ├── Tests +│ │ │ │ ├── Editor +│ │ │ │ │ ├── CommandDispatcherResponseTests.cs +│ │ │ │ │ ├── CommandDispatcherResponseTests.cs.meta +│ │ │ │ │ ├── CommandFieldInfoExtractorTests.cs +│ │ │ │ │ ├── CommandFieldInfoExtractorTests.cs.meta +│ │ │ │ │ ├── ProtocolConstantsTests.cs +│ │ │ │ │ ├── ProtocolConstantsTests.cs.meta +│ │ │ │ │ ├── ResultTests.cs +│ │ │ │ │ ├── ResultTests.cs.meta +│ │ │ │ │ ├── UniCli.Server.Editor.Tests.asmdef +│ │ │ │ │ └── UniCli.Server.Editor.Tests.asmdef.meta +│ │ │ │ └── Editor.meta +│ │ │ ├── Tests.meta +│ │ │ ├── package.json +│ │ │ └── package.json.meta +│ │ ├── manifest.json +│ │ └── packages-lock.json +│ ├── ProjectSettings +│ │ ├── AudioManager.asset +│ │ ├── ClusterInputManager.asset +│ │ ├── DynamicsManager.asset +│ │ ├── EditorBuildSettings.asset +│ │ ├── EditorSettings.asset +│ │ ├── GraphicsSettings.asset +│ │ ├── InputManager.asset +│ │ ├── MemorySettings.asset +│ │ ├── NavMeshAreas.asset +│ │ ├── PackageManagerSettings.asset +│ │ ├── Packages +│ │ │ └── com.unity.testtools.codecoverage +│ │ │ └── Settings.json +│ │ ├── Physics2DSettings.asset +│ │ ├── PresetManager.asset +│ │ ├── ProjectSettings.asset +│ │ ├── ProjectVersion.txt +│ │ ├── QualitySettings.asset +│ │ ├── SceneTemplateSettings.json +│ │ ├── TagManager.asset +│ │ ├── TimeManager.asset +│ │ ├── TimelineSettings.asset +│ │ ├── UniCliSettings.asset +│ │ ├── UnityConnectSettings.asset +│ │ ├── VFXManager.asset +│ │ ├── VersionControlSettings.asset +│ │ └── XRSettings.asset +│ ├── UniCli.Server.Editor.csproj.DotSettings +│ └── UniCli.Server.Runtime.csproj.DotSettings +└── tools + └── generate-command-reference.sh + +``` + +`CLAUDE.md`: + +```md +# UniCli + +## Project Structure + +- `src`: Source code directory + - `src/UniCli.Client`: CLI project for `unicli` + - `src/UniCli.Unity`: Unity server implementation for `unicli` (Unity project) + - `src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor`: Editor-side command handlers + - `src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote`: Runtime debug commands (runs on device via PlayerConnection) + - `src/UniCli.Unity/Assets/Samples`: Sample implementations for the server package + - `src/UniCli.SourceGenerator`: Roslyn Source Generator for auto-generating Inspect command handlers +- `src/UniCli.Protocol`: Shared type definitions between `UniCli.Client` and `UniCli.Unity` +- `samples`: Sample Unity projects for multi-version testing + - `samples/UniCli.Samples.Unity2022LTS`: Unity 2022.3 LTS project + - `samples/UniCli.Samples.Unity6LTS`: Unity 6 (6000.0) LTS project +- `doc`: Documentation directory + - `doc/command-naming-guidelines.md`: Command naming conventions for handlers + +## Quick Commands + +```bash +# Build Protocol (must be built first to trigger file copy) +dotnet build src/UniCli.Protocol + +# Build Client +dotnet build src/UniCli.Client + +# Publish Client and test with the built binary +dotnet publish src/UniCli.Client -o .build +UNICLI_PROJECT=src/UniCli.Unity .build/unicli commands --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Compile --json + +# GameObject operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.Create '{"name":"TestObject"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.Create '{"name":"Child","parent":"TestObject"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.Create '{"name":"WithCollider","components":["BoxCollider"]}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.CreatePrimitive '{"primitiveType":"Cube"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.CreatePrimitive '{"primitiveType":"Sphere","name":"Ball","parent":"Parent"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.GetComponents '{"path":"Main Camera"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.AddComponent '{"path":"Main Camera","typeName":"BoxCollider"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.RemoveComponent '{"componentInstanceId":12345}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.Destroy '{"path":"TestObject"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.SetTransform '{"path":"TestObject","position":[1,2,3]}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.Duplicate '{"path":"TestObject"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.Rename '{"path":"TestObject","name":"Renamed"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec GameObject.SetParent '{"path":"Child","parentPath":"Parent"}' --json + +# Component operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Component.SetProperty '{"componentInstanceId":12345,"propertyPath":"m_IsKinematic","value":"true"}' --json + +# AnimatorController operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AnimatorController.Create '{"assetPath":"Assets/Test.controller"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AnimatorController.Inspect '{"assetPath":"Assets/Test.controller"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AnimatorController.AddParameter '{"assetPath":"Assets/Test.controller","name":"Speed","type":"Float"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AnimatorController.RemoveParameter '{"assetPath":"Assets/Test.controller","name":"Speed"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AnimatorController.AddState '{"assetPath":"Assets/Test.controller","name":"Idle"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AnimatorController.AddTransition '{"assetPath":"Assets/Test.controller","sourceStateName":"Idle","destinationStateName":"Walk"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AnimatorController.AddTransitionCondition '{"assetPath":"Assets/Test.controller","sourceStateName":"Idle","destinationStateName":"Walk","parameter":"Speed","mode":"Greater","threshold":0.1}' --json + +# Animator operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Animator.Inspect '{"path":"SomeGameObject"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Animator.SetController '{"path":"SomeGameObject","controllerAssetPath":"Assets/Test.controller"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Animator.SetParameter '{"path":"SomeGameObject","name":"Speed","value":"1.5"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Animator.Play '{"path":"SomeGameObject","stateName":"Idle"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Animator.CrossFade '{"path":"SomeGameObject","stateName":"Walk","transitionDuration":0.25}' --json + +# Prefab operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Prefab.GetStatus '{"path":"Main Camera"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Prefab.Instantiate '{"assetPath":"Assets/Prefabs/Enemy.prefab"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Prefab.Save '{"path":"Main Camera","assetPath":"Assets/TestPrefab.prefab"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Prefab.Apply '{"path":"MyPrefabInstance"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Prefab.Unpack '{"path":"MyPrefabInstance"}' --json + +# Scene operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.List --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.GetActive --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.Open '{"path":"Assets/Scenes/SampleScene.unity"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.Open '{"path":"Assets/Scenes/Additive.unity","additive":true}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.SetActive '{"name":"SampleScene"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.Save '{"all":true}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.Close '{"name":"Additive"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Scene.New '{"empty":true,"additive":true}' --json + +# PackageManager operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec PackageManager.GetInfo '{"name":"com.unity.test-framework"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec PackageManager.Update '{"name":"com.unity.test-framework"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec PackageManager.Update '{"name":"com.unity.test-framework","version":"1.4.5"}' --json + +# AssetDatabase operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec AssetDatabase.Delete '{"path":"Assets/Prefabs/Old.prefab"}' --json + +# Run Unity EditMode tests (default: only failed/skipped results) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec TestRunner.RunEditMode --json + +# Run Unity PlayMode tests (default: only failed/skipped results) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec TestRunner.RunPlayMode --json + +# Run tests with all results (including passed) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec TestRunner.RunEditMode --resultFilter all --json + +# Run tests with stack traces (first 3 lines per failure) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec TestRunner.RunEditMode --stackTraceLines 3 --json + +# Run tests with summary counts only (no individual results) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec TestRunner.RunEditMode --resultFilter none --json + +# Console logs filtered by type (comma-separated) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Console.GetLog --logType "Warning,Error" --json + +# Console logs with stack traces (first 3 lines) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Console.GetLog --logType Error --stackTraceLines 3 --json + +# Build player +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildPlayer.Build --locationPathName "Builds/Test.app" --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildPlayer.Build --locationPathName "Builds/Test.app" --options Development --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildPlayer.Build --locationPathName "Builds/Test.app" --options Development --options ConnectWithProfiler --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildPlayer.Build --locationPathName "Builds/Test.app" --target Android --scenes "Assets/Scenes/SampleScene.unity" --json + +# Compile player scripts for a specific build target +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildPlayer.Compile --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildPlayer.Compile --target Android --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildPlayer.Compile --target iOS --extraScriptingDefines MY_DEFINE --extraScriptingDefines ANOTHER_DEFINE --json + +# BuildProfile operations (Unity 6+ only) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildProfile.List --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildProfile.GetActive --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildProfile.SetActive '{"path":"Assets/Settings/MyProfile.asset"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildProfile.SetActive '{"path":"none"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildProfile.Inspect '{"path":"Assets/Settings/MyProfile.asset"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec BuildProfile.Inspect --json + +# Connection operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Connection.List --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Connection.Status --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Connection.Connect '{"id":-1}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Connection.Connect '{"ip":"192.168.1.100"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Connection.Connect '{"deviceId":"DEVICE_SERIAL"}' --json + +# Remote debug commands (requires Development Build connected via PlayerConnection) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.List --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.Stats"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.SystemInfo"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.GetHierarchy"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.GetLogs","data":"{\"limit\":100}"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.GetScenes"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.FindGameObjects","data":"{\"name\":\"Player\"}"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.GetPlayerPref","data":"{\"key\":\"HighScore\",\"type\":\"int\"}"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Remote.Invoke '{"command":"Debug.Stats","playerId":1}' --json + +# Material operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Material.Create '{"assetPath":"Assets/Materials/MyMat.mat"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Material.Create '{"assetPath":"Assets/Materials/MyMat.mat","shader":"Standard"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Material.SetColor '{"guid":"abc123","name":"_Color","value":{"r":1,"g":0,"b":0,"a":1}}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Material.GetColor '{"guid":"abc123","name":"_Color"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Material.SetFloat '{"guid":"abc123","name":"_Metallic","value":0.8}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Material.GetFloat '{"guid":"abc123","name":"_Metallic"}' --json + +# Settings operations (Inspect via Source Generator, modify via eval) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec PlayerSettings.Inspect --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec EditorSettings.Inspect --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec EditorUserBuildSettings.Inspect --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli eval 'PlayerSettings.companyName = "MyCompany";' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli eval 'PlayerSettings.SetScriptingBackend(UnityEditor.Build.NamedBuildTarget.Android, ScriptingImplementation.IL2CPP);' --json + +# Type inspection +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Type.List '{"baseType":"UniCli.Server.Editor.Handlers.ICommandHandler"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Type.Inspect '{"typeName":"UnityEditor.PlayerSettings"}' --json + +# Dynamic C# code execution (Eval) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli eval 'return Application.unityVersion;' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli eval 'return PlayerSettings.productName;' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli eval "$(cat <<'EOF' +var go = GameObject.Find("Main Camera"); +return go.transform.position; +EOF +)" --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli eval "$(cat <<'EOF' +var stats = new MyStats(); +stats.objectCount = GameObject.FindObjectsOfType().Length; +return stats; +EOF +)" --declarations "$(cat <<'EOF' +[System.Serializable] +public class MyStats { public int objectCount; } +EOF +)" --json + +# NuGet package management (requires NuGetForUnity) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.List --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.Install '{"id":"Newtonsoft.Json"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.Install '{"id":"Newtonsoft.Json","version":"13.0.3"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.Install '{"id":"MyPackage","source":"/path/to/local/feed"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.Uninstall '{"id":"Newtonsoft.Json"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.Restore --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.ListSources --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.AddSource '{"name":"LocalFeed","path":"/path/to/local/feed"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec NuGet.RemoveSource '{"name":"LocalFeed"}' --json + +# Profiler operations +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.Inspect --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.StartRecording --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.StartRecording '{"deep":true}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.StartRecording '{"editor":true}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.StopRecording --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.SaveProfile '{"path":"Profiles/capture.raw"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.LoadProfile '{"path":"Profiles/capture.raw"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.LoadProfile '{"path":"Profiles/capture.raw","keepExistingData":true}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.GetFrameData --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.GetFrameData '{"frame":10,"limit":5}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.TakeSnapshot --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.TakeSnapshot '{"path":"MemoryCaptures/my_snapshot.snap"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.AnalyzeFrames --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.AnalyzeFrames '{"startFrame":100,"endFrame":200,"topSampleCount":20}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.AnalyzeFrames '{"sampleNameFilter":"BehaviourUpdate"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.FindSpikes '{"frameTimeThresholdMs":16.6}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.FindSpikes '{"gcThresholdBytes":1024}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Profiler.FindSpikes '{"frameTimeThresholdMs":16.6,"gcThresholdBytes":1024,"limit":5}' --json + +# Recorder operations (requires Play Mode and com.unity.recorder package) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Recorder.StartRecording --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Recorder.StartRecording '{"path":"Recordings/test.mp4"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Recorder.StartRecording '{"format":"WebM","quality":"High","frameRate":60}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Recorder.StartRecording '{"width":1920,"height":1080}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Recorder.Status --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Recorder.StopRecording --json + +# Screenshot operations (requires Play Mode) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Screenshot.Capture --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Screenshot.Capture '{"path":"Screenshots/test.png"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Screenshot.Capture '{"path":"Screenshots/hires.png","superSize":2}' --json + +# Module management +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Module.List --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Module.Enable '{"name":"Search"}' --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Module.Disable '{"name":"Profiler"}' --json + +# Compile Unity project (also serves as a build verification for the server) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Compile --json +``` + +## Testing + +When testing CLI behavior, always publish first with `dotnet publish src/UniCli.Client -o .build`, then test with `.build/unicli` directly. Do not use `dotnet run`. + +### Server-side verification (required) + +`dotnet build` only verifies the client-side compilation. When modifying server-side code (`Packages/com.yucchiy.unicli-server/`), **always verify with Unity compilation and tests**. + +```bash +# 1. Build Source Generator (if modified) +dotnet build src/UniCli.SourceGenerator + +# 2. Publish the client first +dotnet publish src/UniCli.Client -o .build + +# 3. Verify server-side compilation (required) +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec Compile --json + +# 3. Run tests +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec TestRunner.RunEditMode --json +UNICLI_PROJECT=src/UniCli.Unity .build/unicli exec TestRunner.RunPlayMode --json +``` + +### Multi-version testing with sample projects + +Sample Unity projects under `samples/` are used to verify commands across different Unity versions. Install the server package and test against each: + +```bash +# Unity 2022.3 LTS +UNICLI_PROJECT=samples/UniCli.Samples.Unity2022LTS .build/unicli exec Compile --json +UNICLI_PROJECT=samples/UniCli.Samples.Unity2022LTS .build/unicli exec TestRunner.RunEditMode --json + +# Unity 6 LTS (6000.0) — supports BuildProfile commands +UNICLI_PROJECT=samples/UniCli.Samples.Unity6LTS .build/unicli exec Compile --json +UNICLI_PROJECT=samples/UniCli.Samples.Unity6LTS .build/unicli exec TestRunner.RunEditMode --json +``` + +### Maintaining documentation + +When adding or modifying commands, update the following files to keep them in sync: + +- `README.md` — Available Commands table and Examples section +- `.claude-plugin/unicli/skills/unity-development/SKILL.md` — Built-in Commands table and Common Workflows section + +When creating new commands, follow the naming conventions in `doc/command-naming-guidelines.md`. + +### Releasing a new version + +1. Create a `release/vX.Y.Z` branch from `main` +2. Bump version in the following 4 files: + - `src/UniCli.Client/UniCli.Client.csproj` (``) + - `src/UniCli.Unity/Packages/com.yucchiy.unicli-server/package.json` (`"version"`) + - `.claude-plugin/marketplace.json` (`"version"`) + - `.claude-plugin/unicli/skills/unity-development/SKILL.md` (`metadata.version`) +3. Build and verify: `dotnet build src/UniCli.Protocol && dotnet publish src/UniCli.Client -o .build` +4. Create a PR to `main` with a changelog summary +5. After merge: `git tag vX.Y.Z && git push origin vX.Y.Z` + - GitHub Actions (`.github/workflows/release.yml`) will automatically build binaries and create a GitHub Release + - The Homebrew formula (`yucchiy/homebrew-tap`) and Scoop manifest (`yucchiy/scoop-bucket`) are automatically updated by CI (using the `UNICLI_RELEASE_TOKEN` secret) + +### Tests requiring Unity connection + +The `exec` and `commands` subcommands require a connection to the Unity Editor. If the connection fails, retry a few times. If it still fails, ask the user to confirm that Unity Editor is running with the project open. + +``` + +`LICENSE`: + +``` +MIT License + +Copyright (c) 2026 Yuichiro Mukai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +``` + +`README.md`: + +```md +# UniCli + +[![GitHub Release](https://img.shields.io/github/v/release/yucchiy/UniCli)](https://github.com/yucchiy/UniCli/releases) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + +A command-line interface for controlling Unity Editor from the terminal. +UniCli lets you compile scripts, run tests, manage packages, inspect GameObjects, and more — all without leaving your terminal. + +Designed to work with AI coding agents such as [Claude Code](https://docs.anthropic.com/en/docs/claude-code), UniCli gives AI the ability to interact with Unity Editor directly through structured CLI commands with JSON output. + +- **80+ built-in commands**: compile, test, build, inspect GameObjects, manage scenes/prefabs/packages, and more +- **Dynamic C# execution**: run arbitrary C# code in Unity via `unicli eval` +- **Extensible**: add custom commands with a single C# class +- **AI-agent ready**: structured JSON output, Claude Code plugin, and Agent Skills support +- **Cross-platform**: NativeAOT binaries for macOS (arm64/x64) and Windows (x64) + +## Table of Contents + +- [Getting Started](#getting-started) + - [Requirements](#requirements) + - [CLI](#cli) + - [Unity Package](#unity-package) + - [Quick Usage](#quick-usage) +- [CLI Usage](#cli-usage) + - [Project Discovery](#project-discovery) +- [Executing Commands](#executing-commands) + - [Parameter syntax](#parameter-syntax) + - [Common options](#common-options) + - [Examples](#examples) +- [Dynamic Code Execution (Eval)](#dynamic-code-execution-eval) +- [Custom Commands](#custom-commands) + - [Text formatting](#text-formatting) + - [Async handlers and cancellation](#async-handlers-and-cancellation) + - [Error handling](#error-handling) +- [Built-in Commands](#built-in-commands) +- [Module Management](#module-management) +- [Architecture](#architecture) +- [Remote Commands](#remote-commands) + - [Prerequisites](#prerequisites) + - [Editor-side commands](#editor-side-commands) + - [Built-in debug commands](#built-in-debug-commands) + - [Creating custom debug commands](#creating-custom-debug-commands) +- [AI Agent Integration](#ai-agent-integration) + - [Claude Code Plugin](#claude-code-plugin) + - [Agent Skills / Codex](#agent-skills--codex) +- [License](#license) + +## Getting Started + +UniCli requires two components: the **CLI** (`unicli`) installed on your machine, and the **Unity Package** (`com.yucchiy.unicli-server`) installed in your Unity project. Both must be set up for UniCli to work. + +### Requirements + +- Unity 2022.3 or later +- macOS (arm64 / x64) or Windows (x64) + +### CLI + +**Homebrew (macOS):** + +```bash +brew tap yucchiy/tap +brew install unicli +``` + +**Scoop (Windows):** + +```powershell +scoop bucket add unicli https://github.com/yucchiy/scoop-bucket +scoop install unicli +``` + +**Manual:** Download the latest binary from the [Releases](https://github.com/yucchiy/UniCli/releases) page and place it in your PATH. + +### Unity Package + +The UniCli package must be installed in your Unity project. You can install it using the CLI: + +```bash +unicli install +``` + +Or add it manually via Unity Package Manager using the git URL: + +``` +https://github.com/yucchiy/UniCli.git?path=src/UniCli.Unity/Packages/com.yucchiy.unicli-server +``` + +### Quick Usage + +Open your Unity project in the Editor, then run `unicli` from the Unity project directory (or any subdirectory). UniCli automatically detects the project by looking for an `Assets` folder in the current directory and its ancestors. + +```bash +# Verify connection +unicli check + +# List all available commands +unicli commands + +# Compile scripts +unicli exec Compile + +# Run EditMode tests +unicli exec TestRunner.RunEditMode + +# Execute arbitrary C# code (--json for JSON output) +unicli eval 'return Application.unityVersion;' --json +``` + +## CLI Usage + +The `unicli` binary provides the following subcommands: + +| Subcommand | Description | +|---------------|-------------------------------------------------------------| +| `check` | Check package installation and Unity Editor connection | +| `install` | Install the UniCli package into a Unity project | +| `exec` | Execute a command on the Unity Editor | +| `eval` | Compile and execute C# code dynamically in the Unity Editor | +| `commands` | List all available commands | +| `status` | Show connection status and project info | +| `completions` | Generate shell completion scripts (bash / zsh / fish) | + +``` +unicli check # verify installation and editor connection +unicli install # install the Unity package +unicli commands # list all available commands +unicli eval '' # compile and execute C# code dynamically +unicli status # show connection details +unicli completions bash # generate shell completions +``` + +Add `--json` to `check`, `commands`, or `status` for machine-readable JSON output. + +### Project Discovery + +By default, `unicli` searches the current directory and its ancestors for a Unity project (a directory containing an `Assets` folder). If you run `unicli` from outside a Unity project, or want to target a specific project, set the `UNICLI_PROJECT` environment variable: + +```bash +# Run from anywhere by specifying the project path +UNICLI_PROJECT=/path/to/my/unity-project unicli exec Compile --json + +# Useful when the current directory is not inside the Unity project +UNICLI_PROJECT=src/UniCli.Unity unicli commands --json +``` + + +## Executing Commands + +Use `unicli exec ` to run commands on the Unity Editor. + +### Parameter syntax + +Parameters can be passed as `--key value` flags (recommended) or as a raw JSON string: + +```bash +# --key value syntax (recommended) +unicli exec GameObject.Find --name "Main Camera" +unicli exec TestRunner.RunEditMode --testNameFilter MyTest + +# Raw JSON syntax +unicli exec GameObject.Find '{"name":"Main Camera"}' +``` + +Boolean flags can be passed without a value: + +```bash +unicli exec GameObject.Find --includeInactive +``` + +Array parameters can be passed by repeating the same flag: + +```bash +unicli exec BuildPlayer.Build --locationPathName "Builds/Test.app" --options Development --options ConnectWithProfiler +unicli exec BuildPlayer.Compile --target iOS --extraScriptingDefines MY_DEFINE --extraScriptingDefines ANOTHER_DEFINE +``` + +### Common options + +These options can be combined with any `exec` command: + +| Option | Description | +|-------------|--------------------------------------| +| `--json` | Output in JSON format | +| `--timeout` | Set command timeout in milliseconds | +| `--no-focus`| Don't bring Unity Editor to front | +| `--help` | Show command parameters and nested type details | + +By default, when the server is not responding (e.g., after an assembly reload), the CLI automatically brings Unity Editor to the foreground using a PID file (`Library/UniCli/server.pid`) and restores focus to the original application once the command completes. Use `--no-focus` to disable this behavior, or set the `UNICLI_FOCUS` environment variable to `0` or `false` to disable it globally. + +### Examples + +```bash +# Compile scripts (--json for JSON output, --timeout to set deadline) +unicli exec Compile --json +unicli exec Compile --timeout 30000 + +# Build the player +unicli exec BuildPlayer.Build --locationPathName "Builds/Test.app" +unicli exec BuildPlayer.Build --locationPathName "Builds/Test.app" --options Development --target Android + +# Run tests +unicli exec TestRunner.RunEditMode +unicli exec TestRunner.RunEditMode --testNameFilter MyTest --stackTraceLines 3 +unicli exec TestRunner.RunEditMode --resultFilter all + +# Find and inspect GameObjects +unicli exec GameObject.Find --name "Main Camera" +unicli exec GameObject.GetHierarchy +unicli exec GameObject.GetComponents --instanceId 1234 + +# Create and modify GameObjects +unicli exec GameObject.Create --name "Enemy" +unicli exec GameObject.SetTransform --path "Enemy" --position 1,2,3 --rotation 0,90,0 +unicli exec GameObject.AddComponent --path "Enemy" --typeName BoxCollider + +# Scene operations +unicli exec Scene.Open --path "Assets/Scenes/Level1.unity" +unicli exec Scene.Save --all + +# Set component properties +unicli exec Component.SetProperty --componentInstanceId 1234 --propertyPath "m_IsKinematic" --value "true" + +# Console logs +unicli exec Console.GetLog --logType "Warning,Error" + +# Show command parameters and usage +unicli exec GameObject.Find --help +``` + +### Inspect nested request/response types + +`unicli exec --help` shows top-level fields and, when available, nested type details. +For machine-readable schemas, use `unicli commands --json`: nested types are exposed via each command's `requestTypeDetails` and `responseTypeDetails` arrays. +Match nested type details by `typeId`; `type` and `typeName` stay human-friendly and may not be unique. + +```bash +# Human-friendly schema (includes nested type details) +unicli exec AssetDatabase.Find --help + +# Machine-readable schema (commands include "requestTypeDetails" / "responseTypeDetails") +unicli commands --json +``` + +See [Built-in Commands](#built-in-commands) for the full list of available commands. + + +## Dynamic Code Execution (Eval) + +`unicli eval` compiles and executes arbitrary C# code in the Unity Editor context using `AssemblyBuilder`. +Code has full access to Unity APIs (`UnityEngine`, `UnityEditor`) as well as any packages and libraries referenced by the project. + +```bash +unicli eval '' [--json] [--declarations ''] [--timeout ] +``` + +| Option | Description | +|---|---| +| `--json` | Output in JSON format | +| `--declarations` | Additional type declarations (classes, structs, enums) | +| `--timeout` | Timeout in milliseconds | + +For multi-line code, use shell heredocs: + +```bash +unicli eval "$(cat <<'EOF' +var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); +var objects = GameObject.FindObjectsOfType(true); +return $"{scene.name}: {objects.Length} objects"; +EOF +)" --json +``` + +The result is returned as raw JSON. If the return type is `[Serializable]`, it is serialized with `JsonUtility`. +`UnityEngine.Object` types use `EditorJsonUtility`. Primitives and strings are returned directly. Code that doesn't return a value (`void` operations) returns `null`. + +Use `--declarations` to define custom types for structured return values: + +```bash +unicli eval "$(cat <<'EOF' +var stats = new SceneStats(); +stats.sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; +stats.objectCount = GameObject.FindObjectsOfType().Length; +return stats; +EOF +)" --declarations "$(cat <<'EOF' +[System.Serializable] +public class SceneStats +{ + public string sceneName; + public int objectCount; +} +EOF +)" --json +``` + +Eval code supports `async`/`await` and receives a `cancellationToken` variable (`System.Threading.CancellationToken`) that is cancelled when the client disconnects. + +Use it for cooperative cancellation of long-running operations: + +```bash +# Wait asynchronously with cancellation support +unicli eval 'await Task.Delay(5000, cancellationToken); return "done";' --json +``` + + +## Custom Commands + +You can extend UniCli by adding custom commands in your Unity project. Commands are auto-discovered — no manual registration required. + +Inherit from `CommandHandler` and define `[Serializable]` request/response types: + +```csharp +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UniCli.Server.Editor.Handlers; + +public sealed class GreetHandler : CommandHandler +{ + public override string CommandName => "MyApp.Greet"; + public override string Description => "Returns a greeting message"; + + protected override ValueTask ExecuteAsync(GreetRequest request, CancellationToken cancellationToken) + { + return new ValueTask(new GreetResponse + { + message = $"Hello, {request.name}!" + }); + } +} + +[Serializable] +public class GreetRequest +{ + public string name; +} + +[Serializable] +public class GreetResponse +{ + public string message; +} +``` + +Once the handler is placed anywhere in your Unity project, it becomes immediately available: + +```bash +unicli exec MyApp.Greet --name "World" +``` + +For naming conventions (when to use concept-based names like `Scene.*` vs API-direct names like `AssetDatabase.*`), see [`doc/command-naming-guidelines.md`](doc/command-naming-guidelines.md). + +For commands that require no input or produce no output, use `Unit` as the type parameter: + +```csharp +public sealed class PingHandler : CommandHandler +{ + public override string CommandName => "MyApp.Ping"; + public override string Description => "Health check"; + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + return new ValueTask(new PingResponse { ok = true }); + } +} +``` + +### Text formatting + +Override `TryWriteFormatted` to provide human-readable output (used when `--json` is not specified): + +```csharp +protected override bool TryWriteFormatted(GreetResponse response, bool success, IFormatWriter writer) +{ + writer.WriteLine(response.message); + return true; +} +``` + +With this override, the output changes depending on whether `--json` is used: + +```bash +$ unicli exec MyApp.Greet --name "World" +Hello, World! + +$ unicli exec MyApp.Greet --name "World" --json +{"message":"Hello, World!"} +``` + +### Async handlers and cancellation + +All command handlers receive a `CancellationToken` that is cancelled when the client disconnects (e.g., Ctrl+C). For long-running async operations, pass the token through to ensure prompt cancellation: + +```csharp +using System.Threading; +using System.Threading.Tasks; +using UniCli.Server.Editor; +using UniCli.Server.Editor.Handlers; + +public sealed class LongRunningHandler : CommandHandler +{ + public override string CommandName => "MyApp.LongTask"; + public override string Description => "A long-running async operation"; + + protected override async ValueTask ExecuteAsync(MyRequest request, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + + // Start an async Unity operation + SomeAsyncUnityApi.Start(result => tcs.SetResult(result)); + + // Use WithCancellation to abort the wait if the client disconnects + var result = await tcs.Task.WithCancellation(cancellationToken); + + return new MyResponse { value = result }; + } +} +``` + +The `WithCancellation` extension method (on `Task` / `Task`) races the task against the cancellation token. If the client disconnects, the await throws `OperationCanceledException` and the server immediately becomes available for the next command. + +For synchronous handlers that complete instantly, the `cancellationToken` parameter can be ignored. + +### Error handling + +Throw `CommandFailedException` to report failures while still returning structured data: + +```csharp +if (hasErrors) + throw new CommandFailedException("Validation failed", response); +``` + + +## Built-in Commands + +The following commands are built in. Run `unicli commands` to see this list from the terminal, or `unicli exec --help` to see parameters for any command. For a full reference with parameter and response details, see [`doc/commands.md`](doc/commands.md). + +### Core + +| Command | Description | +|---|---| +| `Compile` | Compile scripts and return results | +| `Eval` | Compile and execute C# code dynamically | + +### Console + +| Command | Description | +|---|---| +| `Console.GetLog` | Get console log entries (supports comma-separated `logType` filter, e.g. `"Warning,Error"`) | +| `Console.Clear` | Clear console | + +### PlayMode + +| Command | Description | +|---|---| +| `PlayMode.Enter` | Enter play mode | +| `PlayMode.Exit` | Exit play mode | +| `PlayMode.Pause` | Toggle pause | +| `PlayMode.Status` | Get the current play mode state | + +### TestRunner + +| Command | Description | +|---|---| +| `TestRunner.RunEditMode` | Run EditMode tests (`resultFilter`: `"failures"` (default), `"all"`, `"none"`) | +| `TestRunner.RunPlayMode` | Run PlayMode tests (`resultFilter`: `"failures"` (default), `"all"`, `"none"`) | + +### Build + +| Command | Description | +|---|---| +| `BuildPlayer.Build` | Build the player | +| `BuildPlayer.Compile` | Compile player scripts for a build target | +| `BuildTarget.GetActive` | Get the active build target and target group | +| `BuildTarget.Switch` | Switch the active build target | +| `BuildProfile.List` | List all build profiles (Unity 6+) | +| `BuildProfile.GetActive` | Get the active build profile (Unity 6+) | +| `BuildProfile.SetActive` | Set the active build profile (Unity 6+) | +| `BuildProfile.Inspect` | Inspect a build profile's details (Unity 6+) | + +### GameObject / Component + +| Command | Description | +|---|---| +| `GameObject.Find` | Find GameObjects | +| `GameObject.Create` | Create a new GameObject | +| `GameObject.CreatePrimitive` | Create a primitive GameObject | +| `GameObject.GetComponents` | Get components | +| `GameObject.SetActive` | Set active state | +| `GameObject.GetHierarchy` | Get scene hierarchy | +| `GameObject.AddComponent` | Add a component | +| `GameObject.RemoveComponent` | Remove a component | +| `GameObject.Destroy` | Destroy a GameObject | +| `GameObject.SetTransform` | Set local transform | +| `GameObject.Duplicate` | Duplicate a GameObject | +| `GameObject.Rename` | Rename a GameObject | +| `GameObject.SetParent` | Change parent or move to root | +| `Component.SetProperty` | Set a component property (supports ObjectReference via `guid:`, `instanceId:`, asset path) | + +### Scene + +| Command | Description | +|---|---| +| `Scene.List` | List all loaded scenes | +| `Scene.GetActive` | Get the active scene | +| `Scene.SetActive` | Set the active scene | +| `Scene.Open` | Open a scene by asset path | +| `Scene.Close` | Close a loaded scene | +| `Scene.Save` | Save a scene or all open scenes | +| `Scene.New` | Create a new scene | + +### Asset + +| Command | Description | +|---|---| +| `AssetDatabase.Find` | Search assets | +| `AssetDatabase.Import` | Import an asset | +| `AssetDatabase.GetPath` | Get asset path by GUID | +| `AssetDatabase.Delete` | Delete an asset | +| `Prefab.GetStatus` | Get prefab instance status | +| `Prefab.Instantiate` | Instantiate a prefab into scene | +| `Prefab.Save` | Save GameObject as prefab | +| `Prefab.Apply` | Apply prefab overrides | +| `Prefab.Unpack` | Unpack a prefab instance | +| `Material.Create` | Create a new material asset | +| `Material.Inspect` | Read all properties of a material (auto-generated) | +| `Material.SetColor` | Set a color property on a material | +| `Material.GetColor` | Get a color property from a material | +| `Material.SetFloat` | Set a float property on a material | +| `Material.GetFloat` | Get a float property from a material | + +### Animation + +| Command | Description | +|---|---| +| `AnimatorController.Create` | Create a new .controller asset | +| `AnimatorController.Inspect` | Inspect layers, parameters, states | +| `AnimatorController.AddParameter` | Add a parameter | +| `AnimatorController.RemoveParameter` | Remove a parameter | +| `AnimatorController.AddState` | Add a state to a layer | +| `AnimatorController.AddTransition` | Add a transition between states | +| `AnimatorController.AddTransitionCondition` | Add a condition to a transition | +| `Animator.Inspect` | Inspect Animator component | +| `Animator.SetController` | Assign an AnimatorController | +| `Animator.SetParameter` | Set a parameter value (PlayMode) | +| `Animator.Play` | Play a state immediately (PlayMode) | +| `Animator.CrossFade` | Cross-fade to a state (PlayMode) | + +### PackageManager + +| Command | Description | +|---|---| +| `PackageManager.List` | List packages | +| `PackageManager.Add` | Add a package | +| `PackageManager.Remove` | Remove a package | +| `PackageManager.Search` | Search registry | +| `PackageManager.GetInfo` | Get package details | +| `PackageManager.Update` | Update a package | + +### Project / Settings + +| Command | Description | +|---|---| +| `Project.Inspect` | Get project info | +| `PlayerSettings.Inspect` | Get all PlayerSettings values (auto-generated) | +| `EditorSettings.Inspect` | Get all EditorSettings values (auto-generated) | +| `EditorUserBuildSettings.Inspect` | Get all EditorUserBuildSettings values (auto-generated) | + +### AssemblyDefinition + +| Command | Description | +|---|---| +| `AssemblyDefinition.List` | List assembly definitions | +| `AssemblyDefinition.Get` | Get assembly definition | +| `AssemblyDefinition.Create` | Create assembly definition | +| `AssemblyDefinition.AddReference` | Add asmdef reference | +| `AssemblyDefinition.RemoveReference` | Remove asmdef reference | + +### Selection / Window / Menu + +| Command | Description | +|---|---| +| `Selection.Get` | Get the current editor selection | +| `Selection.SetAsset` | Select an asset by path | +| `Selection.SetAssets` | Select multiple assets by paths | +| `Selection.SetGameObject` | Select a GameObject by path | +| `Selection.SetGameObjects` | Select multiple GameObjects by paths | +| `Window.List` | List all available EditorWindow types | +| `Window.Open` | Open an EditorWindow by type name | +| `Window.Focus` | Focus an already-open EditorWindow | +| `Window.Create` | Create a new EditorWindow instance | +| `Menu.List` | List menu items | +| `Menu.Execute` | Execute a menu item | + +### Utility + +| Command | Description | +|---|---| +| `Type.List` | List types derived from a base type | +| `Type.Inspect` | Inspect nested types of a given type | + +### Connection / Remote + +| Command | Description | +|---|---| +| `Connection.List` | List available connection targets | +| `Connection.Connect` | Connect to a target by ID, IP, or device ID | +| `Connection.Status` | Get current connection status | +| `Remote.List` | List debug commands on connected player | +| `Remote.Invoke` | Invoke a debug command on connected player | + +### Profiler + +| Command | Description | +|---|---| +| `Profiler.Inspect` | Get profiler status and memory statistics | +| `Profiler.StartRecording` | Start profiler recording | +| `Profiler.StopRecording` | Stop profiler recording | +| `Profiler.SaveProfile` | Save profiler data to a .raw file | +| `Profiler.LoadProfile` | Load profiler data from a .raw file | +| `Profiler.GetFrameData` | Get CPU profiler sample data for a specific frame | +| `Profiler.TakeSnapshot` | Take a memory snapshot (.snap file) | +| `Profiler.AnalyzeFrames` | Analyze recorded frames and return aggregate statistics | +| `Profiler.FindSpikes` | Find frames exceeding frame time or GC allocation thresholds | + +### Screenshot / Recorder + +| Command | Description | +|---|---| +| `Screenshot.Capture` | Capture Game View screenshot as PNG (requires Play Mode) | +| `Recorder.StartRecording` | Start recording Game View as video (requires Play Mode) | +| `Recorder.StopRecording` | Stop the current video recording | +| `Recorder.Status` | Get the current recording status | + +### Module + +| Command | Description | +|---|---| +| `Module.List` | List all available modules and their enabled status | +| `Module.Enable` | Enable a module and reload the command dispatcher | +| `Module.Disable` | Disable a module and reload the command dispatcher | + +### Optional: Search + +| Command | Description | +|---|---| +| `Search` | Search Unity project using Unity Search API | + +### Optional: NuGet + +Requires [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity). + +| Command | Description | +|---|---| +| `NuGet.List` | List all installed NuGet packages | +| `NuGet.Install` | Install a NuGet package | +| `NuGet.Uninstall` | Uninstall a NuGet package | +| `NuGet.Restore` | Restore all NuGet packages | +| `NuGet.ListSources` | List all configured package sources | +| `NuGet.AddSource` | Add a NuGet package source | +| `NuGet.RemoveSource` | Remove a NuGet package source | + +### Optional: BuildMagic + +Requires [BuildMagic](https://github.com/AnnulusGames/BuildMagic) (`jp.co.cyberagent.buildmagic`). + +| Command | Description | +|---|---| +| `BuildMagic.List` | List all BuildMagic build schemes | +| `BuildMagic.Inspect` | Inspect a build scheme's configurations | +| `BuildMagic.Apply` | Apply a build scheme | + +Settings Inspect commands (`PlayerSettings.Inspect`, `EditorSettings.Inspect`, etc.) are auto-generated via a Roslyn Source Generator, so the available properties always match your exact Unity version. To **modify** settings, use `unicli eval`: + +```bash +unicli eval 'PlayerSettings.companyName = "MyCompany";' --json +``` + + +## Module Management + +UniCli groups optional commands into **modules** that can be toggled on or off per project. Core commands (Compile, Eval, Console, PlayMode, Menu, Build, TestRunner, Settings, etc.) are always available and cannot be disabled. + +The following modules are available: + +| Module | Description | +|---|---| +| Scene | Scene operations | +| GameObject | GameObject and Component operations | +| Assets | AssetDatabase, Prefab, Material operations | +| Profiler | Profiler operations | +| Animation | Animator and AnimatorController operations | +| Remote | Remote debug and Connection operations | +| Recorder | Video recording operations (requires `com.unity.recorder`) | +| Search | Unity Search API operations | +| NuGet | NuGet package management (requires NuGetForUnity) | +| BuildMagic | BuildMagic build scheme operations (requires `jp.co.cyberagent.buildmagic`) | + +All modules are enabled by default. To disable a module, use the CLI or the Unity settings UI (**Edit > Project Settings > UniCli**): + +```bash +# List all modules and their enabled status +unicli exec Module.List --json + +# Enable a module +unicli exec Module.Enable '{"name":"Search"}' --json + +# Disable a module +unicli exec Module.Disable '{"name":"Profiler"}' --json +``` + +Module settings are saved in `ProjectSettings/UniCliSettings.asset`. + +`unicli commands --json` includes `builtIn` and `module` fields for each command, so you can programmatically identify whether a command is built-in or user-defined and which module it belongs to. + + +## Architecture + +UniCli consists of two components: + +- **CLI** (`unicli`) — A NativeAOT-compiled binary that you run from the terminal +- **Unity Package** (`com.yucchiy.unicli-server`) — An Editor plugin that receives and executes commands inside Unity + +### Architecture + +``` +┌──────────┐ Named Pipe ┌────────────────┐ PlayerConnection ┌────────────────┐ +│ unicli │◄───────────────►│ Unity Editor │◄──────────────────►│ Device │ +│ (CLI) │ Length-prefixed │ (Server) │ Chunked messages │ (Dev Build) │ +│ │ JSON messages │ │ │ │ +└──────────┘ └────────────────┘ └────────────────┘ +``` + +**CLI ↔ Editor (Named Pipe):** +The CLI and Unity Editor communicate over a named pipe. The pipe name is derived from a SHA256 hash of the project's `Assets` path, so each project gets its own connection. Messages use a length-prefixed JSON framing protocol with a handshake (magic bytes `UCLI` + protocol version). The server plugin initializes via `[InitializeOnLoad]`, creates a background listener on the named pipe, and enqueues incoming commands to a `ConcurrentQueue`. Commands are dequeued and executed on Unity's main thread every frame via `EditorApplication.update`. + +**Editor ↔ Device (PlayerConnection):** +For remote debugging, the Editor relays commands to a running Development Build via Unity's `PlayerConnection`. The runtime module (`UniCli.Remote`) auto-initializes a `RuntimeDebugReceiver` on the device, which discovers debug commands via reflection and registers message handlers. Responses are split into 16 KB chunks to work around PlayerConnection's undocumented message size limits. The Editor's `RemoteBridge` reassembles chunks and returns the complete response to the CLI. + + +## Remote Commands + +UniCli can invoke debug commands on a running Development Build via Unity's `PlayerConnection`. This lets you inspect runtime state, query performance stats, and execute custom debug operations on a connected device — all from the terminal. + +**Communication path:** CLI → Unity Editor (Named Pipe) → Device (PlayerConnection) + +### Prerequisites + +1. **Define symbol** — Add `UNICLI_REMOTE` to your project's Scripting Define Symbols (Player Settings → Other Settings). + - The remote module's asmdef has two define constraints: `UNICLI_REMOTE || UNITY_EDITOR` and `DEVELOPMENT_BUILD || UNITY_EDITOR`. + - In the Editor, both constraints are satisfied automatically — no additional setup needed for development. + - In player builds, `UNICLI_REMOTE` must be defined **and** the build must be a Development Build for the module to be included. + - This means release builds and builds without `UNICLI_REMOTE` will never contain the remote module code. +2. **Development Build** — Build with the "Development Build" and "Autoconnect Profiler" options enabled to allow PlayerConnection communication. +3. **Connect** — Use `Connection.Connect` to connect the Editor to the running player before sending remote commands. + +### Editor-side commands + +| Command | Description | +|---|---| +| `Remote.List` | List all debug commands registered on the connected player | +| `Remote.Invoke` | Invoke a debug command on the connected player | + +```bash +# List debug commands on connected runtime player +unicli exec Remote.List + +# Invoke a debug command +unicli exec Remote.Invoke '{"command":"Debug.Stats"}' + +# Invoke with parameters +unicli exec Remote.Invoke '{"command":"Debug.GetPlayerPref","data":"{\"key\":\"HighScore\",\"type\":\"int\"}"}' + +# Specify a particular player (when multiple are connected) +unicli exec Remote.Invoke '{"command":"Debug.SystemInfo","playerId":1}' +``` + +### Built-in debug commands + +The following debug commands are included in the package and available on any Development Build with `UNICLI_REMOTE` defined: + +| Command | Description | +|---|---| +| `Debug.SystemInfo` | Device model, OS, CPU, GPU, memory, battery, screen, quality settings | +| `Debug.Stats` | FPS, frame time, memory usage, GC collection counts, scene/object counts | +| `Debug.GetLogs` | Recent log entries from a ring buffer (supports limit and type filter) | +| `Debug.GetHierarchy` | Active scene hierarchy tree with depth, active state, component names | +| `Debug.FindGameObjects` | Substring search across all GameObjects (including inactive) | +| `Debug.GetScenes` | All loaded scenes with name, path, build index, root count | +| `Debug.GetPlayerPref` | Read a PlayerPrefs value by key (string, int, or float) | + +### Creating custom debug commands + +Inherit from `DebugCommand` and override `CommandName` / `Description`. Commands are auto-discovered at runtime via reflection. + +```csharp +using System; +using UniCli.Remote; +using UnityEngine; + +public sealed class ToggleHitboxesCommand : DebugCommand +{ + public override string CommandName => "Debug.ToggleHitboxes"; + public override string Description => "Toggle hitbox visualization"; + + protected override Response ExecuteCommand(Request request) + { + HitboxVisualizer.Enabled = request.enabled; + return new Response { enabled = HitboxVisualizer.Enabled }; + } + + [Serializable] + public class Request + { + public bool enabled; + } + + [Serializable] + public class Response + { + public bool enabled; + } +} +``` + +Use `Unit` as the type parameter when no input or output is needed: + +```csharp +public sealed class ResetStateCommand : DebugCommand +{ + public override string CommandName => "Debug.ResetState"; + public override string Description => "Reset game state"; + + protected override Unit ExecuteCommand(Unit request) + { + GameManager.ResetAll(); + return Unit.Value; + } +} +``` + +Key points: + +- Request/Response types must be `[Serializable]` with **public fields** (required by `JsonUtility`) +- The base class uses `[RequireDerived]` to protect all subclasses from Managed Stripping automatically +- Commands run synchronously on the main thread +- Override `CommandName` (by convention `Debug.*`) and optionally `Description` +- Place custom commands anywhere in your project — they are discovered automatically via reflection at startup + + +## AI Agent Integration + +### Claude Code Plugin + +UniCli provides a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin via the marketplace. This plugin gives Claude Code the ability to interact with Unity Editor — compiling scripts, running tests, inspecting GameObjects, managing packages, and more — as part of its coding workflow. + +With the plugin installed, Claude Code can: + +- **Compile & verify** — catch compilation errors immediately after code changes +- **Run tests** — execute EditMode / PlayMode tests and read results +- **Inspect the scene** — find GameObjects, check components, and navigate the hierarchy +- **Manage packages** — add, remove, and search Unity packages +- **Discover commands** — automatically find all available commands, including project-specific custom commands + +The plugin also handles server package setup: if the `com.yucchiy.unicli-server` package is not yet installed in the Unity project, Claude Code will run `unicli install` to set it up automatically. + +#### Install the plugin + +The UniCli CLI must be installed beforehand. See [Getting Started — CLI](#cli) above. + +```bash +# 1. Add the UniCli marketplace +/plugin marketplace add yucchiy/UniCli + +# 2. Install the plugin +/plugin install unicli@unicli +``` + +### Agent Skills / Codex + +UniCli's skill definition follows the [Agent Skills](https://github.com/anthropics/agent-skills) specification, making it compatible with multiple AI coding agents: + +- **Codex (OpenAI)**: Automatically detected via `.agents/skills/unity-development/` +- **Claude Code**: Installed as a plugin via `.claude-plugin/` +- **Other agents**: Any tool that supports the Agent Skills spec can load the skill from `.agents/skills/` + +#### Install via Codex `$skill-installer` + +If you're using [Codex](https://openai.com/index/introducing-codex/), install the UniCli skill directly from this repository: + +``` +$skill-installer install https://github.com/yucchiy/UniCli/tree/main/.agents/skills/unity-development +``` + +Once installed, Codex automatically detects the skill and gains the ability to interact with Unity Editor. + +#### Manual setup for other projects + +To use UniCli's skill in another project, copy the skill directory: + +```bash +mkdir -p .agents/skills +cp -R /path/to/UniCli/.agents/skills/unity-development .agents/skills/unity-development +``` + +## License + +[MIT](./LICENSE) + +``` + +`UniCli.sln`: + +```sln + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniCli.Client", "src/UniCli.Client/UniCli.Client.csproj", "{FB67A713-18F1-4985-B7F9-DD7AEED66644}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniCli.Protocol", "src/UniCli.Protocol/UniCli.Protocol.csproj", "{C30C491E-5CD7-45D4-9BCD-35EF289A5CD7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniCli.SourceGenerator", "src/UniCli.SourceGenerator/UniCli.SourceGenerator.csproj", "{061B259A-331F-4C54-B64D-4CAB513EDC50}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniCli.Client.Tests", "src/UniCli.Client.Tests/UniCli.Client.Tests.csproj", "{3F7401C1-E6DB-474F-944A-9978D9F0E703}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FB67A713-18F1-4985-B7F9-DD7AEED66644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB67A713-18F1-4985-B7F9-DD7AEED66644}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB67A713-18F1-4985-B7F9-DD7AEED66644}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB67A713-18F1-4985-B7F9-DD7AEED66644}.Release|Any CPU.Build.0 = Release|Any CPU + {C30C491E-5CD7-45D4-9BCD-35EF289A5CD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C30C491E-5CD7-45D4-9BCD-35EF289A5CD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C30C491E-5CD7-45D4-9BCD-35EF289A5CD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C30C491E-5CD7-45D4-9BCD-35EF289A5CD7}.Release|Any CPU.Build.0 = Release|Any CPU + {061B259A-331F-4C54-B64D-4CAB513EDC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {061B259A-331F-4C54-B64D-4CAB513EDC50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {061B259A-331F-4C54-B64D-4CAB513EDC50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {061B259A-331F-4C54-B64D-4CAB513EDC50}.Release|Any CPU.Build.0 = Release|Any CPU + {3F7401C1-E6DB-474F-944A-9978D9F0E703}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F7401C1-E6DB-474F-944A-9978D9F0E703}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F7401C1-E6DB-474F-944A-9978D9F0E703}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F7401C1-E6DB-474F-944A-9978D9F0E703}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FB67A713-18F1-4985-B7F9-DD7AEED66644} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {C30C491E-5CD7-45D4-9BCD-35EF289A5CD7} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {061B259A-331F-4C54-B64D-4CAB513EDC50} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {3F7401C1-E6DB-474F-944A-9978D9F0E703} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection +EndGlobal + +``` + +`doc/command-naming-guidelines.md`: + +```md +# Command Naming Guidelines + +This document defines the naming conventions for UniCli command handlers. + +## Naming Principle + +Command names should be **intuitive for CLI users**, not a 1:1 mirror of Unity API class names. The guiding question is: + +> If a Unity developer types `unicli commands`, will this name immediately convey what it does? + +## Rules + +### 1. Use the concept name when the Unity API name is an implementation detail + +When the Unity API name includes prefixes like `Editor`, suffixes like `Utility`/`Manager`, or is otherwise verbose, use a shorter concept-based name. + +| Command | Unity API | Reason | +|---|---|---| +| `Scene.*` | `EditorSceneManager`, `SceneManager` | "Scene" is the concept; "EditorSceneManager" is an implementation detail | +| `Prefab.*` | `PrefabUtility` | "Prefab" is the concept; "Utility" suffix adds no meaning | +| `PlayMode.*` | `EditorApplication` | "PlayMode" describes the action domain; "EditorApplication" is too broad | +| `TestRunner.*` | `TestRunner.Api` | Simplified from the full namespace | +| `Console.*` | `LogEntry`, `LogEntries` | "Console" matches the Unity Editor window name | +| `Connection.*` | `EditorConnection` | "Connection" is the concept; "Editor" prefix is unnecessary in a CLI | + +### 2. Use the Unity API name when it is already a clear, well-known concept + +When the Unity class name is universally recognized by Unity developers and is concise enough for CLI use, adopt it directly. + +| Command | Unity API | Reason | +|---|---|---| +| `AnimatorController.*` | `AnimatorController` | Distinct concept from `Animator` (runtime component) | +| `AssetDatabase.*` | `AssetDatabase` | Well-known, concise | +| `PackageManager.*` | `PackageManager.Client` | Well-known Unity concept | +| `Profiler.*` | `ProfilerDriver` | "Profiler" is the concept; "Driver" is an implementation detail | +| `BuildProfile.*` | `BuildProfile` | Direct match, Unity 6+ concept | +| `AssemblyDefinition.*` | `AssemblyDefinitionAsset` | "AssemblyDefinition" is well-known; "Asset" suffix is redundant | +| `Material.*` | `Material` | Direct match | + +### 3. Distinguish overlapping concepts with separate categories + +When one Unity domain spans both editor assets and runtime components, use separate command categories rather than merging them. + +| Category | Scope | Example Commands | +|---|---|---| +| `AnimatorController.*` | Editor asset (`.controller` file) | `Create`, `AddState`, `AddTransition` | +| `Animator.*` | Runtime component on a GameObject | `Play`, `SetParameter`, `CrossFade` | + +Merging these under a single `Animator.*` would create ambiguity (e.g., `Animator.Inspect` could mean either). + +### 4. Keep the category name singular and concise + +- Use `Scene`, not `Scenes` or `SceneManagement` +- Use `Prefab`, not `Prefabs` or `PrefabUtility` +- Use `GameObject`, not `GameObjects` (even though operations may return multiple results) + +### 5. Include the Unity API name in the description when the command name differs + +When a command uses a concept-based name that differs from the Unity API, include the original API name in the handler's `Description` property. This allows users to discover commands by searching for the API name they already know. + +```csharp +// Good: users can find this via `unicli commands | grep PrefabUtility` +public override string Description => "Save a GameObject as a prefab asset via PrefabUtility"; + +// Good: API name matches, no extra annotation needed +public override string Description => "Search assets in the AssetDatabase"; +``` + +This solves the discoverability problem: even if a user thinks in terms of Unity API names, `unicli commands --json | grep ` will find the right command. + +### 6. Use `Verb` or `Adjective+Noun` for action names + +| Pattern | Examples | +|---|---| +| CRUD-style | `Create`, `Find`, `Delete`, `List` | +| State changes | `SetActive`, `SetParent`, `SetTransform` | +| Inspection | `Inspect`, `GetComponents`, `GetHierarchy` | +| Domain actions | `Enter`, `Exit`, `Pause`, `Apply`, `Compile` | + +Prefer short, imperative verbs. Avoid redundancy with the category: `GameObject.Create`, not `GameObject.CreateGameObject`. + +## Decision Checklist for New Commands + +When naming a new command, walk through these questions: + +1. **Is the Unity API name already concise and well-known?** + - Yes -> Use it directly (e.g., `AssetDatabase`, `Material`) + - No -> Simplify to the concept name (e.g., `Scene` instead of `EditorSceneManager`) + +2. **Does the name conflict with an existing category?** + - Yes -> Use the more specific name (e.g., `AnimatorController` to avoid conflicting with `Animator`) + - No -> Use the simpler name + +3. **Would a Unity developer immediately understand the command from `unicli commands` output?** + - Yes -> Good to go + - No -> Reconsider the name + +4. **Does the command name differ from the Unity API name?** + - Yes -> Include the API name naturally in the `Description` (e.g., `"... via PrefabUtility"`) + - No -> No extra annotation needed + +``` + +`doc/commands.md`: + +```md +# Command Reference + +> Auto-generated from `unicli commands --json`. Run `tools/generate-command-reference.sh` to update. + + +## Core + + +### Compile + +Trigger script compilation and return results with error details + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `errorCount` | `int` | +| `warningCount` | `int` | +| `errors` | `CompileIssue[]` | +| `warnings` | `CompileIssue[]` | + +--- + + +### Eval + +Compile and execute C# code dynamically in the Unity Editor context + +**Parameters:** + +| Field | Type | +|---|---| +| `code` | `string` | +| `declarations` | `string` | + +**Response:** None + +--- + + +## Animation + + +### Animator.CrossFade + +Cross-fade to a state on an Animator (requires PlayMode) + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `stateName` | `string` | +| `layer` | `int` | +| `transitionDuration` | `float` | +| `normalizedTime` | `float` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `stateName` | `string` | +| `layer` | `int` | +| `transitionDuration` | `float` | +| `normalizedTime` | `float` | + +--- + + +### Animator.Inspect + +Inspect an Animator component (parameters, current state, controller info) + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `gameObjectPath` | `string` | +| `instanceId` | `int` | +| `enabled` | `bool` | +| `controllerAssetPath` | `string` | +| `parameters` | `AnimatorRuntimeParameterInfo[]` | +| `isPlaying` | `bool` | +| `currentStateName` | `string` | +| `currentStateNormalizedTime` | `float` | + +--- + + +### Animator.Play + +Play a state immediately on an Animator (requires PlayMode) + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `stateName` | `string` | +| `layer` | `int` | +| `normalizedTime` | `float` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `stateName` | `string` | +| `layer` | `int` | +| `normalizedTime` | `float` | + +--- + + +### Animator.SetController + +Assign an AnimatorController to an Animator component + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `controllerAssetPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `gameObjectPath` | `string` | +| `controllerAssetPath` | `string` | + +--- + + +### Animator.SetParameter + +Set an Animator parameter value (requires PlayMode) + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `name` | `string` | +| `value` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `name` | `string` | +| `type` | `string` | +| `value` | `string` | + +--- + + +### AnimatorController.AddParameter + +Add a parameter to an AnimatorController + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `name` | `string` | +| `type` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `name` | `string` | +| `type` | `string` | +| `parameterCount` | `int` | + +--- + + +### AnimatorController.AddState + +Add a state to an AnimatorController layer + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `name` | `string` | +| `layerIndex` | `int` | +| `motionAssetPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `name` | `string` | +| `layerIndex` | `int` | +| `motionName` | `string` | +| `stateCount` | `int` | + +--- + + +### AnimatorController.AddTransition + +Add a transition between two states in an AnimatorController + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `sourceStateName` | `string` | +| `destinationStateName` | `string` | +| `layerIndex` | `int` | +| `hasExitTime` | `bool` | +| `exitTime` | `float` | +| `duration` | `float` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `sourceStateName` | `string` | +| `destinationStateName` | `string` | +| `layerIndex` | `int` | +| `hasExitTime` | `bool` | +| `exitTime` | `float` | +| `duration` | `float` | + +--- + + +### AnimatorController.AddTransitionCondition + +Add a condition to a transition between two states in an AnimatorController + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `sourceStateName` | `string` | +| `destinationStateName` | `string` | +| `layerIndex` | `int` | +| `transitionIndex` | `int` | +| `parameter` | `string` | +| `mode` | `string` | +| `threshold` | `float` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `sourceStateName` | `string` | +| `destinationStateName` | `string` | +| `parameter` | `string` | +| `mode` | `string` | +| `threshold` | `float` | +| `conditionCount` | `int` | + +--- + + +### AnimatorController.Create + +Create a new AnimatorController asset + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `layerCount` | `int` | +| `parameterCount` | `int` | + +--- + + +### AnimatorController.Inspect + +Inspect an AnimatorController asset (layers, parameters, states) + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `parameters` | `AnimatorParameterInfo[]` | +| `layers` | `AnimatorLayerInfo[]` | + +--- + + +### AnimatorController.RemoveParameter + +Remove a parameter from an AnimatorController + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `name` | `string` | +| `parameterCount` | `int` | + +--- + + +## AssemblyDefinition + + +### AssemblyDefinition.AddReference + +Add an assembly reference to an existing assembly definition + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `reference` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `addedReference` | `string` | +| `references` | `string[]` | + +--- + + +### AssemblyDefinition.Create + +Create a new assembly definition file + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `directory` | `string` | +| `rootNamespace` | `string` | +| `references` | `string[]` | +| `includePlatforms` | `string[]` | +| `excludePlatforms` | `string[]` | +| `allowUnsafeCode` | `bool` | +| `autoReferenced` | `bool` (default: `true`) | +| `defineConstraints` | `string[]` | +| `noEngineReferences` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | + +--- + + +### AssemblyDefinition.Get + +Get detailed information about a specific assembly definition + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `rootNamespace` | `string` | +| `references` | `string[]` | +| `includePlatforms` | `string[]` | +| `excludePlatforms` | `string[]` | +| `allowUnsafeCode` | `bool` | +| `autoReferenced` | `bool` | +| `defineConstraints` | `string[]` | +| `noEngineReferences` | `bool` | +| `sourceFiles` | `string[]` | +| `defines` | `string[]` | + +--- + + +### AssemblyDefinition.List + +List all assembly definitions in the project + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `assemblies` | `AssemblyDefinitionEntry[]` | +| `totalCount` | `int` | + +--- + + +### AssemblyDefinition.RemoveReference + +Remove an assembly reference from an existing assembly definition + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `reference` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `removedReference` | `string` | +| `references` | `string[]` | + +--- + + +## Assets + + +### AssetDatabase.Delete + +Delete an asset + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `type` | `string` | + +--- + + +### AssetDatabase.Find + +Find assets by filter (e.g. t:Texture, l:MyLabel) + +**Parameters:** + +| Field | Type | +|---|---| +| `filter` | `string` | +| `searchInFolders` | `string[]` | +| `maxResults` | `int` (default: `100`) | + +**Response:** + +| Field | Type | +|---|---| +| `assets` | `AssetInfo[]` | +| `totalFound` | `int` | + +--- + + +### AssetDatabase.GetPath + +Convert between asset GUID and path + +**Parameters:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `path` | `string` | +| `type` | `string` | +| `exists` | `bool` | + +--- + + +### AssetDatabase.Import + +Reimport an asset or refresh the AssetDatabase + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `forceUpdate` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `refreshed` | `bool` | + +--- + + +### Material.Create + +Create a new material asset + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `shader` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `guid` | `string` | +| `shaderName` | `string` | + +--- + + +### Material.GetColor + +Get a color property from a material (Material.GetColor) + +**Parameters:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | +| `value` | `ColorValue` | + +--- + + +### Material.GetFloat + +Get a float property from a material (Material.GetFloat) + +**Parameters:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | +| `value` | `float` | + +--- + + +### Material.Inspect + +Inspect Material instance + +**Parameters:** + +| Field | Type | +|---|---| +| `guid` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `color` | `Color` | +| `mainTextureOffset` | `Vector2` | +| `mainTextureScale` | `Vector2` | +| `renderQueue` | `int` | +| `globalIlluminationFlags` | `string` | +| `doubleSidedGI` | `bool` | +| `enableInstancing` | `bool` | +| `passCount` | `int` | +| `isVariant` | `bool` | + +--- + + +### Material.SetColor + +Set a color property on a material (Material.SetColor) + +**Parameters:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | +| `value` | `ColorValue` | + +**Response:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | +| `value` | `ColorValue` | + +--- + + +### Material.SetFloat + +Set a float property on a material (Material.SetFloat) + +**Parameters:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | +| `value` | `float` | + +**Response:** + +| Field | Type | +|---|---| +| `guid` | `string` | +| `name` | `string` | +| `value` | `float` | + +--- + + +### Prefab.Apply + +Apply overrides of a prefab instance to the source prefab asset via PrefabUtility + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `assetPath` | `string` | + +--- + + +### Prefab.GetStatus + +Get prefab instance status for a GameObject via PrefabUtility + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `status` | `string` | +| `assetPath` | `string` | +| `hasOverrides` | `bool` | +| `isPrefabInstance` | `bool` | + +--- + + +### Prefab.Instantiate + +Instantiate a prefab asset into the scene via PrefabUtility + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `parentInstanceId` | `int` | +| `parentPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `name` | `string` | +| `assetPath` | `string` | + +--- + + +### Prefab.Save + +Save a GameObject as a prefab asset via PrefabUtility + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `assetPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `assetPath` | `string` | + +--- + + +### Prefab.Unpack + +Unpack a prefab instance via PrefabUtility, disconnecting it from the source prefab + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `completely` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `unpackMode` | `string` | + +--- + + +## BuildPlayer + + +### BuildPlayer.Build + +Build the player using BuildPipeline.BuildPlayer + +**Parameters:** + +| Field | Type | +|---|---| +| `target` | `string` | +| `locationPathName` | `string` | +| `scenes` | `string[]` | +| `options` | `string[]` | +| `extraScriptingDefines` | `string[]` | + +**Response:** + +| Field | Type | +|---|---| +| `target` | `string` | +| `targetGroup` | `string` | +| `locationPathName` | `string` | +| `result` | `string` | +| `totalErrorCount` | `int` | +| `totalWarningCount` | `int` | +| `totalBuildTimeSec` | `double` | +| `totalSizeBytes` | `Int64` | +| `steps` | `BuildStepInfo[]` | +| `errors` | `BuildMessageInfo[]` | +| `warnings` | `BuildMessageInfo[]` | + +--- + + +### BuildPlayer.Compile + +Compile player scripts for a specific build target + +**Parameters:** + +| Field | Type | +|---|---| +| `target` | `string` | +| `extraScriptingDefines` | `string[]` | + +**Response:** + +| Field | Type | +|---|---| +| `target` | `string` | +| `targetGroup` | `string` | +| `assemblyCount` | `int` | +| `assemblies` | `string[]` | +| `errorCount` | `int` | +| `warningCount` | `int` | +| `errors` | `CompileIssue[]` | +| `warnings` | `CompileIssue[]` | + +--- + + +## BuildTarget + + +### BuildTarget.GetActive + +Get the active build target and build target group via EditorUserBuildSettings + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `buildTarget` | `string` | +| `buildTargetGroup` | `string` | + +--- + + +### BuildTarget.Switch + +Switch the active build target via EditorUserBuildSettings.SwitchActiveBuildTarget + +**Parameters:** + +| Field | Type | +|---|---| +| `target` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `buildTarget` | `string` | +| `buildTargetGroup` | `string` | + +--- + + +## Commands + + +### Commands.List + +List all available commands with their metadata + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `commands` | `CommandInfo[]` | + +--- + + +## Console + + +### Console.Clear + +Clear Unity Editor console logs + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `clearedCount` | `int` | + +--- + + +### Console.GetLog + +Retrieve Unity Editor console logs with optional filtering + +**Parameters:** + +| Field | Type | +|---|---| +| `logType` | `string` (default: `All`) | +| `searchText` | `string` | +| `maxCount` | `int` (default: `100`) | +| `stackTraceLines` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `logs` | `LogEntry[]` | +| `totalCount` | `int` | +| `displayedCount` | `int` | + +--- + + +## EditorSettings + + +### EditorSettings.Inspect + +Inspect all EditorSettings values + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `unityRemoteDevice` | `string` | +| `unityRemoteCompression` | `string` | +| `unityRemoteResolution` | `string` | +| `unityRemoteJoystickSource` | `string` | +| `serializationMode` | `string` | +| `lineEndingsForNewScripts` | `string` | +| `defaultBehaviorMode` | `string` | +| `prefabModeAllowAutoSave` | `bool` | +| `spritePackerMode` | `string` | +| `spritePackerPaddingPower` | `int` | +| `etcTextureCompressorBehavior` | `int` | +| `etcTextureFastCompressor` | `int` | +| `etcTextureNormalCompressor` | `int` | +| `etcTextureBestCompressor` | `int` | +| `enableTextureStreamingInEditMode` | `bool` | +| `enableTextureStreamingInPlayMode` | `bool` | +| `asyncShaderCompilation` | `bool` | +| `cachingShaderPreprocessor` | `bool` | +| `projectGenerationRootNamespace` | `string` | +| `useLegacyProbeSampleCount` | `bool` | +| `enableCookiesInLightmapper` | `bool` | +| `enableEnlightenBakedGI` | `bool` | +| `enterPlayModeOptionsEnabled` | `bool` | +| `enterPlayModeOptions` | `string` | +| `serializeInlineMappingsOnOneLine` | `bool` | +| `assetPipelineMode` | `string` | +| `cacheServerMode` | `string` | +| `refreshImportMode` | `string` | +| `cacheServerEndpoint` | `string` | +| `cacheServerNamespacePrefix` | `string` | +| `cacheServerEnableDownload` | `bool` | +| `cacheServerEnableUpload` | `bool` | +| `cacheServerEnableAuth` | `bool` | +| `cacheServerEnableTls` | `bool` | +| `cacheServerValidationMode` | `string` | +| `cacheServerDownloadBatchSize` | `int` | +| `gameObjectNamingDigits` | `int` | +| `gameObjectNamingScheme` | `string` | +| `assetNamingUsesSpace` | `bool` | + +--- + + +## EditorUserBuildSettings + + +### EditorUserBuildSettings.Inspect + +Inspect all EditorUserBuildSettings values + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `selectedBuildTargetGroup` | `string` | +| `selectedQnxOsVersion` | `string` | +| `selectedQnxArchitecture` | `string` | +| `selectedEmbeddedLinuxArchitecture` | `string` | +| `remoteDeviceInfo` | `bool` | +| `remoteDeviceAddress` | `string` | +| `remoteDeviceUsername` | `string` | +| `remoteDeviceExports` | `string` | +| `pathOnRemoteDevice` | `string` | +| `selectedStandaloneTarget` | `string` | +| `standaloneBuildSubtarget` | `string` | +| `ps4BuildSubtarget` | `string` | +| `ps4HardwareTarget` | `string` | +| `explicitNullChecks` | `bool` | +| `explicitDivideByZeroChecks` | `bool` | +| `explicitArrayBoundsChecks` | `bool` | +| `needSubmissionMaterials` | `bool` | +| `forceInstallation` | `bool` | +| `movePackageToDiscOuterEdge` | `bool` | +| `compressFilesInPackage` | `bool` | +| `buildScriptsOnly` | `bool` | +| `xboxBuildSubtarget` | `string` | +| `streamingInstallLaunchRange` | `int` | +| `xboxOneDeployMethod` | `string` | +| `xboxOneDeployDrive` | `string` | +| `xboxOneAdditionalDebugPorts` | `string` | +| `xboxOneRebootIfDeployFailsAndRetry` | `bool` | +| `androidBuildSubtarget` | `string` | +| `webGLBuildSubtarget` | `string` | +| `androidETC2Fallback` | `string` | +| `androidBuildSystem` | `string` | +| `androidBuildType` | `string` | +| `androidCreateSymbols` | `string` | +| `wsaUWPBuildType` | `string` | +| `wsaUWPSDK` | `string` | +| `wsaMinUWPSDK` | `string` | +| `wsaArchitecture` | `string` | +| `wsaUWPVisualStudioVersion` | `string` | +| `windowsDevicePortalAddress` | `string` | +| `windowsDevicePortalUsername` | `string` | +| `windowsDevicePortalPassword` | `string` | +| `wsaBuildAndRunDeployTarget` | `string` | +| `overrideMaxTextureSize` | `int` | +| `overrideTextureCompression` | `string` | +| `activeBuildTarget` | `string` | +| `development` | `bool` | +| `connectProfiler` | `bool` | +| `buildWithDeepProfilingSupport` | `bool` | +| `allowDebugging` | `bool` | +| `waitForPlayerConnection` | `bool` | +| `exportAsGoogleAndroidProject` | `bool` | +| `buildAppBundle` | `bool` | +| `symlinkSources` | `bool` | +| `iOSXcodeBuildConfig` | `string` | +| `macOSXcodeBuildConfig` | `string` | +| `switchCreateRomFile` | `bool` | +| `switchEnableRomCompression` | `bool` | +| `switchSaveADF` | `bool` | +| `switchRomCompressionType` | `string` | +| `switchRomCompressionLevel` | `int` | +| `switchRomCompressionConfig` | `string` | +| `switchNVNGraphicsDebugger` | `bool` | +| `generateNintendoSwitchShaderInfo` | `bool` | +| `switchNVNShaderDebugging` | `bool` | +| `switchNVNAftermath` | `bool` | +| `switchNVNDrawValidation_Light` | `bool` | +| `switchNVNDrawValidation_Heavy` | `bool` | +| `switchEnableMemoryTracker` | `bool` | +| `switchWaitForMemoryTrackerOnStartup` | `bool` | +| `switchEnableDebugPad` | `bool` | +| `switchRedirectWritesToHostMount` | `bool` | +| `switchHTCSScriptDebugging` | `bool` | +| `switchUseLegacyNvnPoolAllocator` | `bool` | +| `switchEnableUnpublishableErrors` | `bool` | +| `installInBuildFolder` | `bool` | +| `waitForManagedDebugger` | `bool` | +| `managedDebuggerFixedPort` | `int` | + +--- + + +## GameObject + + +### Component.SetProperty + +Set a component property value via SerializedProperty + +**Parameters:** + +| Field | Type | +|---|---| +| `componentInstanceId` | `int` | +| `propertyPath` | `string` | +| `value` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `componentInstanceId` | `int` | +| `propertyPath` | `string` | +| `previousValue` | `string` | +| `currentValue` | `string` | + +--- + + +### GameObject.AddComponent + +Add a component to a GameObject by type name + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `typeName` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `typeName` | `string` | +| `instanceId` | `int` | +| `enabled` | `bool` | + +--- + + +### GameObject.Create + +Create a new GameObject in the scene + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `parent` | `string` | +| `components` | `string[]` | + +**Response:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `name` | `string` | +| `path` | `string` | +| `isActive` | `bool` | +| `components` | `string[]` | + +--- + + +### GameObject.CreatePrimitive + +Create a primitive GameObject (Cube, Sphere, Capsule, Cylinder, Plane, Quad) + +**Parameters:** + +| Field | Type | +|---|---| +| `primitiveType` | `string` | +| `name` | `string` | +| `parent` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `name` | `string` | +| `path` | `string` | +| `isActive` | `bool` | +| `components` | `string[]` | + +--- + + +### GameObject.Destroy + +Destroy a GameObject from the scene + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `instanceId` | `int` | + +--- + + +### GameObject.Duplicate + +Duplicate an existing GameObject in the scene + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `name` | `string` | +| `path` | `string` | +| `isActive` | `bool` | +| `components` | `string[]` | + +--- + + +### GameObject.Find + +Find GameObjects by name, tag, layer, or component + +**Parameters:** + +| Field | Type | +|---|---| +| `namePattern` | `string` | +| `tag` | `string` | +| `layer` | `int` (default: `-1`) | +| `requiredComponents` | `string[]` | +| `includeInactive` | `bool` | +| `maxResults` | `int` (default: `100`) | + +**Response:** + +| Field | Type | +|---|---| +| `results` | `GameObjectResult[]` | +| `totalFound` | `int` | + +--- + + +### GameObject.GetComponents + +Get detailed component information for a GameObject + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `components` | `ComponentDetail[]` | + +--- + + +### GameObject.GetHierarchy + +Get the scene hierarchy of GameObjects + +**Parameters:** + +| Field | Type | +|---|---| +| `includeInactive` | `bool` | +| `maxDepth` | `int` (default: `-1`) | +| `includeComponents` | `bool` (default: `true`) | + +**Response:** + +| Field | Type | +|---|---| +| `scenes` | `HierarchyScene[]` | + +--- + + +### GameObject.RemoveComponent + +Remove a component from a GameObject by instance ID + +**Parameters:** + +| Field | Type | +|---|---| +| `componentInstanceId` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `gameObjectName` | `string` | +| `typeName` | `string` | +| `componentInstanceId` | `int` | + +--- + + +### GameObject.Rename + +Rename a GameObject + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `previousName` | `string` | +| `name` | `string` | +| `instanceId` | `int` | + +--- + + +### GameObject.SetActive + +Set active state of a GameObject + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `active` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `previousState` | `bool` | +| `currentState` | `bool` | + +--- + + +### GameObject.SetParent + +Change the parent of a GameObject (or move to root) + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `parentInstanceId` | `int` | +| `parentPath` | `string` | +| `worldPositionStays` | `bool` (default: `true`) | + +**Response:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `name` | `string` | +| `path` | `string` | + +--- + + +### GameObject.SetTransform + +Set the local transform (position, rotation, scale) of a GameObject + +**Parameters:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `path` | `string` | +| `position` | `Single[]` | +| `rotation` | `Single[]` | +| `localScale` | `Single[]` | + +**Response:** + +| Field | Type | +|---|---| +| `instanceId` | `int` | +| `name` | `string` | +| `position` | `Single[]` | +| `rotation` | `Single[]` | +| `localScale` | `Single[]` | + +--- + + +## Menu + + +### Menu.Execute + +Execute a Unity Editor menu item by path + +**Parameters:** + +| Field | Type | +|---|---| +| `menuItemPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `executed` | `bool` | +| `menuItemPath` | `string` | + +--- + + +### Menu.List + +List available Unity Editor menu items with filtering + +**Parameters:** + +| Field | Type | +|---|---| +| `filterText` | `string` | +| `filterType` | `string` (default: `contains`) | +| `maxCount` | `int` (default: `200`) | + +**Response:** + +| Field | Type | +|---|---| +| `items` | `MenuItemInfo[]` | +| `totalCount` | `int` | +| `filteredCount` | `int` | + +--- + + +## Module + + +### Module.Disable + +Disable a module and reload the command dispatcher + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | + +**Response:** None + +--- + + +### Module.Enable + +Enable a module and reload the command dispatcher + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | + +**Response:** None + +--- + + +### Module.List + +List all available modules and their enabled status + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `modules` | `ModuleInfo[]` | + +--- + + +## NuGet + + +### NuGet.AddSource + +Add a NuGet package source + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | + +--- + + +### NuGet.Install + +Install a NuGet package by id and optional version + +**Parameters:** + +| Field | Type | +|---|---| +| `id` | `string` | +| `version` | `string` | +| `source` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `id` | `string` | +| `version` | `string` | + +--- + + +### NuGet.List + +List all installed NuGet packages + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `packages` | `NuGetPackageEntry[]` | +| `totalCount` | `int` | + +--- + + +### NuGet.ListSources + +List all configured NuGet package sources + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `sources` | `NuGetSourceEntry[]` | +| `totalCount` | `int` | + +--- + + +### NuGet.RemoveSource + +Remove a NuGet package source + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | + +--- + + +### NuGet.Restore + +Restore all NuGet packages from packages.config + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `packageCount` | `int` | + +--- + + +### NuGet.Uninstall + +Uninstall a NuGet package by id + +**Parameters:** + +| Field | Type | +|---|---| +| `id` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `id` | `string` | + +--- + + +## PackageManager + + +### PackageManager.Add + +Add a package by identifier (e.g., com.unity.foo@1.2.3 or git URL) + +**Parameters:** + +| Field | Type | +|---|---| +| `identifier` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `displayName` | `string` | +| `version` | `string` | +| `source` | `string` | + +--- + + +### PackageManager.GetInfo + +Get detailed information about a specific installed package + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `displayName` | `string` | +| `version` | `string` | +| `source` | `string` | +| `description` | `string` | +| `isDirectDependency` | `bool` | +| `latestVersion` | `string` | +| `dependencies` | `string[]` | + +--- + + +### PackageManager.List + +List all installed packages in the project + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `packages` | `PackageEntry[]` | +| `totalCount` | `int` | + +--- + + +### PackageManager.Remove + +Remove a package by name (e.g., com.unity.cinemachine) + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | + +--- + + +### PackageManager.Search + +Search for packages in the Unity registry + +**Parameters:** + +| Field | Type | +|---|---| +| `query` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `packages` | `PackageSearchEntry[]` | +| `totalCount` | `int` | + +--- + + +### PackageManager.Update + +Update a package to a specific version or the latest version + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `version` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `displayName` | `string` | +| `previousVersion` | `string` | +| `version` | `string` | +| `source` | `string` | + +--- + + +## PlayMode + + +### PlayMode.Enter + +Enter play mode in Unity Editor + +**Parameters:** None + +**Response:** None + +--- + + +### PlayMode.Exit + +Exit play mode in Unity Editor + +**Parameters:** None + +**Response:** None + +--- + + +### PlayMode.Pause + +Toggle pause state in play mode + +**Parameters:** None + +**Response:** None + +--- + + +### PlayMode.Status + +Get the current play mode state + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `isPlaying` | `bool` | +| `isPaused` | `bool` | +| `isCompiling` | `bool` | + +--- + + +## PlayerSettings + + +### PlayerSettings.Inspect + +Inspect all PlayerSettings values + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `companyName` | `string` | +| `productName` | `string` | +| `colorSpace` | `string` | +| `defaultScreenWidth` | `int` | +| `defaultScreenHeight` | `int` | +| `defaultWebScreenWidth` | `int` | +| `defaultWebScreenHeight` | `int` | +| `defaultIsNativeResolution` | `bool` | +| `macRetinaSupport` | `bool` | +| `runInBackground` | `bool` | +| `captureSingleScreen` | `bool` | +| `usePlayerLog` | `bool` | +| `resizableWindow` | `bool` | +| `resetResolutionOnWindowResize` | `bool` | +| `bakeCollisionMeshes` | `bool` | +| `useMacAppStoreValidation` | `bool` | +| `dedicatedServerOptimizations` | `bool` | +| `fullScreenMode` | `string` | +| `enable360StereoCapture` | `bool` | +| `stereoRenderingPath` | `string` | +| `enableFrameTimingStats` | `bool` | +| `enableOpenGLProfilerGPURecorders` | `bool` | +| `allowHDRDisplaySupport` | `bool` | +| `useHDRDisplay` | `bool` | +| `hdrBitDepth` | `string` | +| `visibleInBackground` | `bool` | +| `allowFullscreenSwitch` | `bool` | +| `forceSingleInstance` | `bool` | +| `useFlipModelSwapchain` | `bool` | +| `openGLRequireES31` | `bool` | +| `openGLRequireES31AEP` | `bool` | +| `openGLRequireES32` | `bool` | +| `spriteBatchVertexThreshold` | `int` | +| `suppressCommonWarnings` | `bool` | +| `allowUnsafeCode` | `bool` | +| `gcIncremental` | `bool` | +| `keystorePass` | `string` | +| `keyaliasPass` | `string` | +| `gpuSkinning` | `bool` | +| `graphicsJobs` | `bool` | +| `graphicsJobMode` | `string` | +| `xboxPIXTextureCapture` | `bool` | +| `xboxEnableAvatar` | `bool` | +| `xboxOneResolution` | `int` | +| `enableInternalProfiler` | `bool` | +| `actionOnDotNetUnhandledException` | `string` | +| `logObjCUncaughtExceptions` | `bool` | +| `enableCrashReportAPI` | `bool` | +| `applicationIdentifier` | `string` | +| `visionOSBundleVersion` | `string` | +| `tvOSBundleVersion` | `string` | +| `bundleVersion` | `string` | +| `statusBarHidden` | `bool` | +| `stripEngineCode` | `bool` | +| `defaultInterfaceOrientation` | `string` | +| `allowedAutorotateToPortrait` | `bool` | +| `allowedAutorotateToPortraitUpsideDown` | `bool` | +| `allowedAutorotateToLandscapeRight` | `bool` | +| `allowedAutorotateToLandscapeLeft` | `bool` | +| `useAnimatedAutorotation` | `bool` | +| `use32BitDisplayBuffer` | `bool` | +| `preserveFramebufferAlpha` | `bool` | +| `stripUnusedMeshComponents` | `bool` | +| `strictShaderVariantMatching` | `bool` | +| `mipStripping` | `bool` | +| `advancedLicense` | `bool` | +| `aotOptions` | `string` | +| `cursorHotspot` | `Vector2` | +| `accelerometerFrequency` | `int` | +| `mTRendering` | `bool` | +| `muteOtherAudioSources` | `bool` | +| `audioSpatialExperience` | `string` | +| `legacyClampBlendShapeWeights` | `bool` | +| `enableMetalAPIValidation` | `bool` | +| `windowsGamepadBackendHint` | `string` | +| `insecureHttpOption` | `string` | +| `vulkanEnableSetSRGBWrite` | `bool` | +| `vulkanNumSwapchainBuffers` | `UInt32` | +| `vulkanEnableLateAcquireNextImage` | `bool` | +| `vulkanEnablePreTransform` | `bool` | +| `android` | `AndroidSettings` | +| `iOS` | `iOSSettings` | +| `embeddedLinux` | `EmbeddedLinuxSettings` | +| `lumin` | `LuminSettings` | +| `macOS` | `macOSSettings` | +| `pS4` | `PS4Settings` | +| `qNX` | `QNXSettings` | +| `splashScreen` | `SplashScreenSettings` | +| `switch` | `SwitchSettings` | +| `tvOS` | `tvOSSettings` | +| `visionOS` | `VisionOSSettings` | +| `webGL` | `WebGLSettings` | +| `wSA` | `WSASettings` | +| `xboxOne` | `XboxOneSettings` | + +--- + + +## Profiler + + +### Profiler.AnalyzeFrames + +Analyze recorded frames and return aggregate statistics + +**Parameters:** + +| Field | Type | +|---|---| +| `startFrame` | `int` (default: `-1`) | +| `endFrame` | `int` (default: `-1`) | +| `topSampleCount` | `int` (default: `10`) | +| `sampleNameFilter` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `analyzedFrameCount` | `int` | +| `startFrame` | `int` | +| `endFrame` | `int` | +| `frameTime` | `FrameTimeStats` | +| `gcAlloc` | `GcAllocStats` | +| `topSamples` | `SampleStats[]` | + +--- + + +### Profiler.FindSpikes + +Find frames exceeding frame time or GC allocation thresholds + +**Parameters:** + +| Field | Type | +|---|---| +| `startFrame` | `int` (default: `-1`) | +| `endFrame` | `int` (default: `-1`) | +| `frameTimeThresholdMs` | `float` | +| `gcThresholdBytes` | `Int64` | +| `limit` | `int` (default: `20`) | +| `samplesPerFrame` | `int` (default: `5`) | + +**Response:** + +| Field | Type | +|---|---| +| `searchedFrameCount` | `int` | +| `startFrame` | `int` | +| `endFrame` | `int` | +| `totalSpikeCount` | `int` | +| `spikes` | `SpikeFrame[]` | + +--- + + +### Profiler.GetFrameData + +Get CPU profiler sample data for a specific frame + +**Parameters:** + +| Field | Type | +|---|---| +| `frame` | `int` (default: `-1`) | +| `limit` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `frameIndex` | `int` | +| `frameTimeMs` | `float` | +| `totalSampleCount` | `int` | +| `samples` | `ProfilerSampleInfo[]` | + +--- + + +### Profiler.Inspect + +Get profiler status and memory statistics + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `enabled` | `bool` | +| `deepProfiling` | `bool` | +| `profileEditor` | `bool` | +| `firstFrameIndex` | `int` | +| `lastFrameIndex` | `int` | +| `frameCount` | `int` | +| `totalAllocatedMemory` | `Int64` | +| `totalReservedMemory` | `Int64` | +| `monoHeapSize` | `Int64` | +| `monoUsedSize` | `Int64` | +| `graphicsMemory` | `Int64` | + +--- + + +### Profiler.LoadProfile + +Load profiler data from a .raw file + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `keepExistingData` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `firstFrameIndex` | `int` | +| `lastFrameIndex` | `int` | +| `frameCount` | `int` | + +--- + + +### Profiler.SaveProfile + +Save profiler data to a .raw file + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `size` | `Int64` | + +--- + + +### Profiler.StartRecording + +Start profiler recording + +**Parameters:** + +| Field | Type | +|---|---| +| `deep` | `bool` | +| `editor` | `bool` | +| `keepFrames` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `enabled` | `bool` | +| `deepProfiling` | `bool` | +| `profileEditor` | `bool` | + +--- + + +### Profiler.StopRecording + +Stop profiler recording + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `firstFrameIndex` | `int` | +| `lastFrameIndex` | `int` | +| `frameCount` | `int` | + +--- + + +### Profiler.TakeSnapshot + +Take a memory snapshot (.snap file) + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `size` | `Int64` | + +--- + + +## Project + + +### Project.Inspect + +Get Unity project information + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `unityVersion` | `string` | +| `projectPath` | `string` | +| `productName` | `string` | +| `companyName` | `string` | +| `buildTarget` | `string` | +| `isPlaying` | `bool` | +| `processId` | `int` | +| `serverId` | `string` | +| `serverVersion` | `string` | +| `startedAt` | `string` | +| `uptimeSeconds` | `double` | + +--- + + +## Recorder + + +### Recorder.StartRecording + +Start recording the Game View as a video (requires Play Mode) + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `format` | `string` | +| `width` | `int` | +| `height` | `int` | +| `frameRate` | `float` | +| `quality` | `string` | +| `captureAudio` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `format` | `string` | +| `width` | `int` | +| `height` | `int` | +| `frameRate` | `float` | + +--- + + +### Recorder.Status + +Get the current recording status + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `isRecording` | `bool` | + +--- + + +### Recorder.StopRecording + +Stop the current video recording + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `size` | `Int64` | + +--- + + +## Remote + + +### Connection.Connect + +Connect to a target player/device by ID, IP address, or device ID + +**Parameters:** + +| Field | Type | +|---|---| +| `id` | `int` | +| `ip` | `string` | +| `deviceId` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `id` | `int` | +| `name` | `string` | +| `directConnectionUrl` | `string` | + +--- + + +### Connection.List + +List available connection targets (players/devices) + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `targets` | `ConnectionTarget[]` | + +--- + + +### Connection.Status + +Get current profiler connection status + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `id` | `int` | +| `name` | `string` | +| `directConnectionUrl` | `string` | + +--- + + +### Remote.Invoke + +Invoke a debug command on connected runtime player + +**Parameters:** + +| Field | Type | +|---|---| +| `command` | `string` | +| `data` | `string` | +| `playerId` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `command` | `string` | +| `success` | `bool` | +| `message` | `string` | +| `data` | `string` | + +--- + + +### Remote.List + +List debug commands registered on connected runtime player + +**Parameters:** + +| Field | Type | +|---|---| +| `playerId` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `commands` | `RuntimeCommandInfo[]` | + +--- + + +## Scene + + +### Scene.Close + +Close a loaded scene via EditorSceneManager + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `sceneIndex` | `int` (default: `-1`) | +| `removeScene` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `removed` | `bool` | + +--- + + +### Scene.GetActive + +Get the active scene via SceneManager + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `buildIndex` | `int` | +| `isDirty` | `bool` | +| `isLoaded` | `bool` | +| `rootCount` | `int` | + +--- + + +### Scene.List + +List all loaded scenes via SceneManager + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `scenes` | `SceneInfo[]` | +| `activeSceneName` | `string` | + +--- + + +### Scene.New + +Create a new scene via EditorSceneManager + +**Parameters:** + +| Field | Type | +|---|---| +| `empty` | `bool` | +| `additive` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `buildIndex` | `int` | +| `isDirty` | `bool` | +| `isLoaded` | `bool` | +| `rootCount` | `int` | + +--- + + +### Scene.Open + +Open a scene by asset path via EditorSceneManager + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `additive` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `buildIndex` | `int` | +| `isDirty` | `bool` | +| `isLoaded` | `bool` | +| `rootCount` | `int` | + +--- + + +### Scene.Save + +Save a scene or all open scenes via EditorSceneManager + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `sceneIndex` | `int` (default: `-1`) | +| `saveAsPath` | `string` | +| `all` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `savedScenePaths` | `string[]` | +| `savedCount` | `int` | + +--- + + +### Scene.SetActive + +Set the active scene via SceneManager + +**Parameters:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `sceneIndex` | `int` (default: `-1`) | + +**Response:** + +| Field | Type | +|---|---| +| `name` | `string` | +| `path` | `string` | +| `buildIndex` | `int` | +| `isDirty` | `bool` | +| `isLoaded` | `bool` | +| `rootCount` | `int` | + +--- + + +## Screenshot + + +### Screenshot.Capture + +Capture a screenshot of the Game View and save as PNG (requires Play Mode) + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `superSize` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `width` | `int` | +| `height` | `int` | +| `size` | `Int64` | + +--- + + +## Search + + +### Search + +Search Unity project using Unity Search API + +**Parameters:** + +| Field | Type | +|---|---| +| `query` | `string` | +| `provider` | `string` | +| `maxResults` | `int` (default: `50`) | +| `includePackages` | `bool` | + +**Response:** + +| Field | Type | +|---|---| +| `results` | `SearchResultItem[]` | +| `totalCount` | `int` | +| `displayedCount` | `int` | +| `query` | `string` | + +--- + + +## Selection + + +### Selection.Get + +Get the current selection in the editor + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `gameObjects` | `SelectedGameObjectInfo[]` | +| `assets` | `SelectedAssetInfo[]` | + +--- + + +### Selection.SetAsset + +Select an asset by path + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `assetPath` | `string` | +| `typeName` | `string` | +| `name` | `string` | + +--- + + +### Selection.SetAssets + +Select multiple assets by paths + +**Parameters:** + +| Field | Type | +|---|---| +| `assetPaths` | `string[]` | + +**Response:** + +| Field | Type | +|---|---| +| `selected` | `SelectedAssetInfo[]` | +| `notFound` | `string[]` | + +--- + + +### Selection.SetGameObject + +Select a GameObject by path + +**Parameters:** + +| Field | Type | +|---|---| +| `path` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `path` | `string` | +| `instanceId` | `int` | + +--- + + +### Selection.SetGameObjects + +Select multiple GameObjects by paths + +**Parameters:** + +| Field | Type | +|---|---| +| `paths` | `string[]` | + +**Response:** + +| Field | Type | +|---|---| +| `selected` | `SelectedGameObjectInfo[]` | +| `notFound` | `string[]` | + +--- + + +## TestRunner + + +### TestRunner.List + +List available tests for EditMode or PlayMode + +**Parameters:** + +| Field | Type | +|---|---| +| `mode` | `string` (default: `EditMode`) | + +**Response:** + +| Field | Type | +|---|---| +| `mode` | `string` | +| `total` | `int` | +| `tests` | `TestListEntry[]` | + +--- + + +### TestRunner.RunEditMode + +Run EditMode tests with optional name/assembly filter + +**Parameters:** + +| Field | Type | +|---|---| +| `testNames` | `string[]` | +| `groupNames` | `string[]` | +| `categories` | `string[]` | +| `assemblies` | `string[]` | +| `resultFilter` | `string` (default: `failures`) | +| `stackTraceLines` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `passed` | `int` | +| `failed` | `int` | +| `skipped` | `int` | +| `total` | `int` | +| `results` | `TestResult[]` | + +--- + + +### TestRunner.RunPlayMode + +Run PlayMode tests with optional name/assembly filter + +**Parameters:** + +| Field | Type | +|---|---| +| `testNames` | `string[]` | +| `groupNames` | `string[]` | +| `categories` | `string[]` | +| `assemblies` | `string[]` | +| `resultFilter` | `string` (default: `failures`) | +| `stackTraceLines` | `int` | + +**Response:** + +| Field | Type | +|---|---| +| `passed` | `int` | +| `failed` | `int` | +| `skipped` | `int` | +| `total` | `int` | +| `results` | `TestResult[]` | + +--- + + +## Type + + +### Type.Inspect + +Inspect nested types of a given type + +**Parameters:** + +| Field | Type | +|---|---| +| `typeName` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `typeName` | `string` | +| `nestedTypes` | `TypeInspectNestedInfo[]` | +| `count` | `int` | + +--- + + +### Type.List + +List types derived from a base type or matching a pattern + +**Parameters:** + +| Field | Type | +|---|---| +| `baseType` | `string` | +| `filter` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `types` | `string[]` | +| `count` | `int` | + +--- + + +## Window + + +### Window.Create + +Create a new EditorWindow instance by type name + +**Parameters:** + +| Field | Type | +|---|---| +| `typeName` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `typeName` | `string` | +| `instanceId` | `int` | + +--- + + +### Window.Focus + +Focus an already-open EditorWindow by type name + +**Parameters:** + +| Field | Type | +|---|---| +| `typeName` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `typeName` | `string` | + +--- + + +### Window.List + +List all available EditorWindow types + +**Parameters:** None + +**Response:** + +| Field | Type | +|---|---| +| `windows` | `WindowInfo[]` | + +--- + + +### Window.Open + +Open an EditorWindow by type name + +**Parameters:** + +| Field | Type | +|---|---| +| `typeName` | `string` | + +**Response:** + +| Field | Type | +|---|---| +| `typeName` | `string` | + +--- + + +``` + +`samples/UniCli.Samples.Unity2022LTS/Assets/Scenes.meta`: + +```meta +fileFormatVersion: 2 +guid: c227c512c1a8e47319bb32c8140b5b96 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity2022LTS/Assets/Scenes/SampleScene.unity`: + +```unity +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 8 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_GateFitMode: 2 + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + +``` + +`samples/UniCli.Samples.Unity2022LTS/Assets/Scenes/SampleScene.unity.meta`: + +```meta +fileFormatVersion: 2 +guid: 9fc0d4010bbf28b4594072e72b8655ab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity2022LTS/Packages/manifest.json`: + +```json +{ + "dependencies": { + "com.unity.collab-proxy": "2.11.3", + "com.unity.feature.development": "1.0.1", + "com.unity.textmeshpro": "3.0.7", + "com.unity.timeline": "1.8.10", + "com.unity.ugui": "1.0.0", + "com.unity.visualscripting": "1.9.4", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0", + "jp.co.cyberagent.buildmagic": "https://github.com/CyberAgentGameEntertainment/BuildMagic.git?path=/Packages/jp.co.cyberagent.buildmagic", + "com.yucchiy.unicli-server": "file:../../../src/UniCli.Unity/Packages/com.yucchiy.unicli-server" + } +} + +``` + +`samples/UniCli.Samples.Unity2022LTS/Packages/packages-lock.json`: + +```json +{ + "dependencies": { + "com.unity.collab-proxy": { + "version": "2.11.3", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.editorcoroutines": { + "version": "1.0.0", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "1.0.6", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.feature.development": { + "version": "1.0.1", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.ide.visualstudio": "2.0.22", + "com.unity.ide.rider": "3.0.36", + "com.unity.ide.vscode": "1.2.5", + "com.unity.editorcoroutines": "1.0.0", + "com.unity.performance.profile-analyzer": "1.2.3", + "com.unity.test-framework": "1.1.33", + "com.unity.testtools.codecoverage": "1.2.6" + } + }, + "com.unity.ide.rider": { + "version": "3.0.36", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.22", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.performance.profile-analyzer": { + "version": "1.2.3", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.settings-manager": { + "version": "2.1.0", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.test-framework": { + "version": "1.1.33", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.testtools.codecoverage": { + "version": "1.2.6", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.0.16", + "com.unity.settings-manager": "1.0.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.textmeshpro": { + "version": "3.0.7", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.8.10", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.visualscripting": { + "version": "1.9.4", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.yucchiy.unicli-server": { + "version": "file:../../../src/UniCli.Unity/Packages/com.yucchiy.unicli-server", + "depth": 0, + "source": "local", + "dependencies": {} + }, + "jp.co.cyberagent.buildmagic": { + "version": "https://github.com/CyberAgentGameEntertainment/BuildMagic.git?path=/Packages/jp.co.cyberagent.buildmagic", + "depth": 0, + "source": "git", + "dependencies": {}, + "hash": "98b119d519dcca44c17d1868257a90b8c88d55cc" + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/AudioManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 1024 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 + m_RequestedDSPBufferSize: 1024 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/BuildMagic/Dev.json`: + +```json +{ + "_name": "Dev", + "_postBuildConfigurations": [], + "_internalPrepareConfigurations": [], + "_preBuildConfigurations": [ + { + "rid": 1000 + } + ], + "_baseSchemeName": "", + "references": { + "version": 2, + "RefIds": [ + { + "rid": 1000, + "type": { + "class": "EditorUserBuildSettingsSetDevelopmentTaskConfiguration", + "ns": "BuildMagicEditor.BuiltIn", + "asm": "BuildMagic.Editor" + }, + "data": { + "_value": true + } + } + ] + } +} +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/BuildMagic/Prd.json`: + +```json +{ + "_name": "Prd", + "_postBuildConfigurations": [], + "_internalPrepareConfigurations": [], + "_preBuildConfigurations": [ + { + "rid": 1000 + } + ], + "_baseSchemeName": "", + "references": { + "version": 2, + "RefIds": [ + { + "rid": 1000, + "type": { + "class": "EditorUserBuildSettingsSetDevelopmentTaskConfiguration", + "ns": "BuildMagicEditor.BuiltIn", + "asm": "BuildMagic.Editor" + }, + "data": { + "_value": false + } + } + ] + } +} +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/ClusterInputManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/DynamicsManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_ClothInterCollisionDistance: 0 + m_ClothInterCollisionStiffness: 0 + m_ContactsGeneration: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 + m_AutoSyncTransforms: 0 + m_ReuseCollisionCallbacks: 1 + m_ClothInterCollisionSettingsToggle: 0 + m_ContactPairsMode: 0 + m_BroadphaseType: 0 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 250, y: 250, z: 250} + m_WorldSubdivisions: 8 + m_FrictionType: 0 + m_EnableEnhancedDeterminism: 0 + m_EnableUnifiedHeightmaps: 1 + m_DefaultMaxAngluarSpeed: 7 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/EditorBuildSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: [] + m_configObjects: {} + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/EditorSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_ExternalVersionControlSupport: Visible Meta Files + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 0 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerPaddingPower: 1 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref + m_ProjectGenerationRootNamespace: + m_CollabEditorSettings: + inProgressEnabled: 1 + m_EnableTextureStreamingInEditMode: 1 + m_EnableTextureStreamingInPlayMode: 1 + m_AsyncShaderCompilation: 1 + m_EnterPlayModeOptionsEnabled: 0 + m_EnterPlayModeOptions: 3 + m_ShowLightmapResolutionOverlay: 1 + m_UseLegacyProbeSampleCount: 0 + m_SerializeInlineMappingsOnOneLine: 1 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/GraphicsSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_LegacyDeferred: + m_Mode: 1 + m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, + type: 0} + m_CustomRenderPipeline: {fileID: 0} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_LightsUseLinearIntensity: 0 + m_LightsUseColorTemperature: 0 + m_LogWhenShaderIsCompiled: 0 + m_AllowEnlightenSupportForUpgradedProject: 0 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/InputManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/MemorySettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/NavMeshAreas.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_SettingNames: + - Humanoid + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/PackageManagerSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_EnablePreReleasePackages: 0 + m_EnablePackageDependencies: 0 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + oneTimeWarningShown: 0 + m_Registries: + - m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_Capabilities: 7 + m_UserSelectedRegistryName: + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_Modified: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -830 + m_OriginalInstanceId: -832 + m_LoadAssets: 0 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json`: + +```json +{ + "m_Dictionary": { + "m_DictionaryValues": [] + } +} +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/Physics2DSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_JobOptions: + serializedVersion: 2 + useMultithreading: 0 + useConsistencySorting: 0 + m_InterpolationPosesPerJob: 100 + m_NewContactsPerJob: 30 + m_CollideContactsPerJob: 100 + m_ClearFlagsPerJob: 200 + m_ClearBodyForcesPerJob: 200 + m_SyncDiscreteFixturesPerJob: 50 + m_SyncContinuousFixturesPerJob: 50 + m_FindNearestContactsPerJob: 100 + m_UpdateTriggerContactsPerJob: 100 + m_IslandSolverCostThreshold: 100 + m_IslandSolverBodyCostScale: 1 + m_IslandSolverContactCostScale: 10 + m_IslandSolverJointCostScale: 10 + m_IslandSolverBodiesPerJob: 50 + m_IslandSolverContactsPerJob: 50 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_CallbacksOnDisable: 1 + m_ReuseCollisionCallbacks: 1 + m_AutoSyncTransforms: 0 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/PresetManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1386491679 &1 +PresetManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_DefaultPresets: {} + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/ProjectSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 26 + productGUID: c174ec5a37465436b89f1c091b62d921 + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: UniCli.Samples.Unity2022LTS + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1920 + defaultScreenHeight: 1080 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 1 + unsupportedMSAAFallback: 0 + m_SpriteBatchVertexThreshold: 300 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 1 + androidDisplayOptions: 1 + androidBlitType: 0 + androidResizableWindow: 0 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 1 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 1 + captureSingleScreen: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + dedicatedServerOptimizations: 0 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 1 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 0 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchNVNGraphicsFirmwareMemory: 32 + switchMaxWorkerMultiple: 8 + stadiaPresentMode: 0 + stadiaTargetFramerate: 0 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 1 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 + bundleVersion: 0.1 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 + useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 + applicationIdentifier: {} + buildNumber: + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 0 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 22 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + AndroidPreferredDataLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + APKExpansionFiles: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 1 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 12.0 + tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 12.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreenCustomXibPath: + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreeniPadCustomXibPath: + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + metalCompileShaderBinary: 0 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: c0afd0d1d80e3634a9dac47e8a0426ea + templatePackageId: com.unity.template.3d@8.1.3 + templateDefaultScene: Assets/Scenes/SampleScene.unity + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 1 + AndroidTargetDevices: 0 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + chromeosInputEmulation: 1 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + m_BuildTargetBatching: + - m_BuildTarget: Standalone + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: tvOS + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: Android + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: iPhone + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: WebGL + m_StaticBatching: 0 + m_DynamicBatching: 0 + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: + - m_BuildTarget: MacStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: Switch + m_GraphicsJobs: 1 + - m_BuildTarget: MetroSupport + m_GraphicsJobs: 1 + - m_BuildTarget: AppleTVSupport + m_GraphicsJobs: 0 + - m_BuildTarget: BJMSupport + m_GraphicsJobs: 1 + - m_BuildTarget: LinuxStandaloneSupport + m_GraphicsJobs: 1 + - m_BuildTarget: PS4Player + m_GraphicsJobs: 1 + - m_BuildTarget: iOSSupport + m_GraphicsJobs: 0 + - m_BuildTarget: WindowsStandaloneSupport + m_GraphicsJobs: 1 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobs: 1 + - m_BuildTarget: LuminSupport + m_GraphicsJobs: 0 + - m_BuildTarget: AndroidPlayer + m_GraphicsJobs: 0 + - m_BuildTarget: WebGLSupport + m_GraphicsJobs: 0 + m_BuildTargetGraphicsJobMode: + - m_BuildTarget: PS4Player + m_GraphicsJobMode: 0 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobMode: 0 + m_BuildTargetGraphicsAPIs: + - m_BuildTarget: AndroidPlayer + m_APIs: 150000000b000000 + m_Automatic: 1 + - m_BuildTarget: iOSSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: AppleTVSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: WebGLSupport + m_APIs: 0b000000 + m_Automatic: 1 + m_BuildTargetVRSettings: + - m_BuildTarget: Standalone + m_Enabled: 0 + m_Devices: + - Oculus + - OpenVR + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - m_BuildTarget: Android + m_EncodingQuality: 1 + - m_BuildTarget: iPhone + m_EncodingQuality: 1 + - m_BuildTarget: tvOS + m_EncodingQuality: 1 + m_BuildTargetGroupHDRCubemapEncodingQuality: + - m_BuildTarget: Android + m_EncodingQuality: 1 + - m_BuildTarget: iPhone + m_EncodingQuality: 1 + - m_BuildTarget: tvOS + m_EncodingQuality: 1 + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: + - m_BuildTarget: Android + m_Encoding: 1 + - m_BuildTarget: iPhone + m_Encoding: 1 + - m_BuildTarget: tvOS + m_Encoding: 1 + m_BuildTargetDefaultTextureCompressionFormat: + - m_BuildTarget: Android + m_Format: 3 + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 10.13.0 + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 0 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 + switchUseNewStyleFilepaths: 1 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 1 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 0 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 16 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 1 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 + scriptingDefineSymbols: {} + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: {} + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + managedStrippingLevel: + EmbeddedLinux: 1 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + QNX: 1 + Stadia: 1 + VisionOS: 1 + WebGL: 1 + Windows Store Apps: 1 + XboxOne: 1 + iPhone: 1 + tvOS: 1 + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: UniCli.Samples.Unity2022LTS + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: UniCli.Samples.Unity2022LTS + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} + metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} + cloudServicesEnabled: + UNet: 1 + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 1 + hmiLogStartupTiming: 0 + hmiCpuConfiguration: + apiCompatibilityLevel: 6 + activeInputHandler: 0 + windowsGamepadBackendHint: 0 + cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: + organizationId: + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/ProjectVersion.txt`: + +```txt +m_EditorVersion: 2022.3.62f3 +m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7) + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/QualitySettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 5 + m_QualitySettings: + - serializedVersion: 2 + name: Very Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 1 + textureQuality: 1 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.3 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 4 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.4 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 16 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Medium + pixelLightCount: 1 + shadows: 1 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 1 + lodBias: 0.7 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 64 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: High + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Very High + pixelLightCount: 3 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 70 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1.5 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 1024 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Ultra + pixelLightCount: 4 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 4 + shadowDistance: 150 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 2 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 4096 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + m_PerPlatformDefaultQuality: + Android: 2 + Lumin: 5 + GameCoreScarlett: 5 + GameCoreXboxOne: 5 + Nintendo 3DS: 5 + Nintendo Switch: 5 + PS4: 5 + PS5: 5 + Stadia: 5 + Standalone: 5 + WebGL: 3 + Windows Store Apps: 5 + XboxOne: 5 + iPhone: 2 + tvOS: 2 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/TagManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/TimeManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/UnityConnectSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 1 + m_Enabled: 0 + m_TestMode: 0 + m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events + m_EventUrl: https://cdp.cloud.unity3d.com/v1/events + m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com + m_TestInitMode: 0 + CrashReportingSettings: + m_EventUrl: https://perf-events.cloud.unity3d.com + m_Enabled: 0 + m_LogBufferSize: 10 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_TestMode: 0 + m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/VFXManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!937362698 &1 +VFXManager: + m_ObjectHideFlags: 0 + m_IndirectShader: {fileID: 0} + m_CopyBufferShader: {fileID: 0} + m_SortShader: {fileID: 0} + m_StripUpdateShader: {fileID: 0} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/VersionControlSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 + +``` + +`samples/UniCli.Samples.Unity2022LTS/ProjectSettings/XRSettings.asset`: + +```asset +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/InputSystem_Actions.inputactions`: + +```inputactions +{ + "name": "InputSystem_Actions", + "maps": [ + { + "name": "Player", + "id": "df70fa95-8a34-4494-b137-73ab6b9c7d37", + "actions": [ + { + "name": "Move", + "type": "Value", + "id": "351f2ccd-1f9f-44bf-9bec-d62ac5c5f408", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Look", + "type": "Value", + "id": "6b444451-8a00-4d00-a97e-f47457f736a8", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Attack", + "type": "Button", + "id": "6c2ab1b8-8984-453a-af3d-a3c78ae1679a", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Interact", + "type": "Button", + "id": "852140f2-7766-474d-8707-702459ba45f3", + "expectedControlType": "Button", + "processors": "", + "interactions": "Hold", + "initialStateCheck": false + }, + { + "name": "Crouch", + "type": "Button", + "id": "27c5f898-bc57-4ee1-8800-db469aca5fe3", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Jump", + "type": "Button", + "id": "f1ba0d36-48eb-4cd5-b651-1c94a6531f70", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Previous", + "type": "Button", + "id": "2776c80d-3c14-4091-8c56-d04ced07a2b0", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Next", + "type": "Button", + "id": "b7230bb6-fc9b-4f52-8b25-f5e19cb2c2ba", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Sprint", + "type": "Button", + "id": "641cd816-40e6-41b4-8c3d-04687c349290", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "", + "id": "978bfe49-cc26-4a3d-ab7b-7d7a29327403", + "path": "/leftStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "WASD", + "id": "00ca640b-d935-4593-8157-c05846ea39b3", + "path": "Dpad", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "e2062cb9-1b15-46a2-838c-2f8d72a0bdd9", + "path": "/w", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "8180e8bd-4097-4f4e-ab88-4523101a6ce9", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "320bffee-a40b-4347-ac70-c210eb8bc73a", + "path": "/s", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "1c5327b5-f71c-4f60-99c7-4e737386f1d1", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "d2581a9b-1d11-4566-b27d-b92aff5fabbc", + "path": "/a", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "2e46982e-44cc-431b-9f0b-c11910bf467a", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcfe95b8-67b9-4526-84b5-5d0bc98d6400", + "path": "/d", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "77bff152-3580-4b21-b6de-dcd0c7e41164", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "1635d3fe-58b6-4ba9-a4e2-f4b964f6b5c8", + "path": "/{Primary2DAxis}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3ea4d645-4504-4529-b061-ab81934c3752", + "path": "/stick", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c1f7a91b-d0fd-4a62-997e-7fb9b69bf235", + "path": "/rightStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8c8e490b-c610-4785-884f-f04217b23ca4", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse;Touch", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3e5f5442-8668-4b27-a940-df99bad7e831", + "path": "/{Hatswitch}", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "143bb1cd-cc10-4eca-a2f0-a3664166fe91", + "path": "/buttonWest", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "05f6913d-c316-48b2-a6bb-e225f14c7960", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "886e731e-7071-4ae4-95c0-e61739dad6fd", + "path": "/primaryTouch/tap", + "interactions": "", + "processors": "", + "groups": ";Touch", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "ee3d0cd2-254e-47a7-a8cb-bc94d9658c54", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8255d333-5683-4943-a58a-ccb207ff1dce", + "path": "/{PrimaryAction}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3c1c7f0-bd20-4ee7-a0f1-899b24bca6d7", + "path": "/enter", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "cbac6039-9c09-46a1-b5f2-4e5124ccb5ed", + "path": "/2", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e15ca19d-e649-4852-97d5-7fe8ccc44e94", + "path": "/dpad/right", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "f2e9ba44-c423-42a7-ad56-f20975884794", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8cbb2f4b-a784-49cc-8d5e-c010b8c7f4e6", + "path": "/leftStickPress", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "d8bf24bf-3f2f-4160-a97c-38ec1eb520ba", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "eb40bb66-4559-4dfa-9a2f-820438abb426", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "daba33a1-ad0c-4742-a909-43ad1cdfbeb6", + "path": "/buttonSouth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "603f3daf-40bd-4854-8724-93e8017f59e3", + "path": "/secondaryButton", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1534dc16-a6aa-499d-9c3a-22b47347b52a", + "path": "/1", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "25060bbd-a3a6-476e-8fba-45ae484aad05", + "path": "/dpad/left", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1c04ea5f-b012-41d1-a6f7-02e963b52893", + "path": "/e", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3f66d0b-7751-423f-908b-a11c5bd95930", + "path": "/buttonNorth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4f4649ac-64a8-4a73-af11-b3faef356a4d", + "path": "/buttonEast", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "36e52cba-0905-478e-a818-f4bfcb9f3b9a", + "path": "/c", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + } + ] + }, + { + "name": "UI", + "id": "272f6d14-89ba-496f-b7ff-215263d3219f", + "actions": [ + { + "name": "Navigate", + "type": "PassThrough", + "id": "c95b2375-e6d9-4b88-9c4c-c5e76515df4b", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Submit", + "type": "Button", + "id": "7607c7b6-cd76-4816-beef-bd0341cfe950", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Cancel", + "type": "Button", + "id": "15cef263-9014-4fd5-94d9-4e4a6234a6ef", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Point", + "type": "PassThrough", + "id": "32b35790-4ed0-4e9a-aa41-69ac6d629449", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Click", + "type": "PassThrough", + "id": "3c7022bf-7922-4f7c-a998-c437916075ad", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "RightClick", + "type": "PassThrough", + "id": "44b200b1-1557-4083-816c-b22cbdf77ddf", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "MiddleClick", + "type": "PassThrough", + "id": "dad70c86-b58c-4b17-88ad-f5e53adf419e", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "ScrollWheel", + "type": "PassThrough", + "id": "0489e84a-4833-4c40-bfae-cea84b696689", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDevicePosition", + "type": "PassThrough", + "id": "24908448-c609-4bc3-a128-ea258674378a", + "expectedControlType": "Vector3", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDeviceOrientation", + "type": "PassThrough", + "id": "9caa3d8a-6b2f-4e8e-8bad-6ede561bd9be", + "expectedControlType": "Quaternion", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "Gamepad", + "id": "809f371f-c5e2-4e7a-83a1-d867598f40dd", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "14a5d6e8-4aaf-4119-a9ef-34b8c2c548bf", + "path": "/leftStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "9144cbe6-05e1-4687-a6d7-24f99d23dd81", + "path": "/rightStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2db08d65-c5fb-421b-983f-c71163608d67", + "path": "/leftStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "58748904-2ea9-4a80-8579-b500e6a76df8", + "path": "/rightStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "8ba04515-75aa-45de-966d-393d9bbd1c14", + "path": "/leftStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "712e721c-bdfb-4b23-a86c-a0d9fcfea921", + "path": "/rightStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcd248ae-a788-4676-a12e-f4d81205600b", + "path": "/leftStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "1f04d9bc-c50b-41a1-bfcc-afb75475ec20", + "path": "/rightStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "fb8277d4-c5cd-4663-9dc7-ee3f0b506d90", + "path": "/dpad", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "Joystick", + "id": "e25d9774-381c-4a61-b47c-7b6b299ad9f9", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "3db53b26-6601-41be-9887-63ac74e79d19", + "path": "/stick/up", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "0cb3e13e-3d90-4178-8ae6-d9c5501d653f", + "path": "/stick/down", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "0392d399-f6dd-4c82-8062-c1e9c0d34835", + "path": "/stick/left", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "942a66d9-d42f-43d6-8d70-ecb4ba5363bc", + "path": "/stick/right", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "Keyboard", + "id": "ff527021-f211-4c02-933e-5976594c46ed", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "563fbfdd-0f09-408d-aa75-8642c4f08ef0", + "path": "/w", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "eb480147-c587-4a33-85ed-eb0ab9942c43", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2bf42165-60bc-42ca-8072-8c13ab40239b", + "path": "/s", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "85d264ad-e0a0-4565-b7ff-1a37edde51ac", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "74214943-c580-44e4-98eb-ad7eebe17902", + "path": "/a", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "cea9b045-a000-445b-95b8-0c171af70a3b", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "8607c725-d935-4808-84b1-8354e29bab63", + "path": "/d", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "4cda81dc-9edd-4e03-9d7c-a71a14345d0b", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "9e92bb26-7e3b-4ec4-b06b-3c8f8e498ddc", + "path": "*/{Submit}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Submit", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "82627dcc-3b13-4ba9-841d-e4b746d6553e", + "path": "*/{Cancel}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Cancel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c52c8e0b-8179-41d3-b8a1-d149033bbe86", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e1394cbc-336e-44ce-9ea8-6007ed6193f7", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "5693e57a-238a-46ed-b5ae-e64e6e574302", + "path": "/touch*/position", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4faf7dc9-b979-4210-aa8c-e808e1ef89f5", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8d66d5ba-88d7-48e6-b1cd-198bbfef7ace", + "path": "/tip", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "47c2a644-3ebc-4dae-a106-589b7ca75b59", + "path": "/touch*/press", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "bb9e6b34-44bf-4381-ac63-5aa15d19f677", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "38c99815-14ea-4617-8627-164d27641299", + "path": "/scroll", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "ScrollWheel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4c191405-5738-4d4b-a523-c6a301dbf754", + "path": "/rightButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "RightClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "24066f69-da47-44f3-a07e-0015fb02eb2e", + "path": "/middleButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "MiddleClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "7236c0d9-6ca3-47cf-a6ee-a97f5b59ea77", + "path": "/devicePosition", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDevicePosition", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "23e01e3a-f935-4948-8d8b-9bcac77714fb", + "path": "/deviceRotation", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDeviceOrientation", + "isComposite": false, + "isPartOfComposite": false + } + ] + } + ], + "controlSchemes": [ + { + "name": "Keyboard&Mouse", + "bindingGroup": "Keyboard&Mouse", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + }, + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Gamepad", + "bindingGroup": "Gamepad", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Touch", + "bindingGroup": "Touch", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Joystick", + "bindingGroup": "Joystick", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "XR", + "bindingGroup": "XR", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + } + ] +} +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/InputSystem_Actions.inputactions.meta`: + +```meta +fileFormatVersion: 2 +guid: 052faaac586de48259a63d0c4782560b +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 0 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Readme.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fcf7219bab7fe46a1ad266029b2fee19, type: 3} + m_Name: Readme + m_EditorClassIdentifier: + icon: {fileID: 2800000, guid: 727a75301c3d24613a3ebcec4a24c2c8, type: 3} + title: URP Empty Template + sections: + - heading: Welcome to the Universal Render Pipeline + text: This template includes the settings and assets you need to start creating with the Universal Render Pipeline. + linkText: + url: + - heading: URP Documentation + text: + linkText: Read more about URP + url: https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest + - heading: Forums + text: + linkText: Get answers and support + url: https://forum.unity.com/forums/universal-render-pipeline.383/ + - heading: Report bugs + text: + linkText: Submit a report + url: https://unity3d.com/unity/qa/bug-reporting + loadedLayout: 1 + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Readme.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: 8105016687592461f977c054a80ce2f2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Scenes.meta`: + +```meta +fileFormatVersion: 2 +guid: 2be0bc28a205443109fcdfadac8dd556 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Scenes/SampleScene.unity`: + +```unity +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &330585543 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 330585546} + - component: {fileID: 330585545} + - component: {fileID: 330585544} + - component: {fileID: 330585547} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &330585544 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 +--- !u!20 &330585545 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &330585546 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &330585547 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 1 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 + m_TaaSettings: + quality: 3 + frameInfluence: 0.1 + jitterScale: 1 + mipBias: 0 + varianceClampScale: 0.9 + contrastAdaptiveSharpening: 0 +--- !u!1 &410087039 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 410087041} + - component: {fileID: 410087040} + - component: {fileID: 410087042} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &410087040 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + m_Enabled: 1 + serializedVersion: 11 + m_Type: 1 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 2 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 5000 + m_UseColorTemperature: 1 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &410087041 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &410087042 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 3 + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_LightLayerMask: 1 + m_RenderingLayers: 1 + m_CustomShadowLayers: 0 + m_ShadowLayerMask: 1 + m_ShadowRenderingLayers: 1 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} + m_SoftShadowQuality: 1 +--- !u!1 &832575517 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 832575519} + - component: {fileID: 832575518} + m_Layer: 0 + m_Name: Global Volume + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &832575518 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 832575517} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IsGlobal: 1 + priority: 0 + blendDistance: 0 + weight: 1 + sharedProfile: {fileID: 11400000, guid: 10fc4df2da32a41aaa32d77bc913491c, type: 2} +--- !u!4 &832575519 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 832575517} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 330585546} + - {fileID: 410087041} + - {fileID: 832575519} + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Scenes/SampleScene.unity.meta`: + +```meta +fileFormatVersion: 2 +guid: 99c9720ab356a0642a771bea13969a05 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings.meta`: + +```meta +fileFormatVersion: 2 +guid: 709f11a7f3c4041caa4ef136ea32d874 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/DefaultVolumeProfile.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-9167874883656233139 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5485954d14dfb9a4c8ead8edb0ded5b1, type: 3} + m_Name: LiftGammaGain + m_EditorClassIdentifier: + active: 1 + lift: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + gamma: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + gain: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} +--- !u!114 &-8270506406425502121 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 70afe9e12c7a7ed47911bb608a23a8ff, type: 3} + m_Name: SplitToning + m_EditorClassIdentifier: + active: 1 + shadows: + m_OverrideState: 1 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + highlights: + m_OverrideState: 1 + m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} + balance: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-8104416584915340131 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: CopyPasteTestComponent2 + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEditor.Rendering.Tests:VolumeComponentCopyPasteTests/CopyPasteTestComponent2 + active: 1 + p1: + m_OverrideState: 1 + m_Value: 0 + p2: + m_OverrideState: 1 + m_Value: 0 + p21: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-7750755424749557576 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 60f3b30c03e6ba64d9a27dc9dba8f28d, type: 3} + m_Name: OutlineVolumeComponent + m_EditorClassIdentifier: + active: 1 + Enabled: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-7743500325797982168 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ccf1aba9553839d41ae37dd52e9ebcce, type: 3} + m_Name: MotionBlur + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + quality: + m_OverrideState: 1 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0 + clamp: + m_OverrideState: 1 + m_Value: 0.05 +--- !u!114 &-7274224791359825572 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0fd9ee276a1023e439cf7a9c393195fa, type: 3} + m_Name: TestAnimationCurveVolumeComponent + m_EditorClassIdentifier: + active: 1 + testParameter: + m_OverrideState: 1 + m_Value: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0.5 + value: 10 + inSlope: 0 + outSlope: 10 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 15 + inSlope: 10 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &-6335409530604852063 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3} + m_Name: ColorAdjustments + m_EditorClassIdentifier: + active: 1 + postExposure: + m_OverrideState: 1 + m_Value: 0 + contrast: + m_OverrideState: 1 + m_Value: 0 + colorFilter: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + hueShift: + m_OverrideState: 1 + m_Value: 0 + saturation: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-6288072647309666549 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 29fa0085f50d5e54f8144f766051a691, type: 3} + m_Name: FilmGrain + m_EditorClassIdentifier: + active: 1 + type: + m_OverrideState: 1 + m_Value: 0 + intensity: + m_OverrideState: 1 + m_Value: 0 + response: + m_OverrideState: 1 + m_Value: 0.8 + texture: + m_OverrideState: 1 + m_Value: {fileID: 0} +--- !u!114 &-5520245016509672950 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 97c23e3b12dc18c42a140437e53d3951, type: 3} + m_Name: Tonemapping + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + neutralHDRRangeReductionMode: + m_OverrideState: 1 + m_Value: 2 + acesPreset: + m_OverrideState: 1 + m_Value: 3 + hueShiftAmount: + m_OverrideState: 1 + m_Value: 0 + detectPaperWhite: + m_OverrideState: 1 + m_Value: 0 + paperWhite: + m_OverrideState: 1 + m_Value: 300 + detectBrightnessLimits: + m_OverrideState: 1 + m_Value: 1 + minNits: + m_OverrideState: 1 + m_Value: 0.005 + maxNits: + m_OverrideState: 1 + m_Value: 1000 +--- !u!114 &-5360449096862653589 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: VolumeComponentSupportedEverywhere + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEngine.Rendering.Tests:VolumeComponentEditorSupportedOnTests/VolumeComponentSupportedEverywhere + active: 1 +--- !u!114 &-5139089513906902183 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5a00a63fdd6bd2a45ab1f2d869305ffd, type: 3} + m_Name: OasisFogVolumeComponent + m_EditorClassIdentifier: + active: 1 + Density: + m_OverrideState: 1 + m_Value: 0 + StartDistance: + m_OverrideState: 1 + m_Value: 0 + HeightRange: + m_OverrideState: 1 + m_Value: {x: 0, y: 50} + Tint: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + SunScatteringIntensity: + m_OverrideState: 1 + m_Value: 2 +--- !u!114 &-4463884970436517307 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fb60a22f311433c4c962b888d1393f88, type: 3} + m_Name: PaniniProjection + m_EditorClassIdentifier: + active: 1 + distance: + m_OverrideState: 1 + m_Value: 0 + cropToFit: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &-1410297666881709256 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6bd486065ce11414fa40e631affc4900, type: 3} + m_Name: ProbeVolumesOptions + m_EditorClassIdentifier: + active: 1 + normalBias: + m_OverrideState: 1 + m_Value: 0.33 + viewBias: + m_OverrideState: 1 + m_Value: 0 + scaleBiasWithMinProbeDistance: + m_OverrideState: 1 + m_Value: 0 + samplingNoise: + m_OverrideState: 1 + m_Value: 0.1 + animateSamplingNoise: + m_OverrideState: 1 + m_Value: 1 + leakReductionMode: + m_OverrideState: 1 + m_Value: 1 + minValidDotProductValue: + m_OverrideState: 1 + m_Value: 0.1 + occlusionOnlyReflectionNormalization: + m_OverrideState: 1 + m_Value: 1 + intensityMultiplier: + m_OverrideState: 1 + m_Value: 1 + skyOcclusionIntensityMultiplier: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &-1216621516061285780 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3} + m_Name: Bloom + m_EditorClassIdentifier: + active: 1 + skipIterations: + m_OverrideState: 1 + m_Value: 1 + threshold: + m_OverrideState: 1 + m_Value: 0.9 + intensity: + m_OverrideState: 1 + m_Value: 0 + scatter: + m_OverrideState: 1 + m_Value: 0.7 + clamp: + m_OverrideState: 1 + m_Value: 65472 + tint: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + highQualityFiltering: + m_OverrideState: 1 + m_Value: 0 + downscale: + m_OverrideState: 1 + m_Value: 0 + maxIterations: + m_OverrideState: 1 + m_Value: 6 + dirtTexture: + m_OverrideState: 1 + m_Value: {fileID: 0} + dimension: 1 + dirtIntensity: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-1170528603972255243 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 221518ef91623a7438a71fef23660601, type: 3} + m_Name: WhiteBalance + m_EditorClassIdentifier: + active: 1 + temperature: + m_OverrideState: 1 + m_Value: 0 + tint: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &-581120513425526550 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: CopyPasteTestComponent3 + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEditor.Rendering.Tests:VolumeComponentCopyPasteTests/CopyPasteTestComponent3 + active: 1 + p1: + m_OverrideState: 1 + m_Value: 0 + p2: + m_OverrideState: 1 + m_Value: 0 + p31: + m_OverrideState: 1 + m_Value: {r: 0, g: 0, b: 0, a: 1} +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: DefaultVolumeProfile + m_EditorClassIdentifier: + components: + - {fileID: -9167874883656233139} + - {fileID: 1918650496244738858} + - {fileID: 853819529557874667} + - {fileID: 1052315754049611418} + - {fileID: -1170528603972255243} + - {fileID: -8270506406425502121} + - {fileID: -5520245016509672950} + - {fileID: 7173750748008157695} + - {fileID: 1666464333004379222} + - {fileID: 9001657382290151224} + - {fileID: -6335409530604852063} + - {fileID: -1216621516061285780} + - {fileID: 3959858460715838825} + - {fileID: -7743500325797982168} + - {fileID: 4644742534064026673} + - {fileID: -4463884970436517307} + - {fileID: -6288072647309666549} + - {fileID: 7518938298396184218} + - {fileID: -1410297666881709256} + - {fileID: -7750755424749557576} + - {fileID: -5139089513906902183} +--- !u!114 &853819529557874667 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 06437c1ff663d574d9447842ba0a72e4, type: 3} + m_Name: ScreenSpaceLensFlare + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 + tintColor: + m_OverrideState: 1 + m_Value: {r: 1, g: 1, b: 1, a: 1} + bloomMip: + m_OverrideState: 1 + m_Value: 1 + firstFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + secondaryFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + warpedFlareIntensity: + m_OverrideState: 1 + m_Value: 1 + warpedFlareScale: + m_OverrideState: 1 + m_Value: {x: 1, y: 1} + samples: + m_OverrideState: 1 + m_Value: 1 + sampleDimmer: + m_OverrideState: 1 + m_Value: 0.5 + vignetteEffect: + m_OverrideState: 1 + m_Value: 1 + startingPosition: + m_OverrideState: 1 + m_Value: 1.25 + scale: + m_OverrideState: 1 + m_Value: 1.5 + streaksIntensity: + m_OverrideState: 1 + m_Value: 0 + streaksLength: + m_OverrideState: 1 + m_Value: 0.5 + streaksOrientation: + m_OverrideState: 1 + m_Value: 0 + streaksThreshold: + m_OverrideState: 1 + m_Value: 0.25 + resolution: + m_OverrideState: 1 + m_Value: 4 + chromaticAbberationIntensity: + m_OverrideState: 1 + m_Value: 0.5 +--- !u!114 &1052315754049611418 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 558a8e2b6826cf840aae193990ba9f2e, type: 3} + m_Name: ShadowsMidtonesHighlights + m_EditorClassIdentifier: + active: 1 + shadows: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + midtones: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + highlights: + m_OverrideState: 1 + m_Value: {x: 1, y: 1, z: 1, w: 0} + shadowsStart: + m_OverrideState: 1 + m_Value: 0 + shadowsEnd: + m_OverrideState: 1 + m_Value: 0.3 + highlightsStart: + m_OverrideState: 1 + m_Value: 0.55 + highlightsEnd: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &1666464333004379222 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3eb4b772797da9440885e8bd939e9560, type: 3} + m_Name: ColorCurves + m_EditorClassIdentifier: + active: 1 + master: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + red: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + green: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + blue: + m_OverrideState: 1 + m_Value: + k__BackingField: 2 + m_Loop: 0 + m_ZeroValue: 0 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 1 + outSlope: 1 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + hueVsHue: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 1 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + hueVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 1 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + satVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 0 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + lumVsSat: + m_OverrideState: 1 + m_Value: + k__BackingField: 0 + m_Loop: 0 + m_ZeroValue: 0.5 + m_Range: 1 + m_Curve: + serializedVersion: 2 + m_Curve: [] + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!114 &1918650496244738858 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e021b4c809a781e468c2988c016ebbea, type: 3} + m_Name: ColorLookup + m_EditorClassIdentifier: + active: 1 + texture: + m_OverrideState: 1 + m_Value: {fileID: 0} + dimension: 1 + contribution: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &3959858460715838825 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c01700fd266d6914ababb731e09af2eb, type: 3} + m_Name: DepthOfField + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 0 + gaussianStart: + m_OverrideState: 1 + m_Value: 10 + gaussianEnd: + m_OverrideState: 1 + m_Value: 30 + gaussianMaxRadius: + m_OverrideState: 1 + m_Value: 1 + highQualitySampling: + m_OverrideState: 1 + m_Value: 0 + focusDistance: + m_OverrideState: 1 + m_Value: 10 + aperture: + m_OverrideState: 1 + m_Value: 5.6 + focalLength: + m_OverrideState: 1 + m_Value: 50 + bladeCount: + m_OverrideState: 1 + m_Value: 5 + bladeCurvature: + m_OverrideState: 1 + m_Value: 1 + bladeRotation: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &4251301726029935498 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 74955a4b0b4243bc87231e8b59ed9140, type: 3} + m_Name: TestVolume + m_EditorClassIdentifier: + active: 1 + param: + m_OverrideState: 1 + m_Value: 123 +--- !u!114 &4644742534064026673 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 81180773991d8724ab7f2d216912b564, type: 3} + m_Name: ChromaticAberration + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &6940869943325143175 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: VolumeComponentSupportedOnAnySRP + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEngine.Rendering.Tests:VolumeComponentEditorSupportedOnTests/VolumeComponentSupportedOnAnySRP + active: 1 +--- !u!114 &7173750748008157695 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3} + m_Name: Vignette + m_EditorClassIdentifier: + active: 1 + color: + m_OverrideState: 1 + m_Value: {r: 0, g: 0, b: 0, a: 1} + center: + m_OverrideState: 1 + m_Value: {x: 0.5, y: 0.5} + intensity: + m_OverrideState: 1 + m_Value: 0 + smoothness: + m_OverrideState: 1 + m_Value: 0.2 + rounded: + m_OverrideState: 1 + m_Value: 0 +--- !u!114 &7518938298396184218 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c5e1dc532bcb41949b58bc4f2abfbb7e, type: 3} + m_Name: LensDistortion + m_EditorClassIdentifier: + active: 1 + intensity: + m_OverrideState: 1 + m_Value: 0 + xMultiplier: + m_OverrideState: 1 + m_Value: 1 + yMultiplier: + m_OverrideState: 1 + m_Value: 1 + center: + m_OverrideState: 1 + m_Value: {x: 0.5, y: 0.5} + scale: + m_OverrideState: 1 + m_Value: 1 +--- !u!114 &9001657382290151224 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: cdfbdbb87d3286943a057f7791b43141, type: 3} + m_Name: ChannelMixer + m_EditorClassIdentifier: + active: 1 + redOutRedIn: + m_OverrideState: 1 + m_Value: 100 + redOutGreenIn: + m_OverrideState: 1 + m_Value: 0 + redOutBlueIn: + m_OverrideState: 1 + m_Value: 0 + greenOutRedIn: + m_OverrideState: 1 + m_Value: 0 + greenOutGreenIn: + m_OverrideState: 1 + m_Value: 100 + greenOutBlueIn: + m_OverrideState: 1 + m_Value: 0 + blueOutRedIn: + m_OverrideState: 1 + m_Value: 0 + blueOutGreenIn: + m_OverrideState: 1 + m_Value: 0 + blueOutBlueIn: + m_OverrideState: 1 + m_Value: 100 +--- !u!114 &9122958982931076880 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: CopyPasteTestComponent1 + m_EditorClassIdentifier: Unity.RenderPipelines.Core.Editor.Tests:UnityEditor.Rendering.Tests:VolumeComponentCopyPasteTests/CopyPasteTestComponent1 + active: 1 + p1: + m_OverrideState: 1 + m_Value: 0 + p2: + m_OverrideState: 1 + m_Value: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/DefaultVolumeProfile.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: ab09877e2e707104187f6f83e2f62510 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/Mobile_RPAsset.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3} + m_Name: Mobile_RPAsset + m_EditorClassIdentifier: + k_AssetVersion: 12 + k_AssetPreviousVersion: 12 + m_RendererType: 1 + m_RendererData: {fileID: 0} + m_RendererDataList: + - {fileID: 11400000, guid: 65bc7dbf4170f435aa868c779acfb082, type: 2} + m_DefaultRendererIndex: 0 + m_RequireDepthTexture: 0 + m_RequireOpaqueTexture: 0 + m_OpaqueDownsampling: 0 + m_SupportsTerrainHoles: 1 + m_SupportsHDR: 1 + m_HDRColorBufferPrecision: 0 + m_MSAA: 1 + m_RenderScale: 0.8 + m_UpscalingFilter: 0 + m_FsrOverrideSharpness: 0 + m_FsrSharpness: 0.92 + m_EnableLODCrossFade: 1 + m_LODCrossFadeDitheringType: 1 + m_ShEvalMode: 0 + m_LightProbeSystem: 0 + m_ProbeVolumeMemoryBudget: 1024 + m_ProbeVolumeBlendingMemoryBudget: 256 + m_SupportProbeVolumeGPUStreaming: 0 + m_SupportProbeVolumeDiskStreaming: 0 + m_SupportProbeVolumeScenarios: 0 + m_SupportProbeVolumeScenarioBlending: 0 + m_ProbeVolumeSHBands: 1 + m_MainLightRenderingMode: 1 + m_MainLightShadowsSupported: 1 + m_MainLightShadowmapResolution: 1024 + m_AdditionalLightsRenderingMode: 1 + m_AdditionalLightsPerObjectLimit: 4 + m_AdditionalLightShadowsSupported: 0 + m_AdditionalLightsShadowmapResolution: 2048 + m_AdditionalLightsShadowResolutionTierLow: 256 + m_AdditionalLightsShadowResolutionTierMedium: 512 + m_AdditionalLightsShadowResolutionTierHigh: 1024 + m_ReflectionProbeBlending: 1 + m_ReflectionProbeBoxProjection: 1 + m_ShadowDistance: 50 + m_ShadowCascadeCount: 1 + m_Cascade2Split: 0.25 + m_Cascade3Split: {x: 0.1, y: 0.3} + m_Cascade4Split: {x: 0.067, y: 0.2, z: 0.467} + m_CascadeBorder: 0.2 + m_ShadowDepthBias: 1 + m_ShadowNormalBias: 1 + m_AnyShadowsSupported: 1 + m_SoftShadowsSupported: 0 + m_ConservativeEnclosingSphere: 1 + m_NumIterationsEnclosingSphere: 64 + m_SoftShadowQuality: 2 + m_AdditionalLightsCookieResolution: 1024 + m_AdditionalLightsCookieFormat: 1 + m_UseSRPBatcher: 1 + m_SupportsDynamicBatching: 0 + m_MixedLightingSupported: 1 + m_SupportsLightCookies: 1 + m_SupportsLightLayers: 1 + m_DebugLevel: 0 + m_StoreActionsOptimization: 0 + m_UseAdaptivePerformance: 1 + m_ColorGradingMode: 0 + m_ColorGradingLutSize: 32 + m_UseFastSRGBLinearConversion: 1 + m_SupportDataDrivenLensFlare: 1 + m_SupportScreenSpaceLensFlare: 1 + m_GPUResidentDrawerMode: 0 + m_UseLegacyLightmaps: 0 + m_SmallMeshScreenPercentage: 0 + m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0 + m_ShadowType: 1 + m_LocalShadowsSupported: 0 + m_LocalShadowsAtlasResolution: 256 + m_MaxPixelLights: 0 + m_ShadowAtlasResolution: 256 + m_VolumeFrameworkUpdateMode: 0 + m_VolumeProfile: {fileID: 11400000, guid: 10fc4df2da32a41aaa32d77bc913491c, type: 2} + apvScenesData: + obsoleteSceneBounds: + m_Keys: [] + m_Values: [] + obsoleteHasProbeVolumes: + m_Keys: [] + m_Values: + m_PrefilteringModeMainLightShadows: 3 + m_PrefilteringModeAdditionalLight: 4 + m_PrefilteringModeAdditionalLightShadows: 0 + m_PrefilterXRKeywords: 1 + m_PrefilteringModeForwardPlus: 1 + m_PrefilteringModeDeferredRendering: 0 + m_PrefilteringModeScreenSpaceOcclusion: 0 + m_PrefilterDebugKeywords: 1 + m_PrefilterWriteRenderingLayers: 1 + m_PrefilterHDROutput: 1 + m_PrefilterSSAODepthNormals: 1 + m_PrefilterSSAOSourceDepthLow: 1 + m_PrefilterSSAOSourceDepthMedium: 0 + m_PrefilterSSAOSourceDepthHigh: 1 + m_PrefilterSSAOInterleaved: 0 + m_PrefilterSSAOBlueNoise: 1 + m_PrefilterSSAOSampleCountLow: 1 + m_PrefilterSSAOSampleCountMedium: 0 + m_PrefilterSSAOSampleCountHigh: 1 + m_PrefilterDBufferMRT1: 1 + m_PrefilterDBufferMRT2: 1 + m_PrefilterDBufferMRT3: 1 + m_PrefilterSoftShadowsQualityLow: 1 + m_PrefilterSoftShadowsQualityMedium: 1 + m_PrefilterSoftShadowsQualityHigh: 1 + m_PrefilterSoftShadows: 0 + m_PrefilterScreenCoord: 1 + m_PrefilterNativeRenderPass: 1 + m_PrefilterUseLegacyLightmaps: 0 + m_ShaderVariantLogLevel: 0 + m_ShadowCascades: 0 + m_Textures: + blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3} + bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3} + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/Mobile_RPAsset.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: 5e6cbd92db86f4b18aec3ed561671858 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/Mobile_Renderer.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3} + m_Name: Mobile_Renderer + m_EditorClassIdentifier: + debugShaders: + debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, + type: 3} + hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} + probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, + type: 3} + probeVolumeResources: + probeVolumeDebugShader: {fileID: 0} + probeVolumeFragmentationDebugShader: {fileID: 0} + probeVolumeOffsetDebugShader: {fileID: 0} + probeVolumeSamplingDebugShader: {fileID: 0} + probeSamplingDebugMesh: {fileID: 0} + probeSamplingDebugTexture: {fileID: 0} + probeVolumeBlendStatesCS: {fileID: 0} + m_RendererFeatures: [] + m_RendererFeatureMap: + m_UseNativeRenderPass: 1 + postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} + m_AssetVersion: 2 + m_OpaqueLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_TransparentLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_DefaultStencilState: + overrideStencilState: 0 + stencilReference: 0 + stencilCompareFunction: 8 + passOperation: 2 + failOperation: 0 + zFailOperation: 0 + m_ShadowTransparentReceive: 0 + m_RenderingMode: 0 + m_DepthPrimingMode: 0 + m_CopyDepthMode: 0 + m_AccurateGbufferNormals: 0 + m_IntermediateTextureMode: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/Mobile_Renderer.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: 65bc7dbf4170f435aa868c779acfb082 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/PC_RPAsset.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3} + m_Name: PC_RPAsset + m_EditorClassIdentifier: + k_AssetVersion: 12 + k_AssetPreviousVersion: 12 + m_RendererType: 1 + m_RendererData: {fileID: 0} + m_RendererDataList: + - {fileID: 11400000, guid: f288ae1f4751b564a96ac7587541f7a2, type: 2} + m_DefaultRendererIndex: 0 + m_RequireDepthTexture: 1 + m_RequireOpaqueTexture: 1 + m_OpaqueDownsampling: 1 + m_SupportsTerrainHoles: 1 + m_SupportsHDR: 1 + m_HDRColorBufferPrecision: 0 + m_MSAA: 1 + m_RenderScale: 1 + m_UpscalingFilter: 0 + m_FsrOverrideSharpness: 0 + m_FsrSharpness: 0.92 + m_EnableLODCrossFade: 1 + m_LODCrossFadeDitheringType: 1 + m_ShEvalMode: 0 + m_LightProbeSystem: 0 + m_ProbeVolumeMemoryBudget: 1024 + m_ProbeVolumeBlendingMemoryBudget: 256 + m_SupportProbeVolumeGPUStreaming: 0 + m_SupportProbeVolumeDiskStreaming: 0 + m_SupportProbeVolumeScenarios: 0 + m_SupportProbeVolumeScenarioBlending: 0 + m_ProbeVolumeSHBands: 1 + m_MainLightRenderingMode: 1 + m_MainLightShadowsSupported: 1 + m_MainLightShadowmapResolution: 2048 + m_AdditionalLightsRenderingMode: 1 + m_AdditionalLightsPerObjectLimit: 4 + m_AdditionalLightShadowsSupported: 1 + m_AdditionalLightsShadowmapResolution: 2048 + m_AdditionalLightsShadowResolutionTierLow: 256 + m_AdditionalLightsShadowResolutionTierMedium: 512 + m_AdditionalLightsShadowResolutionTierHigh: 1024 + m_ReflectionProbeBlending: 1 + m_ReflectionProbeBoxProjection: 1 + m_ShadowDistance: 50 + m_ShadowCascadeCount: 4 + m_Cascade2Split: 0.25 + m_Cascade3Split: {x: 0.1, y: 0.3} + m_Cascade4Split: {x: 0.12299999, y: 0.2926, z: 0.53599995} + m_CascadeBorder: 0.107758604 + m_ShadowDepthBias: 0.1 + m_ShadowNormalBias: 0.5 + m_AnyShadowsSupported: 1 + m_SoftShadowsSupported: 1 + m_ConservativeEnclosingSphere: 1 + m_NumIterationsEnclosingSphere: 64 + m_SoftShadowQuality: 3 + m_AdditionalLightsCookieResolution: 2048 + m_AdditionalLightsCookieFormat: 3 + m_UseSRPBatcher: 1 + m_SupportsDynamicBatching: 0 + m_MixedLightingSupported: 1 + m_SupportsLightCookies: 1 + m_SupportsLightLayers: 1 + m_DebugLevel: 0 + m_StoreActionsOptimization: 0 + m_UseAdaptivePerformance: 1 + m_ColorGradingMode: 0 + m_ColorGradingLutSize: 32 + m_UseFastSRGBLinearConversion: 0 + m_SupportDataDrivenLensFlare: 1 + m_SupportScreenSpaceLensFlare: 1 + m_GPUResidentDrawerMode: 0 + m_UseLegacyLightmaps: 0 + m_SmallMeshScreenPercentage: 0 + m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0 + m_ShadowType: 1 + m_LocalShadowsSupported: 0 + m_LocalShadowsAtlasResolution: 256 + m_MaxPixelLights: 0 + m_ShadowAtlasResolution: 256 + m_VolumeFrameworkUpdateMode: 0 + m_VolumeProfile: {fileID: 11400000, guid: 10fc4df2da32a41aaa32d77bc913491c, type: 2} + apvScenesData: + obsoleteSceneBounds: + m_Keys: [] + m_Values: [] + obsoleteHasProbeVolumes: + m_Keys: [] + m_Values: + m_PrefilteringModeMainLightShadows: 3 + m_PrefilteringModeAdditionalLight: 4 + m_PrefilteringModeAdditionalLightShadows: 0 + m_PrefilterXRKeywords: 1 + m_PrefilteringModeForwardPlus: 1 + m_PrefilteringModeDeferredRendering: 0 + m_PrefilteringModeScreenSpaceOcclusion: 1 + m_PrefilterDebugKeywords: 1 + m_PrefilterWriteRenderingLayers: 0 + m_PrefilterHDROutput: 1 + m_PrefilterSSAODepthNormals: 0 + m_PrefilterSSAOSourceDepthLow: 1 + m_PrefilterSSAOSourceDepthMedium: 1 + m_PrefilterSSAOSourceDepthHigh: 1 + m_PrefilterSSAOInterleaved: 1 + m_PrefilterSSAOBlueNoise: 0 + m_PrefilterSSAOSampleCountLow: 1 + m_PrefilterSSAOSampleCountMedium: 0 + m_PrefilterSSAOSampleCountHigh: 1 + m_PrefilterDBufferMRT1: 1 + m_PrefilterDBufferMRT2: 1 + m_PrefilterDBufferMRT3: 0 + m_PrefilterSoftShadowsQualityLow: 0 + m_PrefilterSoftShadowsQualityMedium: 0 + m_PrefilterSoftShadowsQualityHigh: 0 + m_PrefilterSoftShadows: 0 + m_PrefilterScreenCoord: 1 + m_PrefilterNativeRenderPass: 1 + m_PrefilterUseLegacyLightmaps: 0 + m_ShaderVariantLogLevel: 0 + m_ShadowCascades: 0 + m_Textures: + blueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3} + bayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3} + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/PC_RPAsset.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: 4b83569d67af61e458304325a23e5dfd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/PC_Renderer.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de640fe3d0db1804a85f9fc8f5cadab6, type: 3} + m_Name: PC_Renderer + m_EditorClassIdentifier: + debugShaders: + debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, + type: 3} + hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} + probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, + type: 3} + probeVolumeResources: + probeVolumeDebugShader: {fileID: 4800000, guid: e5c6678ed2aaa91408dd3df699057aae, + type: 3} + probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 03cfc4915c15d504a9ed85ecc404e607, + type: 3} + probeVolumeOffsetDebugShader: {fileID: 4800000, guid: 53a11f4ebaebf4049b3638ef78dc9664, + type: 3} + probeVolumeSamplingDebugShader: {fileID: 4800000, guid: 8f96cd657dc40064aa21efcc7e50a2e7, + type: 3} + probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 57d7c4c16e2765b47a4d2069b311bffe, + type: 3} + probeSamplingDebugTexture: {fileID: 2800000, guid: 24ec0e140fb444a44ab96ee80844e18e, + type: 3} + probeVolumeBlendStatesCS: {fileID: 7200000, guid: b9a23f869c4fd45f19c5ada54dd82176, + type: 3} + m_RendererFeatures: + - {fileID: 7833122117494664109} + m_RendererFeatureMap: ad6b866f10d7b46c + m_UseNativeRenderPass: 1 + postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} + m_AssetVersion: 2 + m_OpaqueLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_TransparentLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_DefaultStencilState: + overrideStencilState: 0 + stencilReference: 1 + stencilCompareFunction: 3 + passOperation: 2 + failOperation: 0 + zFailOperation: 0 + m_ShadowTransparentReceive: 1 + m_RenderingMode: 2 + m_DepthPrimingMode: 0 + m_CopyDepthMode: 0 + m_AccurateGbufferNormals: 0 + m_IntermediateTextureMode: 0 +--- !u!114 &7833122117494664109 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f62c9c65cf3354c93be831c8bc075510, type: 3} + m_Name: ScreenSpaceAmbientOcclusion + m_EditorClassIdentifier: + m_Active: 1 + m_Settings: + AOMethod: 0 + Downsample: 0 + AfterOpaque: 0 + Source: 1 + NormalSamples: 1 + Intensity: 0.4 + DirectLightingStrength: 0.25 + Radius: 0.3 + Samples: 1 + BlurQuality: 0 + Falloff: 100 + SampleCount: -1 + m_BlueNoise256Textures: + - {fileID: 2800000, guid: 36f118343fc974119bee3d09e2111500, type: 3} + - {fileID: 2800000, guid: 4b7b083e6b6734e8bb2838b0b50a0bc8, type: 3} + - {fileID: 2800000, guid: c06cc21c692f94f5fb5206247191eeee, type: 3} + - {fileID: 2800000, guid: cb76dd40fa7654f9587f6a344f125c9a, type: 3} + - {fileID: 2800000, guid: e32226222ff144b24bf3a5a451de54bc, type: 3} + - {fileID: 2800000, guid: 3302065f671a8450b82c9ddf07426f3a, type: 3} + - {fileID: 2800000, guid: 56a77a3e8d64f47b6afe9e3c95cb57d5, type: 3} + m_Shader: {fileID: 4800000, guid: 0849e84e3d62649e8882e9d6f056a017, type: 3} + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/PC_Renderer.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: f288ae1f4751b564a96ac7587541f7a2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/SampleSceneProfile.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-7893295128165547882 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3} + m_Name: Bloom + m_EditorClassIdentifier: + active: 1 + skipIterations: + m_OverrideState: 1 + m_Value: 0 + threshold: + m_OverrideState: 1 + m_Value: 1 + intensity: + m_OverrideState: 1 + m_Value: 0.25 + scatter: + m_OverrideState: 1 + m_Value: 0.5 + clamp: + m_OverrideState: 0 + m_Value: 65472 + tint: + m_OverrideState: 0 + m_Value: {r: 1, g: 1, b: 1, a: 1} + highQualityFiltering: + m_OverrideState: 1 + m_Value: 1 + downscale: + m_OverrideState: 0 + m_Value: 0 + maxIterations: + m_OverrideState: 0 + m_Value: 6 + dirtTexture: + m_OverrideState: 0 + m_Value: {fileID: 0} + dimension: 1 + dirtIntensity: + m_OverrideState: 0 + m_Value: 0 +--- !u!114 &-3357603926938260329 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3} + m_Name: Vignette + m_EditorClassIdentifier: + active: 1 + color: + m_OverrideState: 0 + m_Value: {r: 0, g: 0, b: 0, a: 1} + center: + m_OverrideState: 0 + m_Value: {x: 0.5, y: 0.5} + intensity: + m_OverrideState: 1 + m_Value: 0.2 + smoothness: + m_OverrideState: 0 + m_Value: 0.2 + rounded: + m_OverrideState: 0 + m_Value: 0 +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} + m_Name: SampleSceneProfile + m_EditorClassIdentifier: + components: + - {fileID: 849379129802519247} + - {fileID: -7893295128165547882} + - {fileID: 7391319092446245454} + - {fileID: -3357603926938260329} +--- !u!114 &849379129802519247 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 97c23e3b12dc18c42a140437e53d3951, type: 3} + m_Name: Tonemapping + m_EditorClassIdentifier: + active: 1 + mode: + m_OverrideState: 1 + m_Value: 1 + neutralHDRRangeReductionMode: + m_OverrideState: 0 + m_Value: 2 + acesPreset: + m_OverrideState: 0 + m_Value: 3 + hueShiftAmount: + m_OverrideState: 0 + m_Value: 0 + detectPaperWhite: + m_OverrideState: 1 + m_Value: 0 + paperWhite: + m_OverrideState: 1 + m_Value: 234 + detectBrightnessLimits: + m_OverrideState: 1 + m_Value: 1 + minNits: + m_OverrideState: 1 + m_Value: 0.005 + maxNits: + m_OverrideState: 1 + m_Value: 647 +--- !u!114 &7391319092446245454 +MonoBehaviour: + m_ObjectHideFlags: 3 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ccf1aba9553839d41ae37dd52e9ebcce, type: 3} + m_Name: MotionBlur + m_EditorClassIdentifier: + active: 0 + mode: + m_OverrideState: 0 + m_Value: 0 + quality: + m_OverrideState: 1 + m_Value: 2 + intensity: + m_OverrideState: 1 + m_Value: 0.6 + clamp: + m_OverrideState: 0 + m_Value: 0.05 + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/SampleSceneProfile.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: 10fc4df2da32a41aaa32d77bc913491c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2ec995e51a6e251468d2a3fd8a686257, type: 3} + m_Name: UniversalRenderPipelineGlobalSettings + m_EditorClassIdentifier: + m_ShaderStrippingSetting: + m_Version: 0 + m_ExportShaderVariants: 1 + m_ShaderVariantLogLevel: 0 + m_StripRuntimeDebugShaders: 1 + m_URPShaderStrippingSetting: + m_Version: 0 + m_StripUnusedPostProcessingVariants: 1 + m_StripUnusedVariants: 1 + m_StripScreenCoordOverrideVariants: 1 + m_ShaderVariantLogLevel: 0 + m_ExportShaderVariants: 1 + m_StripDebugVariants: 1 + m_StripUnusedPostProcessingVariants: 1 + m_StripUnusedVariants: 1 + m_StripScreenCoordOverrideVariants: 1 + supportRuntimeDebugDisplay: 0 + m_EnableRenderGraph: 0 + m_Settings: + m_SettingsList: + m_List: + - rid: 6852985685364965376 + - rid: 6852985685364965377 + - rid: 6852985685364965378 + - rid: 6852985685364965379 + - rid: 6852985685364965380 + - rid: 6852985685364965381 + - rid: 6852985685364965382 + - rid: 6852985685364965383 + - rid: 6852985685364965384 + - rid: 6852985685364965385 + - rid: 6852985685364965386 + - rid: 6852985685364965387 + - rid: 6852985685364965388 + - rid: 6852985685364965389 + - rid: 6852985685364965390 + - rid: 6852985685364965391 + - rid: 6852985685364965392 + - rid: 6852985685364965393 + - rid: 6852985685364965394 + - rid: 8712630790384254976 + - rid: 372555651542941696 + - rid: 372555651542941697 + - rid: 372555651542941698 + - rid: 372555651542941699 + - rid: 372555651542941700 + - rid: 372555651542941701 + m_RuntimeSettings: + m_List: [] + m_AssetVersion: 8 + m_ObsoleteDefaultVolumeProfile: {fileID: 0} + m_RenderingLayerNames: + - Light Layer default + - Light Layer 1 + - Light Layer 2 + - Light Layer 3 + - Light Layer 4 + - Light Layer 5 + - Light Layer 6 + - Light Layer 7 + m_ValidRenderingLayers: 0 + lightLayerName0: Light Layer default + lightLayerName1: Light Layer 1 + lightLayerName2: Light Layer 2 + lightLayerName3: Light Layer 3 + lightLayerName4: Light Layer 4 + lightLayerName5: Light Layer 5 + lightLayerName6: Light Layer 6 + lightLayerName7: Light Layer 7 + apvScenesData: + obsoleteSceneBounds: + m_Keys: [] + m_Values: [] + obsoleteHasProbeVolumes: + m_Keys: [] + m_Values: + references: + version: 2 + RefIds: + - rid: 372555651542941696 + type: {class: ScreenSpaceAmbientOcclusionPersistentResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Shader: {fileID: 4800000, guid: 0849e84e3d62649e8882e9d6f056a017, type: 3} + m_Version: 0 + - rid: 372555651542941697 + type: {class: PostProcessData/TextureResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + blueNoise16LTex: + - {fileID: 2800000, guid: 81200413a40918d4d8702e94db29911c, type: 3} + - {fileID: 2800000, guid: d50c5e07c9911a74982bddf7f3075e7b, type: 3} + - {fileID: 2800000, guid: 1134690bf9216164dbc75050e35b7900, type: 3} + - {fileID: 2800000, guid: 7ce2118f74614a94aa8a0cdf2e6062c3, type: 3} + - {fileID: 2800000, guid: 2ca97df9d1801e84a8a8f2c53cb744f0, type: 3} + - {fileID: 2800000, guid: e63eef8f54aa9dc4da9a5ac094b503b5, type: 3} + - {fileID: 2800000, guid: 39451254daebd6d40b52899c1f1c0c1b, type: 3} + - {fileID: 2800000, guid: c94ad916058dff743b0f1c969ddbe660, type: 3} + - {fileID: 2800000, guid: ed5ea7ce59ca8ec4f9f14bf470a30f35, type: 3} + - {fileID: 2800000, guid: 071e954febf155243a6c81e48f452644, type: 3} + - {fileID: 2800000, guid: 96aaab9cc247d0b4c98132159688c1af, type: 3} + - {fileID: 2800000, guid: fc3fa8f108657e14486697c9a84ccfc5, type: 3} + - {fileID: 2800000, guid: bfed3e498947fcb4890b7f40f54d85b9, type: 3} + - {fileID: 2800000, guid: d512512f4af60a442ab3458489412954, type: 3} + - {fileID: 2800000, guid: 47a45908f6db0cb44a0d5e961143afec, type: 3} + - {fileID: 2800000, guid: 4dcc0502f8586f941b5c4a66717205e8, type: 3} + - {fileID: 2800000, guid: 9d92991794bb5864c8085468b97aa067, type: 3} + - {fileID: 2800000, guid: 14381521ff11cb74abe3fe65401c23be, type: 3} + - {fileID: 2800000, guid: d36f0fe53425e08499a2333cf423634c, type: 3} + - {fileID: 2800000, guid: d4044ea2490d63b43aa1765f8efbf8a9, type: 3} + - {fileID: 2800000, guid: c9bd74624d8070f429e3f46d161f9204, type: 3} + - {fileID: 2800000, guid: d5c9b274310e5524ebe32a4e4da3df1f, type: 3} + - {fileID: 2800000, guid: f69770e54f2823f43badf77916acad83, type: 3} + - {fileID: 2800000, guid: 10b6c6d22e73dea46a8ab36b6eebd629, type: 3} + - {fileID: 2800000, guid: a2ec5cbf5a9b64345ad3fab0912ddf7b, type: 3} + - {fileID: 2800000, guid: 1c3c6d69a645b804fa232004b96b7ad3, type: 3} + - {fileID: 2800000, guid: d18a24d7b4ed50f4387993566d9d3ae2, type: 3} + - {fileID: 2800000, guid: c989e1ed85cf7154caa922fec53e6af6, type: 3} + - {fileID: 2800000, guid: ff47e5a0f105eb34883b973e51f4db62, type: 3} + - {fileID: 2800000, guid: fa042edbfc40fbd4bad0ab9d505b1223, type: 3} + - {fileID: 2800000, guid: 896d9004736809c4fb5973b7c12eb8b9, type: 3} + - {fileID: 2800000, guid: 179f794063d2a66478e6e726f84a65bc, type: 3} + filmGrainTex: + - {fileID: 2800000, guid: 654c582f7f8a5a14dbd7d119cbde215d, type: 3} + - {fileID: 2800000, guid: dd77ffd079630404e879388999033049, type: 3} + - {fileID: 2800000, guid: 1097e90e1306e26439701489f391a6c0, type: 3} + - {fileID: 2800000, guid: f0b67500f7fad3b4c9f2b13e8f41ba6e, type: 3} + - {fileID: 2800000, guid: 9930fb4528622b34687b00bbe6883de7, type: 3} + - {fileID: 2800000, guid: bd9e8c758250ef449a4b4bfaad7a2133, type: 3} + - {fileID: 2800000, guid: 510a2f57334933e4a8dbabe4c30204e4, type: 3} + - {fileID: 2800000, guid: b4db8180660810945bf8d55ab44352ad, type: 3} + - {fileID: 2800000, guid: fd2fd78b392986e42a12df2177d3b89c, type: 3} + - {fileID: 2800000, guid: 5cdee82a77d13994f83b8fdabed7c301, type: 3} + smaaAreaTex: {fileID: 2800000, guid: d1f1048909d55cd4fa1126ab998f617e, type: 3} + smaaSearchTex: {fileID: 2800000, guid: 51eee22c2a633ef4aada830eed57c3fd, type: 3} + m_TexturesResourcesVersion: 0 + - rid: 372555651542941698 + type: {class: ScreenSpaceAmbientOcclusionDynamicResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_BlueNoise256Textures: + - {fileID: 2800000, guid: 36f118343fc974119bee3d09e2111500, type: 3} + - {fileID: 2800000, guid: 4b7b083e6b6734e8bb2838b0b50a0bc8, type: 3} + - {fileID: 2800000, guid: c06cc21c692f94f5fb5206247191eeee, type: 3} + - {fileID: 2800000, guid: cb76dd40fa7654f9587f6a344f125c9a, type: 3} + - {fileID: 2800000, guid: e32226222ff144b24bf3a5a451de54bc, type: 3} + - {fileID: 2800000, guid: 3302065f671a8450b82c9ddf07426f3a, type: 3} + - {fileID: 2800000, guid: 56a77a3e8d64f47b6afe9e3c95cb57d5, type: 3} + m_Version: 0 + - rid: 372555651542941699 + type: {class: PostProcessData/ShaderResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + stopNanPS: {fileID: 4800000, guid: 1121bb4e615ca3c48b214e79e841e823, type: 3} + subpixelMorphologicalAntialiasingPS: {fileID: 4800000, guid: 63eaba0ebfb82cc43bde059b4a8c65f6, type: 3} + gaussianDepthOfFieldPS: {fileID: 4800000, guid: 5e7134d6e63e0bc47a1dd2669cedb379, type: 3} + bokehDepthOfFieldPS: {fileID: 4800000, guid: 2aed67ad60045d54ba3a00c91e2d2631, type: 3} + cameraMotionBlurPS: {fileID: 4800000, guid: 1edcd131364091c46a17cbff0b1de97a, type: 3} + paniniProjectionPS: {fileID: 4800000, guid: a15b78cf8ca26ca4fb2090293153c62c, type: 3} + lutBuilderLdrPS: {fileID: 4800000, guid: 65df88701913c224d95fc554db28381a, type: 3} + lutBuilderHdrPS: {fileID: 4800000, guid: ec9fec698a3456d4fb18cf8bacb7a2bc, type: 3} + bloomPS: {fileID: 4800000, guid: 5f1864addb451f54bae8c86d230f736e, type: 3} + temporalAntialiasingPS: {fileID: 4800000, guid: 9c70c1a35ff15f340b38ea84842358bf, type: 3} + LensFlareDataDrivenPS: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3} + LensFlareScreenSpacePS: {fileID: 4800000, guid: 701880fecb344ea4c9cd0db3407ab287, type: 3} + scalingSetupPS: {fileID: 4800000, guid: e8ee25143a34b8c4388709ea947055d1, type: 3} + easuPS: {fileID: 4800000, guid: 562b7ae4f629f144aa97780546fce7c6, type: 3} + uberPostPS: {fileID: 4800000, guid: e7857e9d0c934dc4f83f270f8447b006, type: 3} + finalPostPassPS: {fileID: 4800000, guid: c49e63ed1bbcb334780a3bd19dfed403, type: 3} + m_ShaderResourcesVersion: 0 + - rid: 372555651542941700 + type: {class: UniversalRenderPipelineEditorAssets, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_DefaultSettingsVolumeProfile: {fileID: 11400000, guid: eda47df5b85f4f249abf7abd73db2cb2, type: 2} + - rid: 372555651542941701 + type: {class: UniversalRenderPipelineRuntimeXRResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_xrOcclusionMeshPS: {fileID: 4800000, guid: 4431b1f1f743fbf4eb310a967890cbea, type: 3} + m_xrMirrorViewPS: {fileID: 4800000, guid: d5a307c014552314b9f560906d708772, type: 3} + m_xrMotionVector: {fileID: 4800000, guid: f89aac1e4f84468418fe30e611dff395, type: 3} + - rid: 6852985685364965376 + type: {class: URPShaderStrippingSetting, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_StripUnusedPostProcessingVariants: 1 + m_StripUnusedVariants: 1 + m_StripScreenCoordOverrideVariants: 1 + - rid: 6852985685364965377 + type: {class: UniversalRenderPipelineEditorShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_AutodeskInteractive: {fileID: 4800000, guid: 0e9d5a909a1f7e84882a534d0d11e49f, type: 3} + m_AutodeskInteractiveTransparent: {fileID: 4800000, guid: 5c81372d981403744adbdda4433c9c11, type: 3} + m_AutodeskInteractiveMasked: {fileID: 4800000, guid: 80aa867ac363ac043847b06ad71604cd, type: 3} + m_TerrainDetailLit: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3} + m_TerrainDetailGrassBillboard: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3} + m_TerrainDetailGrass: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3} + m_DefaultSpeedTree7Shader: {fileID: 4800000, guid: 0f4122b9a743b744abe2fb6a0a88868b, type: 3} + m_DefaultSpeedTree8Shader: {fileID: -6465566751694194690, guid: 9920c1f1781549a46ba081a2a15a16ec, type: 3} + m_DefaultSpeedTree9Shader: {fileID: -6465566751694194690, guid: cbd3e1cc4ae141c42a30e33b4d666a61, type: 3} + - rid: 6852985685364965378 + type: {class: UniversalRendererResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_CopyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3} + m_CameraMotionVector: {fileID: 4800000, guid: c56b7e0d4c7cb484e959caeeedae9bbf, type: 3} + m_StencilDeferredPS: {fileID: 4800000, guid: e9155b26e1bc55942a41e518703fe304, type: 3} + m_DBufferClear: {fileID: 4800000, guid: f056d8bd2a1c7e44e9729144b4c70395, type: 3} + - rid: 6852985685364965379 + type: {class: UniversalRenderPipelineDebugShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_DebugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3} + m_HdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} + m_ProbeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, type: 3} + - rid: 6852985685364965380 + type: {class: UniversalRenderPipelineRuntimeShaders, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_FallbackErrorShader: {fileID: 4800000, guid: e6e9a19c3678ded42a3bc431ebef7dbd, type: 3} + m_BlitHDROverlay: {fileID: 4800000, guid: a89bee29cffa951418fc1e2da94d1959, type: 3} + m_CoreBlitPS: {fileID: 4800000, guid: 93446b5c5339d4f00b85c159e1159b7c, type: 3} + m_CoreBlitColorAndDepthPS: {fileID: 4800000, guid: d104b2fc1ca6445babb8e90b0758136b, type: 3} + m_SamplingPS: {fileID: 4800000, guid: 04c410c9937594faa893a11dceb85f7e, type: 3} + m_TerrainDetailLit: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, type: 3} + m_TerrainDetailGrassBillboard: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, type: 3} + m_TerrainDetailGrass: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, type: 3} + - rid: 6852985685364965381 + type: {class: UniversalRenderPipelineRuntimeTextures, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 1 + m_BlueNoise64LTex: {fileID: 2800000, guid: e3d24661c1e055f45a7560c033dbb837, type: 3} + m_BayerMatrixTex: {fileID: 2800000, guid: f9ee4ed84c1d10c49aabb9b210b0fc44, type: 3} + m_DebugFontTex: {fileID: 2800000, guid: 26a413214480ef144b2915d6ff4d0beb, type: 3} + - rid: 6852985685364965382 + type: {class: Renderer2DResources, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_LightShader: {fileID: 4800000, guid: 3f6c848ca3d7bca4bbe846546ac701a1, type: 3} + m_ProjectedShadowShader: {fileID: 4800000, guid: ce09d4a80b88c5a4eb9768fab4f1ee00, type: 3} + m_SpriteShadowShader: {fileID: 4800000, guid: 44fc62292b65ab04eabcf310e799ccf6, type: 3} + m_SpriteUnshadowShader: {fileID: 4800000, guid: de02b375720b5c445afe83cd483bedf3, type: 3} + m_GeometryShadowShader: {fileID: 4800000, guid: 19349a0f9a7ed4c48a27445bcf92e5e1, type: 3} + m_GeometryUnshadowShader: {fileID: 4800000, guid: 77774d9009bb81447b048c907d4c6273, type: 3} + m_FallOffLookup: {fileID: 2800000, guid: 5688ab254e4c0634f8d6c8e0792331ca, type: 3} + m_CopyDepthPS: {fileID: 4800000, guid: d6dae50ee9e1bfa4db75f19f99355220, type: 3} + m_DefaultLitMaterial: {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2} + m_DefaultUnlitMaterial: {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2} + m_DefaultMaskMaterial: {fileID: 2100000, guid: 15d0c3709176029428a0da2f8cecf0b5, type: 2} + - rid: 6852985685364965383 + type: {class: UniversalRenderPipelineEditorMaterials, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_DefaultMaterial: {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2} + m_DefaultParticleMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2} + m_DefaultLineMaterial: {fileID: 2100000, guid: e823cd5b5d27c0f4b8256e7c12ee3e6d, type: 2} + m_DefaultTerrainMaterial: {fileID: 2100000, guid: 594ea882c5a793440b60ff72d896021e, type: 2} + m_DefaultDecalMaterial: {fileID: 2100000, guid: 31d0dcc6f2dd4e4408d18036a2c93862, type: 2} + m_DefaultSpriteMaterial: {fileID: 2100000, guid: 9dfc825aed78fcd4ba02077103263b40, type: 2} + - rid: 6852985685364965384 + type: {class: URPDefaultVolumeProfileSettings, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_VolumeProfile: {fileID: 11400000, guid: ab09877e2e707104187f6f83e2f62510, type: 2} + - rid: 6852985685364965385 + type: {class: RenderGraphSettings, ns: UnityEngine.Rendering.Universal, asm: Unity.RenderPipelines.Universal.Runtime} + data: + m_Version: 0 + m_EnableRenderCompatibilityMode: 0 + - rid: 6852985685364965386 + type: {class: GPUResidentDrawerResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.GPUDriven.Runtime} + data: + m_Version: 0 + m_InstanceDataBufferCopyKernels: {fileID: 7200000, guid: f984aeb540ded8b4fbb8a2047ab5b2e2, type: 3} + m_InstanceDataBufferUploadKernels: {fileID: 7200000, guid: 53864816eb00f2343b60e1a2c5a262ef, type: 3} + m_TransformUpdaterKernels: {fileID: 7200000, guid: 2a567b9b2733f8d47a700c3c85bed75b, type: 3} + m_WindDataUpdaterKernels: {fileID: 7200000, guid: fde76746e4fd0ed418c224f6b4084114, type: 3} + m_OccluderDepthPyramidKernels: {fileID: 7200000, guid: 08b2b5fb307b0d249860612774a987da, type: 3} + m_InstanceOcclusionCullingKernels: {fileID: 7200000, guid: f6d223acabc2f974795a5a7864b50e6c, type: 3} + m_OcclusionCullingDebugKernels: {fileID: 7200000, guid: b23e766bcf50ca4438ef186b174557df, type: 3} + m_DebugOcclusionTestPS: {fileID: 4800000, guid: d3f0849180c2d0944bc71060693df100, type: 3} + m_DebugOccluderPS: {fileID: 4800000, guid: b3c92426a88625841ab15ca6a7917248, type: 3} + - rid: 6852985685364965387 + type: {class: STP/RuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_setupCS: {fileID: 7200000, guid: 33be2e9a5506b2843bdb2bdff9cad5e1, type: 3} + m_preTaaCS: {fileID: 7200000, guid: a679dba8ec4d9ce45884a270b0e22dda, type: 3} + m_taaCS: {fileID: 7200000, guid: 3923900e2b41b5e47bc25bfdcbcdc9e6, type: 3} + - rid: 6852985685364965388 + type: {class: ProbeVolumeBakingResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + dilationShader: {fileID: 7200000, guid: 6bb382f7de370af41b775f54182e491d, type: 3} + subdivideSceneCS: {fileID: 7200000, guid: bb86f1f0af829fd45b2ebddda1245c22, type: 3} + voxelizeSceneShader: {fileID: 4800000, guid: c8b6a681c7b4e2e4785ffab093907f9e, type: 3} + traceVirtualOffsetCS: {fileID: -6772857160820960102, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3} + traceVirtualOffsetRT: {fileID: -5126288278712620388, guid: ff2cbab5da58bf04d82c5f34037ed123, type: 3} + skyOcclusionCS: {fileID: -6772857160820960102, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3} + skyOcclusionRT: {fileID: -5126288278712620388, guid: 5a2a534753fbdb44e96c3c78b5a6999d, type: 3} + renderingLayerCS: {fileID: -6772857160820960102, guid: 94a070d33e408384bafc1dea4a565df9, type: 3} + renderingLayerRT: {fileID: -5126288278712620388, guid: 94a070d33e408384bafc1dea4a565df9, type: 3} + - rid: 6852985685364965389 + type: {class: ProbeVolumeGlobalSettings, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + m_ProbeVolumeDisableStreamingAssets: 0 + - rid: 6852985685364965390 + type: {class: ProbeVolumeDebugResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + probeVolumeDebugShader: {fileID: 4800000, guid: 3b21275fd12d65f49babb5286f040f2d, type: 3} + probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 3a80877c579b9144ebdcc6d923bca303, type: 3} + probeVolumeSamplingDebugShader: {fileID: 4800000, guid: bf54e6528c79a224e96346799064c393, type: 3} + probeVolumeOffsetDebugShader: {fileID: 4800000, guid: db8bd7436dc2c5f4c92655307d198381, type: 3} + probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 20be25aac4e22ee49a7db76fb3df6de2, type: 3} + numbersDisplayTex: {fileID: 2800000, guid: 73fe53b428c5b3440b7e87ee830b608a, type: 3} + - rid: 6852985685364965391 + type: {class: IncludeAdditionalRPAssets, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_version: 0 + m_IncludeReferencedInScenes: 0 + m_IncludeAssetsByLabel: 0 + m_LabelToInclude: + - rid: 6852985685364965392 + type: {class: ShaderStrippingSetting, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 0 + m_ExportShaderVariants: 1 + m_ShaderVariantLogLevel: 0 + m_StripRuntimeDebugShaders: 1 + - rid: 6852985685364965393 + type: {class: ProbeVolumeRuntimeResources, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 1 + probeVolumeBlendStatesCS: {fileID: 7200000, guid: a3f7b8c99de28a94684cb1daebeccf5d, type: 3} + probeVolumeUploadDataCS: {fileID: 7200000, guid: 0951de5992461754fa73650732c4954c, type: 3} + probeVolumeUploadDataL2CS: {fileID: 7200000, guid: 6196f34ed825db14b81fb3eb0ea8d931, type: 3} + - rid: 6852985685364965394 + type: {class: RenderGraphGlobalSettings, ns: UnityEngine.Rendering, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_version: 0 + m_EnableCompilationCaching: 1 + m_EnableValidityChecks: 1 + - rid: 8712630790384254976 + type: {class: RenderGraphUtilsResources, ns: UnityEngine.Rendering.RenderGraphModule.Util, asm: Unity.RenderPipelines.Core.Runtime} + data: + m_Version: 0 + m_CoreCopyPS: {fileID: 4800000, guid: 12dc59547ea167a4ab435097dd0f9add, type: 3} + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset.meta`: + +```meta +fileFormatVersion: 2 +guid: 18dc0cd2c080841dea60987a38ce93fa +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo.meta`: + +```meta +fileFormatVersion: 2 +guid: ba062aa6c92b140379dbc06b43dd3b9b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Icons.meta`: + +```meta +fileFormatVersion: 2 +guid: 8a0c9218a650547d98138cd835033977 +folderAsset: yes +timeCreated: 1484670163 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Icons/URP.png.meta`: + +```meta +fileFormatVersion: 2 +guid: 727a75301c3d24613a3ebcec4a24c2c8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Layout.wlt`: + +```wlt +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12004, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_PixelRect: + serializedVersion: 2 + x: 0 + y: 45 + width: 1666 + height: 958 + m_ShowMode: 4 + m_Title: + m_RootView: {fileID: 6} + m_MinSize: {x: 950, y: 542} + m_MaxSize: {x: 10000, y: 10000} +--- !u!114 &2 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 466 + width: 290 + height: 442 + m_MinSize: {x: 234, y: 271} + m_MaxSize: {x: 10004, y: 10021} + m_ActualView: {fileID: 14} + m_Panes: + - {fileID: 14} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &3 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 4} + - {fileID: 2} + m_Position: + serializedVersion: 2 + x: 973 + y: 0 + width: 290 + height: 908 + m_MinSize: {x: 234, y: 492} + m_MaxSize: {x: 10004, y: 14042} + vertical: 1 + controlID: 226 +--- !u!114 &4 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 290 + height: 466 + m_MinSize: {x: 204, y: 221} + m_MaxSize: {x: 4004, y: 4021} + m_ActualView: {fileID: 17} + m_Panes: + - {fileID: 17} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &5 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 466 + width: 973 + height: 442 + m_MinSize: {x: 202, y: 221} + m_MaxSize: {x: 4002, y: 4021} + m_ActualView: {fileID: 15} + m_Panes: + - {fileID: 15} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &6 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12008, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 7} + - {fileID: 8} + - {fileID: 9} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1666 + height: 958 + m_MinSize: {x: 950, y: 542} + m_MaxSize: {x: 10000, y: 10000} +--- !u!114 &7 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12011, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 1666 + height: 30 + m_MinSize: {x: 0, y: 0} + m_MaxSize: {x: 0, y: 0} + m_LastLoadedLayoutName: Tutorial +--- !u!114 &8 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 10} + - {fileID: 3} + - {fileID: 11} + m_Position: + serializedVersion: 2 + x: 0 + y: 30 + width: 1666 + height: 908 + m_MinSize: {x: 713, y: 492} + m_MaxSize: {x: 18008, y: 14042} + vertical: 0 + controlID: 74 +--- !u!114 &9 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12042, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 938 + width: 1666 + height: 20 + m_MinSize: {x: 0, y: 0} + m_MaxSize: {x: 0, y: 0} +--- !u!114 &10 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12010, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: + - {fileID: 12} + - {fileID: 5} + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 973 + height: 908 + m_MinSize: {x: 202, y: 442} + m_MaxSize: {x: 4002, y: 8042} + vertical: 1 + controlID: 75 +--- !u!114 &11 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 1263 + y: 0 + width: 403 + height: 908 + m_MinSize: {x: 277, y: 71} + m_MaxSize: {x: 4002, y: 4021} + m_ActualView: {fileID: 13} + m_Panes: + - {fileID: 13} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &12 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_Children: [] + m_Position: + serializedVersion: 2 + x: 0 + y: 0 + width: 973 + height: 466 + m_MinSize: {x: 202, y: 221} + m_MaxSize: {x: 4002, y: 4021} + m_ActualView: {fileID: 16} + m_Panes: + - {fileID: 16} + m_Selected: 0 + m_LastSelected: 0 +--- !u!114 &13 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12019, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_AutoRepaintOnSceneChange: 0 + m_MinSize: {x: 275, y: 50} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Inspector + m_Image: {fileID: -6905738622615590433, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_DepthBufferBits: 0 + m_Pos: + serializedVersion: 2 + x: 2 + y: 19 + width: 401 + height: 887 + m_ScrollPosition: {x: 0, y: 0} + m_InspectorMode: 0 + m_PreviewResizer: + m_CachedPref: -160 + m_ControlHash: -371814159 + m_PrefName: Preview_InspectorPreview + m_PreviewWindow: {fileID: 0} +--- !u!114 &14 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12014, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_AutoRepaintOnSceneChange: 0 + m_MinSize: {x: 230, y: 250} + m_MaxSize: {x: 10000, y: 10000} + m_TitleContent: + m_Text: Project + m_Image: {fileID: -7501376956915960154, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_DepthBufferBits: 0 + m_Pos: + serializedVersion: 2 + x: 2 + y: 19 + width: 286 + height: 421 + m_SearchFilter: + m_NameFilter: + m_ClassNames: [] + m_AssetLabels: [] + m_AssetBundleNames: [] + m_VersionControlStates: [] + m_ReferencingInstanceIDs: + m_ScenePaths: [] + m_ShowAllHits: 0 + m_SearchArea: 0 + m_Folders: + - Assets + m_ViewMode: 0 + m_StartGridSize: 64 + m_LastFolders: + - Assets + m_LastFoldersGridSize: -1 + m_LastProjectPath: /Users/danielbrauer/Unity Projects/New Unity Project 47 + m_IsLocked: 0 + m_FolderTreeState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: ee240000 + m_LastClickedID: 9454 + m_ExpandedIDs: ee24000000ca9a3bffffff7f + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_AssetTreeState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: 68fbffff + m_LastClickedID: 0 + m_ExpandedIDs: ee240000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_ListAreaState: + m_SelectedInstanceIDs: 68fbffff + m_LastClickedInstanceID: -1176 + m_HadKeyboardFocusLastEvent: 0 + m_ExpandedInstanceIDs: c6230000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 1 + m_ClientGUIView: {fileID: 0} + m_CreateAssetUtility: + m_EndAction: {fileID: 0} + m_InstanceID: 0 + m_Path: + m_Icon: {fileID: 0} + m_ResourceFile: + m_NewAssetIndexInList: -1 + m_ScrollPosition: {x: 0, y: 0} + m_GridSize: 64 + m_DirectoriesAreaWidth: 110 +--- !u!114 &15 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12015, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_AutoRepaintOnSceneChange: 1 + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Game + m_Image: {fileID: -2087823869225018852, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_DepthBufferBits: 32 + m_Pos: + serializedVersion: 2 + x: 0 + y: 19 + width: 971 + height: 421 + m_MaximizeOnPlay: 0 + m_Gizmos: 0 + m_Stats: 0 + m_SelectedSizes: 00000000000000000000000000000000000000000000000000000000000000000000000000000000 + m_TargetDisplay: 0 + m_ZoomArea: + m_HRangeLocked: 0 + m_VRangeLocked: 0 + m_HBaseRangeMin: -242.75 + m_HBaseRangeMax: 242.75 + m_VBaseRangeMin: -101 + m_VBaseRangeMax: 101 + m_HAllowExceedBaseRangeMin: 1 + m_HAllowExceedBaseRangeMax: 1 + m_VAllowExceedBaseRangeMin: 1 + m_VAllowExceedBaseRangeMax: 1 + m_ScaleWithWindow: 0 + m_HSlider: 0 + m_VSlider: 0 + m_IgnoreScrollWheelUntilClicked: 0 + m_EnableMouseInput: 1 + m_EnableSliderZoom: 0 + m_UniformScale: 1 + m_UpDirection: 1 + m_DrawArea: + serializedVersion: 2 + x: 0 + y: 17 + width: 971 + height: 404 + m_Scale: {x: 2, y: 2} + m_Translation: {x: 485.5, y: 202} + m_MarginLeft: 0 + m_MarginRight: 0 + m_MarginTop: 0 + m_MarginBottom: 0 + m_LastShownAreaInsideMargins: + serializedVersion: 2 + x: -242.75 + y: -101 + width: 485.5 + height: 202 + m_MinimalGUI: 1 + m_defaultScale: 2 + m_TargetTexture: {fileID: 0} + m_CurrentColorSpace: 0 + m_LastWindowPixelSize: {x: 1942, y: 842} + m_ClearInEditMode: 1 + m_NoCameraWarning: 1 + m_LowResolutionForAspectRatios: 01000000000100000100 +--- !u!114 &16 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12013, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_AutoRepaintOnSceneChange: 1 + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Scene + m_Image: {fileID: 2318424515335265636, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_DepthBufferBits: 32 + m_Pos: + serializedVersion: 2 + x: 0 + y: 19 + width: 971 + height: 445 + m_SceneLighting: 1 + lastFramingTime: 0 + m_2DMode: 0 + m_isRotationLocked: 0 + m_AudioPlay: 0 + m_Position: + m_Target: {x: 0, y: 0, z: 0} + speed: 2 + m_Value: {x: 0, y: 0, z: 0} + m_RenderMode: 0 + m_ValidateTrueMetals: 0 + m_SceneViewState: + showFog: 1 + showMaterialUpdate: 0 + showSkybox: 1 + showFlares: 1 + showImageEffects: 1 + grid: + xGrid: + m_Target: 0 + speed: 2 + m_Value: 0 + yGrid: + m_Target: 1 + speed: 2 + m_Value: 1 + zGrid: + m_Target: 0 + speed: 2 + m_Value: 0 + m_Rotation: + m_Target: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + speed: 2 + m_Value: {x: -0.08717229, y: 0.89959055, z: -0.21045254, w: -0.3726226} + m_Size: + m_Target: 10 + speed: 2 + m_Value: 10 + m_Ortho: + m_Target: 0 + speed: 2 + m_Value: 0 + m_LastSceneViewRotation: {x: 0, y: 0, z: 0, w: 0} + m_LastSceneViewOrtho: 0 + m_ReplacementShader: {fileID: 0} + m_ReplacementString: + m_LastLockedObject: {fileID: 0} + m_ViewIsLockedToObject: 0 +--- !u!114 &17 +MonoBehaviour: + m_ObjectHideFlags: 52 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12061, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_AutoRepaintOnSceneChange: 0 + m_MinSize: {x: 200, y: 200} + m_MaxSize: {x: 4000, y: 4000} + m_TitleContent: + m_Text: Hierarchy + m_Image: {fileID: -590624980919486359, guid: 0000000000000000d000000000000000, + type: 0} + m_Tooltip: + m_DepthBufferBits: 0 + m_Pos: + serializedVersion: 2 + x: 2 + y: 19 + width: 286 + height: 445 + m_TreeViewState: + scrollPos: {x: 0, y: 0} + m_SelectedIDs: 68fbffff + m_LastClickedID: -1176 + m_ExpandedIDs: 7efbffff00000000 + m_RenameOverlay: + m_UserAcceptedRename: 0 + m_Name: + m_OriginalName: + m_EditFieldRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 0 + height: 0 + m_UserData: 0 + m_IsWaitingForDelay: 0 + m_IsRenaming: 0 + m_OriginalEventType: 11 + m_IsRenamingFilename: 0 + m_ClientGUIView: {fileID: 0} + m_SearchString: + m_ExpandedScenes: + - + m_CurrenRootInstanceID: 0 + m_Locked: 0 + m_CurrentSortingName: TransformSorting + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Layout.wlt.meta`: + +```meta +fileFormatVersion: 2 +guid: eabc9546105bf4accac1fd62a63e88e6 +timeCreated: 1487337779 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Scripts.meta`: + +```meta +fileFormatVersion: 2 +guid: 5a9bcd70e6a4b4b05badaa72e827d8e0 +folderAsset: yes +timeCreated: 1475835190 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Scripts/Editor.meta`: + +```meta +fileFormatVersion: 2 +guid: 3ad9b87dffba344c89909c6d1b1c17e1 +folderAsset: yes +timeCreated: 1475593892 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs`: + +```cs +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using System; +using System.IO; +using System.Reflection; + +[CustomEditor(typeof(Readme))] +[InitializeOnLoad] +public class ReadmeEditor : Editor +{ + static string s_ShowedReadmeSessionStateName = "ReadmeEditor.showedReadme"; + + static string s_ReadmeSourceDirectory = "Assets/TutorialInfo"; + + const float k_Space = 16f; + + static ReadmeEditor() + { + EditorApplication.delayCall += SelectReadmeAutomatically; + } + + static void RemoveTutorial() + { + if (EditorUtility.DisplayDialog("Remove Readme Assets", + + $"All contents under {s_ReadmeSourceDirectory} will be removed, are you sure you want to proceed?", + "Proceed", + "Cancel")) + { + if (Directory.Exists(s_ReadmeSourceDirectory)) + { + FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory); + FileUtil.DeleteFileOrDirectory(s_ReadmeSourceDirectory + ".meta"); + } + else + { + Debug.Log($"Could not find the Readme folder at {s_ReadmeSourceDirectory}"); + } + + var readmeAsset = SelectReadme(); + if (readmeAsset != null) + { + var path = AssetDatabase.GetAssetPath(readmeAsset); + FileUtil.DeleteFileOrDirectory(path + ".meta"); + FileUtil.DeleteFileOrDirectory(path); + } + + AssetDatabase.Refresh(); + } + } + + static void SelectReadmeAutomatically() + { + if (!SessionState.GetBool(s_ShowedReadmeSessionStateName, false)) + { + var readme = SelectReadme(); + SessionState.SetBool(s_ShowedReadmeSessionStateName, true); + + if (readme && !readme.loadedLayout) + { + LoadLayout(); + readme.loadedLayout = true; + } + } + } + + static void LoadLayout() + { + var assembly = typeof(EditorApplication).Assembly; + var windowLayoutType = assembly.GetType("UnityEditor.WindowLayout", true); + var method = windowLayoutType.GetMethod("LoadWindowLayout", BindingFlags.Public | BindingFlags.Static); + method.Invoke(null, new object[] { Path.Combine(Application.dataPath, "TutorialInfo/Layout.wlt"), false }); + } + + static Readme SelectReadme() + { + var ids = AssetDatabase.FindAssets("Readme t:Readme"); + if (ids.Length == 1) + { + var readmeObject = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(ids[0])); + + Selection.objects = new UnityEngine.Object[] { readmeObject }; + + return (Readme)readmeObject; + } + else + { + Debug.Log("Couldn't find a readme"); + return null; + } + } + + protected override void OnHeaderGUI() + { + var readme = (Readme)target; + Init(); + + var iconWidth = Mathf.Min(EditorGUIUtility.currentViewWidth / 3f - 20f, 128f); + + GUILayout.BeginHorizontal("In BigTitle"); + { + if (readme.icon != null) + { + GUILayout.Space(k_Space); + GUILayout.Label(readme.icon, GUILayout.Width(iconWidth), GUILayout.Height(iconWidth)); + } + GUILayout.Space(k_Space); + GUILayout.BeginVertical(); + { + + GUILayout.FlexibleSpace(); + GUILayout.Label(readme.title, TitleStyle); + GUILayout.FlexibleSpace(); + } + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + } + GUILayout.EndHorizontal(); + } + + public override void OnInspectorGUI() + { + var readme = (Readme)target; + Init(); + + foreach (var section in readme.sections) + { + if (!string.IsNullOrEmpty(section.heading)) + { + GUILayout.Label(section.heading, HeadingStyle); + } + + if (!string.IsNullOrEmpty(section.text)) + { + GUILayout.Label(section.text, BodyStyle); + } + + if (!string.IsNullOrEmpty(section.linkText)) + { + if (LinkLabel(new GUIContent(section.linkText))) + { + Application.OpenURL(section.url); + } + } + + GUILayout.Space(k_Space); + } + + if (GUILayout.Button("Remove Readme Assets", ButtonStyle)) + { + RemoveTutorial(); + } + } + + bool m_Initialized; + + GUIStyle LinkStyle + { + get { return m_LinkStyle; } + } + + [SerializeField] + GUIStyle m_LinkStyle; + + GUIStyle TitleStyle + { + get { return m_TitleStyle; } + } + + [SerializeField] + GUIStyle m_TitleStyle; + + GUIStyle HeadingStyle + { + get { return m_HeadingStyle; } + } + + [SerializeField] + GUIStyle m_HeadingStyle; + + GUIStyle BodyStyle + { + get { return m_BodyStyle; } + } + + [SerializeField] + GUIStyle m_BodyStyle; + + GUIStyle ButtonStyle + { + get { return m_ButtonStyle; } + } + + [SerializeField] + GUIStyle m_ButtonStyle; + + void Init() + { + if (m_Initialized) + return; + m_BodyStyle = new GUIStyle(EditorStyles.label); + m_BodyStyle.wordWrap = true; + m_BodyStyle.fontSize = 14; + m_BodyStyle.richText = true; + + m_TitleStyle = new GUIStyle(m_BodyStyle); + m_TitleStyle.fontSize = 26; + + m_HeadingStyle = new GUIStyle(m_BodyStyle); + m_HeadingStyle.fontStyle = FontStyle.Bold; + m_HeadingStyle.fontSize = 18; + + m_LinkStyle = new GUIStyle(m_BodyStyle); + m_LinkStyle.wordWrap = false; + + // Match selection color which works nicely for both light and dark skins + m_LinkStyle.normal.textColor = new Color(0x00 / 255f, 0x78 / 255f, 0xDA / 255f, 1f); + m_LinkStyle.stretchWidth = false; + + m_ButtonStyle = new GUIStyle(EditorStyles.miniButton); + m_ButtonStyle.fontStyle = FontStyle.Bold; + + m_Initialized = true; + } + + bool LinkLabel(GUIContent label, params GUILayoutOption[] options) + { + var position = GUILayoutUtility.GetRect(label, LinkStyle, options); + + Handles.BeginGUI(); + Handles.color = LinkStyle.normal.textColor; + Handles.DrawLine(new Vector3(position.xMin, position.yMax), new Vector3(position.xMax, position.yMax)); + Handles.color = Color.white; + Handles.EndGUI(); + + EditorGUIUtility.AddCursorRect(position, MouseCursor.Link); + + return GUI.Button(position, label, LinkStyle); + } +} + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 476cc7d7cd9874016adc216baab94a0a +timeCreated: 1484146680 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Scripts/Readme.cs`: + +```cs +using System; +using UnityEngine; + +public class Readme : ScriptableObject +{ + public Texture2D icon; + public string title; + public Section[] sections; + public bool loadedLayout; + + [Serializable] + public class Section + { + public string heading, text, linkText, url; + } +} + +``` + +`samples/UniCli.Samples.Unity6LTS/Assets/TutorialInfo/Scripts/Readme.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: fcf7219bab7fe46a1ad266029b2fee19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - icon: {instanceID: 0} + executionOrder: 0 + icon: {fileID: 2800000, guid: a186f8a87ca4f4d3aa864638ad5dfb65, type: 3} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`samples/UniCli.Samples.Unity6LTS/Packages/manifest.json`: + +```json +{ + "dependencies": { + "com.unity.ai.navigation": "2.0.9", + "com.unity.collab-proxy": "2.10.0", + "com.unity.ide.rider": "3.0.38", + "com.unity.ide.visualstudio": "2.0.25", + "com.unity.inputsystem": "1.14.2", + "com.unity.multiplayer.center": "1.0.0", + "com.unity.render-pipelines.universal": "17.0.4", + "com.unity.test-framework": "1.6.0", + "com.unity.timeline": "1.8.9", + "com.unity.ugui": "2.0.0", + "com.unity.visualscripting": "1.9.7", + "com.unity.modules.accessibility": "1.0.0", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0", + "com.yucchiy.unicli-server": "file:../../../src/UniCli.Unity/Packages/com.yucchiy.unicli-server" + } +} + +``` + +`samples/UniCli.Samples.Unity6LTS/Packages/packages-lock.json`: + +```json +{ + "dependencies": { + "com.unity.ai.navigation": { + "version": "2.0.9", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.ai": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.burst": { + "version": "1.8.25", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collab-proxy": { + "version": "2.10.0", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.collections": { + "version": "2.6.2", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.burst": "1.8.23", + "com.unity.mathematics": "1.3.2", + "com.unity.test-framework": "1.4.6", + "com.unity.nuget.mono-cecil": "1.11.5", + "com.unity.test-framework.performance": "3.0.3" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "2.0.5", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.ide.rider": { + "version": "3.0.38", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.25", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.31" + }, + "url": "https://packages.unity.com" + }, + "com.unity.inputsystem": { + "version": "1.14.2", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.mathematics": { + "version": "1.3.2", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.multiplayer.center": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + } + }, + "com.unity.nuget.mono-cecil": { + "version": "1.11.5", + "depth": 3, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.render-pipelines.core": { + "version": "17.0.4", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.20", + "com.unity.mathematics": "1.3.2", + "com.unity.ugui": "2.0.0", + "com.unity.collections": "2.4.3", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.rendering.light-transport": "1.0.1" + } + }, + "com.unity.render-pipelines.universal": { + "version": "17.0.4", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.0.4", + "com.unity.shadergraph": "17.0.4", + "com.unity.render-pipelines.universal-config": "17.0.3" + } + }, + "com.unity.render-pipelines.universal-config": { + "version": "17.0.3", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.0.3" + } + }, + "com.unity.rendering.light-transport": { + "version": "1.0.1", + "depth": 2, + "source": "builtin", + "dependencies": { + "com.unity.collections": "2.2.0", + "com.unity.mathematics": "1.2.4", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.searcher": { + "version": "4.9.3", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.shadergraph": { + "version": "17.0.4", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "17.0.4", + "com.unity.searcher": "4.9.3" + } + }, + "com.unity.test-framework": { + "version": "1.6.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.ext.nunit": "2.0.3", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.test-framework.performance": { + "version": "3.2.0", + "depth": 3, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.33", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.8.9", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "2.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.visualscripting": { + "version": "1.9.7", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.yucchiy.unicli-server": { + "version": "file:../../../src/UniCli.Unity/Packages/com.yucchiy.unicli-server", + "depth": 0, + "source": "local", + "dependencies": {} + }, + "com.unity.modules.accessibility": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.hierarchycore": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.hierarchycore": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/AudioManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 1024 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 + m_RequestedDSPBufferSize: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/ClusterInputManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/DynamicsManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_ClothInterCollisionDistance: 0.1 + m_ClothInterCollisionStiffness: 0.2 + m_ContactsGeneration: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 + m_AutoSyncTransforms: 0 + m_ReuseCollisionCallbacks: 1 + m_ClothInterCollisionSettingsToggle: 0 + m_ClothGravity: {x: 0, y: -9.81, z: 0} + m_ContactPairsMode: 0 + m_BroadphaseType: 0 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 250, y: 250, z: 250} + m_WorldSubdivisions: 8 + m_FrictionType: 0 + m_EnableEnhancedDeterminism: 0 + m_EnableUnifiedHeightmaps: 1 + m_SolverType: 0 + m_DefaultMaxAngularSpeed: 50 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/EditorBuildSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: + - enabled: 1 + path: Assets/Scenes/SampleScene.unity + guid: 99c9720ab356a0642a771bea13969a05 + m_configObjects: + com.unity.input.settings.actions: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3} + m_UseUCBPForAssetBundles: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/EditorSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 14 + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 0 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerCacheSize: 10 + m_SpritePackerPaddingPower: 1 + m_Bc7TextureCompressor: 0 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref + m_ProjectGenerationRootNamespace: + m_EnableTextureStreamingInEditMode: 1 + m_EnableTextureStreamingInPlayMode: 1 + m_EnableEditorAsyncCPUTextureLoading: 0 + m_AsyncShaderCompilation: 1 + m_PrefabModeAllowAutoSave: 1 + m_EnterPlayModeOptionsEnabled: 1 + m_EnterPlayModeOptions: 0 + m_GameObjectNamingDigits: 1 + m_GameObjectNamingScheme: 0 + m_AssetNamingUsesSpace: 1 + m_InspectorUseIMGUIDefaultInspector: 0 + m_UseLegacyProbeSampleCount: 0 + m_SerializeInlineMappingsOnOneLine: 1 + m_DisableCookiesInLightmapper: 0 + m_ShadowmaskStitching: 0 + m_AssetPipelineMode: 1 + m_RefreshImportMode: 0 + m_CacheServerMode: 0 + m_CacheServerEndpoint: + m_CacheServerNamespacePrefix: default + m_CacheServerEnableDownload: 1 + m_CacheServerEnableUpload: 1 + m_CacheServerEnableAuth: 0 + m_CacheServerEnableTls: 0 + m_CacheServerValidationMode: 2 + m_CacheServerDownloadBatchSize: 128 + m_EnableEnlightenBakedGI: 0 + m_ReferencedClipsExactNaming: 1 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/GraphicsSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 16 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_VideoShadersIncludeMode: 2 + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_PreloadShadersBatchTimeLimit: -1 + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, + type: 0} + m_CustomRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, + type: 2} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_BrgStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_RenderPipelineGlobalSettingsMap: + UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 18dc0cd2c080841dea60987a38ce93fa, + type: 2} + m_LightsUseLinearIntensity: 1 + m_LightsUseColorTemperature: 1 + m_LogWhenShaderIsCompiled: 0 + m_LightProbeOutsideHullStrategy: 0 + m_CameraRelativeLightCulling: 0 + m_CameraRelativeShadowCulling: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/InputManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: joystick button 8 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Enable Debug Button 2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: backspace + altNegativeButton: + altPositiveButton: joystick button 9 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Reset + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Next + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page down + altNegativeButton: + altPositiveButton: joystick button 5 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Previous + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: page up + altNegativeButton: + altPositiveButton: joystick button 4 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Validate + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Persistent + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: right shift + altNegativeButton: + altPositiveButton: joystick button 2 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Multiplier + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: joystick button 3 + gravity: 0 + dead: 0 + sensitivity: 0 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 6 + joyNum: 0 + - serializedVersion: 3 + m_Name: Debug Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 2 + axis: 5 + joyNum: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/MemorySettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/MultiplayerManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!655991488 &1 +MultiplayerManager: + m_ObjectHideFlags: 0 + m_EnableMultiplayerRoles: 0 + m_StrippingTypes: {} + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/NavMeshAreas.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_SettingNames: + - Humanoid + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/PackageManagerSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_EnablePreviewPackages: 0 + m_EnablePackageDependencies: 0 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + oneTimeWarningShown: 0 + m_Registries: + - m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_Capabilities: 7 + m_UserSelectedRegistryName: + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_ErrorMessage: + m_Original: + m_Id: + m_Name: + m_Url: + m_Scopes: [] + m_IsDefault: 0 + m_Capabilities: 0 + m_Modified: 0 + m_Name: + m_Url: + m_Scopes: + - + m_SelectedScopeIndex: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/Physics2DSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_JobOptions: + serializedVersion: 2 + useMultithreading: 0 + useConsistencySorting: 0 + m_InterpolationPosesPerJob: 100 + m_NewContactsPerJob: 30 + m_CollideContactsPerJob: 100 + m_ClearFlagsPerJob: 200 + m_ClearBodyForcesPerJob: 200 + m_SyncDiscreteFixturesPerJob: 50 + m_SyncContinuousFixturesPerJob: 50 + m_FindNearestContactsPerJob: 100 + m_UpdateTriggerContactsPerJob: 100 + m_IslandSolverCostThreshold: 100 + m_IslandSolverBodyCostScale: 1 + m_IslandSolverContactCostScale: 10 + m_IslandSolverJointCostScale: 10 + m_IslandSolverBodiesPerJob: 50 + m_IslandSolverContactsPerJob: 50 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_CallbacksOnDisable: 1 + m_ReuseCollisionCallbacks: 0 + m_AutoSyncTransforms: 0 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/PresetManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1386491679 &1 +PresetManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_DefaultPresets: {} + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/ProjectSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 28 + productGUID: 78a9ec249d5d94ed286764e21f956157 + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: UniCli.Samples.Unity6LTS + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1024 + defaultScreenHeight: 768 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 1 + unsupportedMSAAFallback: 0 + m_SpriteBatchMaxVertexCount: 65535 + m_SpriteBatchVertexThreshold: 300 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 0 + androidDisplayOptions: 1 + androidBlitType: 0 + androidResizeableActivity: 1 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 1 + androidApplicationEntry: 2 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 1 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + dedicatedServerOptimizations: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 1 + meshDeformation: 2 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchMaxWorkerMultiple: 8 + switchNVNGraphicsFirmwareMemory: 32 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 1 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 + bundleVersion: 0.1.0 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 + useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.4 + androidMinAspectRatio: 1 + applicationIdentifier: + Android: com.UnityTechnologies.com.unity.template.urpblank + Standalone: com.Unity-Technologies.com.unity.template.urp-blank + iPhone: com.Unity-Technologies.com.unity.template.urp-blank + buildNumber: + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 1 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 23 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + androidSplitApplicationBinary: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 13.0 + tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 13.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + metalCompileShaderBinary: 0 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: 3c72c65a16f0acb438eed22b8b16c24a + templatePackageId: com.unity.template.urp-blank@17.0.12 + templateDefaultScene: Assets/Scenes/SampleScene.unity + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 2 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidEnableArm64MTE: 0 + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + androidAppCategory: 3 + useAndroidAppCategory: 1 + androidAppCategoryOther: + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 + AndroidReportGooglePlayAppDependencies: 1 + androidSymbolsSizeThreshold: 800 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + - m_BuildTarget: tvOS + m_Icons: + - m_Textures: [] + m_Width: 1280 + m_Height: 768 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 800 + m_Height: 480 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 400 + m_Height: 240 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 4640 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 2320 + m_Height: 720 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 3840 + m_Height: 1440 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 1920 + m_Height: 720 + m_Kind: 1 + m_SubKind: + m_BuildTargetBatching: [] + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: [] + m_BuildTargetGraphicsJobMode: [] + m_BuildTargetGraphicsAPIs: + - m_BuildTarget: iOSSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: AndroidPlayer + m_APIs: 150000000b000000 + m_Automatic: 0 + m_BuildTargetVRSettings: [] + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - serializedVersion: 2 + m_BuildTarget: Android + m_EncodingQuality: 1 + m_BuildTargetGroupHDRCubemapEncodingQuality: [] + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: + - m_BuildTarget: Android + m_Encoding: 1 + m_BuildTargetDefaultTextureCompressionFormat: + - serializedVersion: 3 + m_BuildTarget: Android + m_Formats: 03000000 + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + editorGfxJobOverride: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 11.0 + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 0 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 + switchUseNewStyleFilepaths: 0 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + switchUpgradedPlayerSettingsToNMETA: 0 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 2 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 2 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 32 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 0 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLEnableWebGPU: 0 + webGLPowerPreference: 2 + webGLWebAssemblyTable: 0 + webGLWebAssemblyBigInt: 0 + webGLCloseOnQuit: 0 + webWasm2023: 0 + scriptingDefineSymbols: {} + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: + Android: 1 + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + il2cppStacktraceInformation: {} + managedStrippingLevel: {} + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + editorAssembliesCompatibilityLevel: 1 + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: UniCli.Samples.Unity6LTS + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: UniCli.Samples.Unity6LTS + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} + metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} + cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 0 + hmiCpuConfiguration: + hmiLogStartupTiming: 0 + qnxGraphicConfPath: + apiCompatibilityLevel: 6 + captureStartupLogs: {} + activeInputHandler: 1 + windowsGamepadBackendHint: 0 + cloudProjectId: 7a40a374-9107-485b-9d7a-25f80bd9dbbb + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: UniCli.Samples.Unity6LTS + organizationId: yucchiy + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 + androidVulkanDenyFilterList: [] + androidVulkanAllowFilterList: [] + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/ProjectVersion.txt`: + +```txt +m_EditorVersion: 6000.0.62f1 +m_EditorVersionWithRevision: 6000.0.62f1 (f99f05b3e950) + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/QualitySettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 1 + m_QualitySettings: + - serializedVersion: 4 + name: Mobile + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + skinWeights: 2 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 1 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 100 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 1 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858, + type: 2} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: + - Standalone + - serializedVersion: 4 + name: PC + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + skinWeights: 4 + globalTextureMipmapLimit: 0 + textureMipmapLimitSettings: [] + anisotropicTextures: 2 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 1 + useLegacyDetailDistribution: 1 + adaptiveVsync: 0 + vSyncCount: 0 + realtimeGICPUUsage: 100 + adaptiveVsyncExtraA: 0 + adaptiveVsyncExtraB: 0 + lodBias: 2 + maximumLODLevel: 0 + enableLODCrossFade: 1 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, + type: 2} + terrainQualityOverrides: 0 + terrainPixelError: 1 + terrainDetailDensityScale: 1 + terrainBasemapDistance: 1000 + terrainDetailDistance: 80 + terrainTreeDistance: 5000 + terrainBillboardStart: 50 + terrainFadeLength: 5 + terrainMaxTrees: 50 + excludedTargetPlatforms: + - Android + - iPhone + m_TextureMipmapLimitGroupNames: [] + m_PerPlatformDefaultQuality: + Android: 0 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Lumin: 0 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + Server: 0 + Stadia: 0 + Standalone: 1 + WebGL: 0 + Windows Store Apps: 0 + XboxOne: 0 + iPhone: 0 + tvOS: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/ShaderGraphSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de02f9e1d18f588468e474319d09a723, type: 3} + m_Name: + m_EditorClassIdentifier: + shaderVariantLimit: 128 + overrideShaderVariantLimit: 0 + customInterpolatorErrorThreshold: 32 + customInterpolatorWarningThreshold: 16 + customHeatmapValues: {fileID: 0} + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/TagManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 + m_RenderingLayers: + - Default + - Light Layer 1 + - Light Layer 2 + - Light Layer 3 + - Light Layer 4 + - Light Layer 5 + - Light Layer 6 + - Light Layer 7 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/TimeManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/URPProjectSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 247994e1f5a72c2419c26a37e9334c01, type: 3} + m_Name: + m_EditorClassIdentifier: + m_LastMaterialVersion: 10 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/UnityConnectSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 1 + m_Enabled: 0 + m_TestMode: 0 + m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events + m_EventUrl: https://cdp.cloud.unity3d.com/v1/events + m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com + m_TestInitMode: 0 + CrashReportingSettings: + m_EventUrl: https://perf-events.cloud.unity3d.com + m_Enabled: 0 + m_LogBufferSize: 10 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_TestMode: 0 + m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/VFXManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!937362698 &1 +VFXManager: + m_ObjectHideFlags: 0 + m_IndirectShader: {fileID: 0} + m_CopyBufferShader: {fileID: 0} + m_SortShader: {fileID: 0} + m_StripUpdateShader: {fileID: 0} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/VersionControlSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 + +``` + +`samples/UniCli.Samples.Unity6LTS/ProjectSettings/XRSettings.asset`: + +```asset +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} +``` + +`src/UniCli.Client.Tests/ArgumentParserTests.cs`: + +```cs +using System.Buffers; +using System.Text; +using System.Text.Json; +using UniCli.Client; +using UniCli.Protocol; + +namespace UniCli.Client.Tests; + +public class ParseKeyValueArgsTests +{ + [Fact] + public void EmptyArgs_ReturnsEmptyDictionary() + { + var result = ArgumentParser.ParseKeyValueArgs([]); + Assert.Empty(result); + } + + [Fact] + public void SingleKeyValue_ParsesCorrectly() + { + var result = ArgumentParser.ParseKeyValueArgs(["--name", "hello"]); + Assert.Single(result); + Assert.Equal(["hello"], result["name"]); + } + + [Fact] + public void MultipleKeyValues_ParsesAll() + { + var result = ArgumentParser.ParseKeyValueArgs(["--name", "foo", "--count", "3"]); + Assert.Equal(2, result.Count); + Assert.Equal(["foo"], result["name"]); + Assert.Equal(["3"], result["count"]); + } + + [Fact] + public void BooleanFlag_WithoutValue_AddsNull() + { + var result = ArgumentParser.ParseKeyValueArgs(["--verbose"]); + Assert.Single(result); + Assert.Equal([null], result["verbose"]); + } + + [Fact] + public void BooleanFlag_FollowedByAnotherFlag_AddsNull() + { + var result = ArgumentParser.ParseKeyValueArgs(["--verbose", "--name", "test"]); + Assert.Equal(2, result.Count); + Assert.Equal([null], result["verbose"]); + Assert.Equal(["test"], result["name"]); + } + + [Fact] + public void RepeatedKey_AccumulatesValues() + { + var result = ArgumentParser.ParseKeyValueArgs(["--tag", "a", "--tag", "b", "--tag", "c"]); + Assert.Single(result); + Assert.Equal(["a", "b", "c"], result["tag"]); + } + + [Fact] + public void CaseInsensitive_Keys() + { + var result = ArgumentParser.ParseKeyValueArgs(["--Name", "foo", "--NAME", "bar"]); + Assert.Single(result); + Assert.Equal(["foo", "bar"], result["name"]); + } + + [Fact] + public void NonFlagArgs_AreIgnored() + { + var result = ArgumentParser.ParseKeyValueArgs(["positional", "--key", "val"]); + Assert.Single(result); + Assert.Equal(["val"], result["key"]); + } +} + +public class BuildJsonFromKeyValuesTests +{ + private static string Build(Dictionary> pairs, CommandFieldInfo[] fields) + => ArgumentParser.BuildJsonFromKeyValues(pairs, fields); + + private static Dictionary> Pairs(params (string key, string? value)[] items) + { + var dict = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var (key, value) in items) + { + if (!dict.TryGetValue(key, out var list)) + { + list = []; + dict[key] = list; + } + list.Add(value); + } + return dict; + } + + [Fact] + public void EmptyPairs_ReturnsEmptyObject() + { + var json = Build(Pairs(), []); + Assert.Equal("{}", json); + } + + [Fact] + public void StringField_WritesString() + { + var fields = new[] { new CommandFieldInfo { name = "name", type = "string" } }; + var json = Build(Pairs(("name", "hello")), fields); + using var doc = JsonDocument.Parse(json); + Assert.Equal("hello", doc.RootElement.GetProperty("name").GetString()); + } + + [Fact] + public void IntField_WritesNumber() + { + var fields = new[] { new CommandFieldInfo { name = "count", type = "int" } }; + var json = Build(Pairs(("count", "42")), fields); + using var doc = JsonDocument.Parse(json); + Assert.Equal(42, doc.RootElement.GetProperty("count").GetInt32()); + } + + [Fact] + public void FloatField_WritesNumber() + { + var fields = new[] { new CommandFieldInfo { name = "rate", type = "float" } }; + var json = Build(Pairs(("rate", "1.5")), fields); + using var doc = JsonDocument.Parse(json); + Assert.True(Math.Abs(1.5 - doc.RootElement.GetProperty("rate").GetDouble()) < 0.001); + } + + [Fact] + public void BoolField_WritesBool() + { + var fields = new[] { new CommandFieldInfo { name = "active", type = "bool" } }; + var json = Build(Pairs(("active", "true")), fields); + using var doc = JsonDocument.Parse(json); + Assert.True(doc.RootElement.GetProperty("active").GetBoolean()); + } + + [Fact] + public void NullValue_WritesTrue() + { + var fields = new[] { new CommandFieldInfo { name = "verbose", type = "bool" } }; + var json = Build(Pairs(("verbose", null)), fields); + using var doc = JsonDocument.Parse(json); + Assert.True(doc.RootElement.GetProperty("verbose").GetBoolean()); + } + + [Fact] + public void ArrayField_WritesArray() + { + var fields = new[] { new CommandFieldInfo { name = "tags", type = "string[]" } }; + var pairs = Pairs(("tags", "a"), ("tags", "b")); + var json = Build(pairs, fields); + using var doc = JsonDocument.Parse(json); + var arr = doc.RootElement.GetProperty("tags"); + Assert.Equal(JsonValueKind.Array, arr.ValueKind); + Assert.Equal(2, arr.GetArrayLength()); + Assert.Equal("a", arr[0].GetString()); + Assert.Equal("b", arr[1].GetString()); + } + + [Fact] + public void IntArrayField_WritesNumberArray() + { + var fields = new[] { new CommandFieldInfo { name = "ids", type = "int[]" } }; + var pairs = Pairs(("ids", "1"), ("ids", "2"), ("ids", "3")); + var json = Build(pairs, fields); + using var doc = JsonDocument.Parse(json); + var arr = doc.RootElement.GetProperty("ids"); + Assert.Equal(3, arr.GetArrayLength()); + Assert.Equal(1, arr[0].GetInt32()); + Assert.Equal(2, arr[1].GetInt32()); + Assert.Equal(3, arr[2].GetInt32()); + } + + [Fact] + public void UnknownField_DefaultsToString() + { + var json = Build(Pairs(("unknown", "value")), []); + using var doc = JsonDocument.Parse(json); + Assert.Equal("value", doc.RootElement.GetProperty("unknown").GetString()); + } + + [Fact] + public void FieldNameCase_UsesSchemaCase() + { + var fields = new[] { new CommandFieldInfo { name = "MyField", type = "string" } }; + var json = Build(Pairs(("myfield", "val")), fields); + using var doc = JsonDocument.Parse(json); + Assert.Equal("val", doc.RootElement.GetProperty("MyField").GetString()); + } + + [Fact] + public void RepeatedScalar_UsesLastValue() + { + var fields = new[] { new CommandFieldInfo { name = "name", type = "string" } }; + var pairs = Pairs(("name", "first"), ("name", "second")); + var json = Build(pairs, fields); + using var doc = JsonDocument.Parse(json); + Assert.Equal("second", doc.RootElement.GetProperty("name").GetString()); + } +} + +public class WriteScalarValueTests +{ + private static string WriteScalar(string value, string fieldType) + { + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer); + writer.WriteStartObject(); + writer.WritePropertyName("v"); + ArgumentParser.WriteScalarValue(writer, value, fieldType); + writer.WriteEndObject(); + writer.Flush(); + var json = Encoding.UTF8.GetString(buffer.WrittenSpan); + using var doc = JsonDocument.Parse(json); + return doc.RootElement.GetProperty("v").GetRawText(); + } + + [Fact] + public void Int_WritesNumber() => Assert.Equal("42", WriteScalar("42", "int")); + + [Fact] + public void Float_WritesNumber() + { + var result = WriteScalar("1.5", "float"); + Assert.True(double.TryParse(result, out var val)); + Assert.True(Math.Abs(1.5 - val) < 0.001); + } + + [Fact] + public void Double_WritesNumber() + { + var result = WriteScalar("3.14", "double"); + Assert.True(double.TryParse(result, out var val)); + Assert.True(Math.Abs(3.14 - val) < 0.001); + } + + [Fact] + public void Bool_WritesBool() => Assert.Equal("true", WriteScalar("true", "bool")); + + [Fact] + public void BoolFalse_WritesBool() => Assert.Equal("false", WriteScalar("false", "bool")); + + [Fact] + public void InvalidInt_FallsBackToString() => Assert.Equal("\"abc\"", WriteScalar("abc", "int")); + + [Fact] + public void UnknownType_WritesString() => Assert.Equal("\"hello\"", WriteScalar("hello", "custom")); +} + +public class IsRetryableErrorTests +{ + [Fact] + public void ServerClosedConnection_IsRetryable() + => Assert.True(ArgumentParser.IsRetryableError("Server closed connection unexpectedly")); + + [Fact] + public void CommunicationError_IsRetryable() + => Assert.True(ArgumentParser.IsRetryableError("Communication error: broken pipe")); + + [Fact] + public void UnknownCommand_IsNotRetryable() + => Assert.False(ArgumentParser.IsRetryableError("Unknown command: Foo")); + + [Fact] + public void EmptyString_IsNotRetryable() + => Assert.False(ArgumentParser.IsRetryableError("")); +} + +public class ArrayTypeHelperTests +{ + [Fact] + public void StringArray_IsArrayType() => Assert.True(ArgumentParser.IsArrayType("string[]")); + + [Fact] + public void IntArray_IsArrayType() => Assert.True(ArgumentParser.IsArrayType("int[]")); + + [Fact] + public void PlainString_IsNotArrayType() => Assert.False(ArgumentParser.IsArrayType("string")); + + [Fact] + public void GetElementType_StringArray() => Assert.Equal("string", ArgumentParser.GetArrayElementType("string[]")); + + [Fact] + public void GetElementType_IntArray() => Assert.Equal("int", ArgumentParser.GetArrayElementType("int[]")); +} + +``` + +`src/UniCli.Client.Tests/CommandExecutorTests.cs`: + +```cs +using System.IO; +using UniCli.Protocol; +using UniCli.Client; + +namespace UniCli.Client.Tests; + +public class BuildLaunchStartedMessageTests +{ + [Fact] + public void IncludesAbsoluteProjectRootAndRetryGuidance() + { + var result = CommandExecutor.BuildLaunchStartedMessage("src/UniCli.Unity"); + + Assert.Contains("Unity Editor was not running, so UniCli started it.", result); + Assert.Contains(Path.GetFullPath("src/UniCli.Unity"), result); + Assert.Contains("run the command again", result); + } + + [Fact] + public void AssetsPath_IsNormalizedToProjectRoot() + { + var result = CommandExecutor.BuildLaunchStartedMessage("src/UniCli.Unity/Assets"); + + Assert.Contains(Path.GetFullPath("src/UniCli.Unity"), result); + Assert.DoesNotContain(Path.GetFullPath("src/UniCli.Unity/Assets"), result); + } +} + +public class FormatTypeDetailHeadingTests +{ + [Fact] + public void ReturnsTypeName_WhenUnique() + { + var detail = new CommandTypeDetail + { + typeName = "Duplicate", + typeId = "Tests:Alpha.Duplicate" + }; + + var result = CommandExecutor.FormatTypeDetailHeading( + detail, + new Dictionary { ["Duplicate"] = 1 }); + + Assert.Equal("Duplicate", result); + } + + [Fact] + public void AppendsTypeId_WhenTypeNameCollides() + { + var detail = new CommandTypeDetail + { + typeName = "Duplicate", + typeId = "Tests:Alpha.Duplicate" + }; + + var result = CommandExecutor.FormatTypeDetailHeading( + detail, + new Dictionary { ["Duplicate"] = 2 }); + + Assert.Equal("Duplicate (Tests:Alpha.Duplicate)", result); + } +} + +``` + +`src/UniCli.Client.Tests/ProjectIdentifierTests.cs`: + +```cs +using System.Runtime.InteropServices; + +namespace UniCli.Client.Tests; + +public class NormalizePathForHashTests +{ + [SkippableFact] + public void BackslashesAreReplacedWithForwardSlashes_Unix() + { + Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + Assert.Equal("C:/Users/dev/MyProject/Assets", ProjectIdentifier.NormalizePathForHash(@"C:\Users\dev\MyProject\Assets")); + } + + [SkippableFact] + public void BackslashesAreReplacedWithForwardSlashes_Windows() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + Assert.Equal("c:/users/dev/myproject/assets", ProjectIdentifier.NormalizePathForHash(@"C:\Users\dev\MyProject\Assets")); + } + + [SkippableFact] + public void ForwardSlashesArePreserved_Unix() + { + Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + Assert.Equal("C:/Users/dev/MyProject/Assets", ProjectIdentifier.NormalizePathForHash("C:/Users/dev/MyProject/Assets")); + } + + [SkippableFact] + public void ForwardSlashesArePreserved_Windows() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + Assert.Equal("c:/users/dev/myproject/assets", ProjectIdentifier.NormalizePathForHash("C:/Users/dev/MyProject/Assets")); + } + + [SkippableFact] + public void MixedSeparatorsAreNormalized_Unix() + { + Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + Assert.Equal("C:/Users/dev/MyProject/Assets", ProjectIdentifier.NormalizePathForHash(@"C:\Users/dev\MyProject/Assets")); + } + + [SkippableFact] + public void MixedSeparatorsAreNormalized_Windows() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + Assert.Equal("c:/users/dev/myproject/assets", ProjectIdentifier.NormalizePathForHash(@"C:\Users/dev\MyProject/Assets")); + } +} + +public class GetProjectHashTests +{ + [Fact] + public void SamePathWithDifferentSeparators_ProducesSameHash() + { + var hashForward = ProjectIdentifier.GetProjectHash( + ProjectIdentifier.NormalizePathForHash("C:/Users/dev/MyProject/Assets")); + var hashBackslash = ProjectIdentifier.GetProjectHash( + ProjectIdentifier.NormalizePathForHash(@"C:\Users\dev\MyProject\Assets")); + + Assert.Equal(hashForward, hashBackslash); + } + + [Fact] + public void DifferentPaths_ProduceDifferentHashes() + { + var hash1 = ProjectIdentifier.GetProjectHash( + ProjectIdentifier.NormalizePathForHash("C:/Users/dev/ProjectA/Assets")); + var hash2 = ProjectIdentifier.GetProjectHash( + ProjectIdentifier.NormalizePathForHash("C:/Users/dev/ProjectB/Assets")); + + Assert.NotEqual(hash1, hash2); + } + + [Fact] + public void HashIsEightCharacters() + { + var hash = ProjectIdentifier.GetProjectHash( + ProjectIdentifier.NormalizePathForHash("/Users/dev/MyProject/Assets")); + + Assert.Equal(8, hash.Length); + } + + [Fact] + public void HashIsLowercaseHex() + { + var hash = ProjectIdentifier.GetProjectHash( + ProjectIdentifier.NormalizePathForHash("/Users/dev/MyProject/Assets")); + + Assert.Matches("^[0-9a-f]{8}$", hash); + } +} + +``` + +`src/UniCli.Client.Tests/UniCli.Client.Tests.csproj`: + +```csproj + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + +``` + +`src/UniCli.Client.Tests/UnityLauncherTests.cs`: + +```cs +using System.Runtime.InteropServices; + +namespace UniCli.Client.Tests; + +public class CreateStartInfoTests +{ + [SkippableFact] + public void UsesOpenCommandWithAppBundle_Unix() + { + Skip.If(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + + var editorPath = "/Applications/Unity/Hub/Editor/6000.0.0f1/Unity.app/Contents/MacOS/Unity"; + var projectRoot = "/tmp/MyProject"; + + var startInfo = UnityLauncher.CreateStartInfo(editorPath, projectRoot, isMacOS: true); + + Assert.Equal("open", startInfo.FileName); + Assert.False(startInfo.UseShellExecute); + Assert.False(startInfo.RedirectStandardOutput); + Assert.False(startInfo.RedirectStandardError); + Assert.Equal(["-a", "/Applications/Unity/Hub/Editor/6000.0.0f1/Unity.app", "--args", "-projectPath", projectRoot], startInfo.ArgumentList); + } + + [SkippableFact] + public void UsesDetachedDirectLaunch_Windows() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + + var editorPath = @"C:\Program Files\Unity\Hub\Editor\6000.0.0f1\Editor\Unity.exe"; + var projectRoot = @"C:\work\MyProject"; + + var startInfo = UnityLauncher.CreateStartInfo(editorPath, projectRoot, isMacOS: false); + + Assert.Equal(editorPath, startInfo.FileName); + Assert.True(startInfo.UseShellExecute); + Assert.False(startInfo.RedirectStandardOutput); + Assert.False(startInfo.RedirectStandardError); + Assert.Equal(["-projectPath", projectRoot], startInfo.ArgumentList); + } +} + +public class TryGetMacOSAppBundlePathTests +{ + [SkippableFact] + public void UnityBinaryInsideAppBundle_ReturnsBundlePath() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)); + Assert.Equal("/Applications/Unity/Hub/Editor/6000.0.0f1/Unity.app", + UnityLauncher.TryGetMacOSAppBundlePath("/Applications/Unity/Hub/Editor/6000.0.0f1/Unity.app/Contents/MacOS/Unity")); + } + + [SkippableFact] + public void NonBundlePath_ReturnsNull() + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)); + Assert.Null(UnityLauncher.TryGetMacOSAppBundlePath("/opt/unity/Editor/Unity")); + } +} + +public class NormalizeProjectRootTests +{ + [Fact] + public void RelativeProjectRoot_IsConvertedToAbsolutePath() + { + var result = UnityLauncher.NormalizeProjectRoot("src/UniCli.Unity"); + + Assert.Equal(Path.GetFullPath("src/UniCli.Unity"), result); + } + + [Fact] + public void AssetsPath_IsTrimmedToProjectRootAndConvertedToAbsolutePath() + { + var result = UnityLauncher.NormalizeProjectRoot("src/UniCli.Unity/Assets"); + + Assert.Equal(Path.GetFullPath("src/UniCli.Unity"), result); + } +} + +``` + +`src/UniCli.Client/ArgumentParser.cs`: + +```cs +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using UniCli.Protocol; + +namespace UniCli.Client; + +internal static class ArgumentParser +{ + public static Dictionary> ParseKeyValueArgs(string[] args) + { + var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + for (var i = 0; i < args.Length; i++) + { + if (!args[i].StartsWith("--")) + continue; + + var key = args[i].Substring(2); + + if (!result.TryGetValue(key, out var list)) + { + list = new List(); + result[key] = list; + } + + if (i + 1 < args.Length && !args[i + 1].StartsWith("--")) + { + list.Add(args[i + 1]); + i++; + } + else + { + list.Add(null); + } + } + + return result; + } + + public static bool IsArrayType(string fieldType) => fieldType.EndsWith("[]"); + + public static string GetArrayElementType(string fieldType) => fieldType.Substring(0, fieldType.Length - 2); + + public static string BuildJsonFromKeyValues(Dictionary> pairs, CommandFieldInfo[] fields) + { + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer); + + writer.WriteStartObject(); + + foreach (var (key, values) in pairs) + { + var field = fields.FirstOrDefault( + f => f.name.Equals(key, StringComparison.OrdinalIgnoreCase)); + var fieldName = field?.name ?? key; + var fieldType = field?.type ?? "string"; + + writer.WritePropertyName(fieldName); + + if (IsArrayType(fieldType)) + { + var elementType = GetArrayElementType(fieldType); + writer.WriteStartArray(); + foreach (var value in values) + { + if (value != null) + WriteScalarValue(writer, value, elementType); + } + writer.WriteEndArray(); + } + else + { + var value = values[values.Count - 1]; + if (value == null) + writer.WriteBooleanValue(true); + else + WriteScalarValue(writer, value, fieldType); + } + } + + writer.WriteEndObject(); + writer.Flush(); + + return Encoding.UTF8.GetString(buffer.WrittenSpan); + } + + public static void WriteScalarValue(Utf8JsonWriter writer, string value, string fieldType) + { + switch (fieldType) + { + case "int" when int.TryParse(value, out var intVal): + writer.WriteNumberValue(intVal); + break; + case "float" when float.TryParse(value, out var floatVal): + writer.WriteNumberValue(floatVal); + break; + case "double" when double.TryParse(value, out var doubleVal): + writer.WriteNumberValue(doubleVal); + break; + case "bool" when bool.TryParse(value, out var boolVal): + writer.WriteBooleanValue(boolVal); + break; + default: + writer.WriteStringValue(value); + break; + } + } + + public static bool IsRetryableError(string error) + { + return error.StartsWith("Server closed connection", StringComparison.Ordinal) + || error.StartsWith("Communication error", StringComparison.Ordinal); + } +} + +``` + +`src/UniCli.Client/CliResult.cs`: + +```cs +using UniCli.Protocol; + +namespace UniCli.Client; + +internal sealed class CliResult +{ + public bool Success { get; init; } + public string Message { get; init; } = ""; + public string? JsonData { get; init; } + public string? FormattedText { get; init; } + + public static CliResult FromResponse(CommandResponse response) + { + var hasData = !string.IsNullOrEmpty(response.data); + var isText = response.format == "text"; + + return new CliResult + { + Success = response.success, + Message = response.message, + JsonData = hasData && !isText ? response.data : null, + FormattedText = hasData && isText ? response.data : null + }; + } + + public static CliResult Error(string message) => new() + { + Success = false, + Message = message + }; + + public static CliResult Ok(string message, string? jsonData = null, string? formattedText = null) => new() + { + Success = true, + Message = message, + JsonData = jsonData, + FormattedText = formattedText + }; +} + +``` + +`src/UniCli.Client/CommandExecutor.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Client; + +internal static class CommandExecutor +{ + private const int DefaultMaxRetries = 5; + private const int RetryDelayMs = 1000; + + public static async Task> SendAsync( + string command, string data, int timeoutMs = 0, string format = "", + int maxRetries = DefaultMaxRetries, bool focusEditor = false) + { + var explicitPath = Environment.GetEnvironmentVariable("UNICLI_PROJECT"); + var unityProjectRoot = explicitPath ?? ProjectIdentifier.FindUnityProjectRoot(); + + if (unityProjectRoot == null) + return Result.Error( + "Unity project not found.\n Run this command from within a Unity project directory,\n or set UNICLI_PROJECT environment variable to specify the project path."); + + var pipeName = ProjectIdentifier.GetPipeName(unityProjectRoot); + + var request = new CommandRequest + { + command = command, + data = data, + format = format, + cwd = Directory.GetCurrentDirectory(), + clientVersion = VersionInfo.Version + }; + + string lastError = ""; + long focusSavedState = 0; + for (var attempt = 1; attempt <= maxRetries; attempt++) + { + using var client = new PipeClient(pipeName); + + var connectResult = await client.ConnectAsync(); + if (connectResult.IsError) + { + lastError = connectResult.ErrorValue; + + if (!UnityProcessActivator.IsUnityRunning(unityProjectRoot)) + { + Console.Error.WriteLine("Unity is not running, launching..."); + var launchResult = UnityLauncher.Launch(unityProjectRoot); + if (launchResult.IsError) + { + await RestoreFocusAsync(focusSavedState); + return Result.Error( + $"Failed to launch Unity Editor: {launchResult.ErrorValue}"); + } + + await RestoreFocusAsync(focusSavedState); + return Result.Error( + BuildLaunchStartedMessage(unityProjectRoot)); + } + + focusSavedState = await TryFocusOnceAsync( + focusEditor, focusSavedState, unityProjectRoot); + + if (attempt < maxRetries) + { + Console.Error.WriteLine( + $"Waiting for server... (attempt {attempt}/{maxRetries})"); + await Task.Delay(RetryDelayMs); + } + continue; + } + + var result = await client.SendCommandAsync(request, timeoutMs); + if (result.IsSuccess) + { + await RestoreFocusAsync(focusSavedState); + return result; + } + + if (!IsRetryableError(result.ErrorValue)) + { + await RestoreFocusAsync(focusSavedState); + return result; + } + + lastError = result.ErrorValue; + focusSavedState = await TryFocusOnceAsync( + focusEditor, focusSavedState, unityProjectRoot); + + if (attempt < maxRetries) + { + Console.Error.WriteLine( + $"Server disconnected, retrying... (attempt {attempt}/{maxRetries})"); + await Task.Delay(RetryDelayMs); + } + } + + await RestoreFocusAsync(focusSavedState); + return Result.Error( + $"Failed to communicate with Unity Editor server after {maxRetries} attempts.\n" + + $" Project: {unityProjectRoot}\n" + + $" Pipe: {pipeName}\n" + + $" Last error: {lastError}\n\n" + + $" Make sure:\n" + + $" - Unity Editor is running with the project open\n" + + $" - UniCli server package is installed and enabled"); + } + + private static bool IsRetryableError(string error) => ArgumentParser.IsRetryableError(error); + + internal static string BuildLaunchStartedMessage(string projectRoot) + { + var normalizedProjectRoot = UnityLauncher.NormalizeProjectRoot(projectRoot); + return "Unity Editor was not running, so UniCli started it.\n" + + $" Project: {normalizedProjectRoot}\n" + + " Wait for Unity Editor to finish loading, then run the command again."; + } + + private static async Task TryFocusOnceAsync( + bool focusEditor, long savedState, string projectRoot) + { + if (!focusEditor || savedState != 0) + return savedState; + + var pid = UnityProcessActivator.ReadPidFile(projectRoot); + if (pid <= 0) + return 0; + + Console.Error.WriteLine("Server not responding, focusing Unity Editor..."); + return await UnityProcessActivator.TryActivateAsync(projectRoot); + } + + private static async Task RestoreFocusAsync(long savedState) + { + if (savedState != 0) + await UnityProcessActivator.TryRestoreFocusAsync(savedState); + } + + public static async Task PrintCommandHelpAsync(string commandName) + { + var result = await SendAsync("Commands.List", ""); + + return result.Match( + onSuccess: response => + { + if (!response.success || string.IsNullOrEmpty(response.data)) + { + Console.Error.WriteLine($"Failed to fetch command list: {response.message}"); + return 1; + } + + var listResponse = JsonSerializer.Deserialize(response.data, ProtocolJsonContext.Default.CommandListResponse); + var cmd = listResponse?.commands?.FirstOrDefault( + c => c.name.Equals(commandName, StringComparison.OrdinalIgnoreCase)); + + if (cmd == null) + { + Console.Error.WriteLine($"Unknown command: {commandName}"); + return 1; + } + + WriteCommandHelp(cmd); + return 0; + }, + onError: error => + { + Console.Error.WriteLine(error); + return 1; + }); + } + + private static void WriteCommandHelp(CommandInfo cmd) + { + Console.WriteLine($"{cmd.name} - {cmd.description}"); + + if (cmd.requestFields != null && cmd.requestFields.Length > 0) + { + Console.WriteLine(); + Console.WriteLine("Request parameters:"); + WriteFieldTable(cmd.requestFields, includeDefaultValue: true); + WriteTypeDetails("Request type details:", cmd.requestTypeDetails, includeDefaultValue: true); + } + + if (cmd.responseFields != null && cmd.responseFields.Length > 0) + { + Console.WriteLine(); + Console.WriteLine("Response fields:"); + WriteFieldTable(cmd.responseFields, includeDefaultValue: false); + WriteTypeDetails("Response type details:", cmd.responseTypeDetails, includeDefaultValue: false); + } + } + + private static void WriteFieldTable(CommandFieldInfo[] fields, bool includeDefaultValue, string indent = " ") + { + if (fields.Length == 0) + return; + + var maxNameLen = fields.Max(f => f.name.Length); + var maxTypeLen = fields.Max(f => f.type.Length); + + foreach (var field in fields) + { + var defaultPart = includeDefaultValue && !string.IsNullOrEmpty(field.defaultValue) + ? $" (default: {field.defaultValue})" + : ""; + Console.WriteLine($"{indent}{field.name.PadRight(maxNameLen)} {field.type.PadRight(maxTypeLen)}{defaultPart}"); + } + } + + private static void WriteTypeDetails(string title, CommandTypeDetail[]? details, bool includeDefaultValue) + { + if (details == null || details.Length == 0) + return; + + var typeNameCounts = details + .GroupBy(d => d.typeName, StringComparer.Ordinal) + .ToDictionary(g => g.Key, g => g.Count(), StringComparer.Ordinal); + + Console.WriteLine(); + Console.WriteLine(title); + + foreach (var detail in details) + { + Console.WriteLine($" {FormatTypeDetailHeading(detail, typeNameCounts)}:"); + WriteFieldTable(detail.fields, includeDefaultValue, " "); + } + } + + internal static string FormatTypeDetailHeading( + CommandTypeDetail detail, + IReadOnlyDictionary typeNameCounts) + { + if (!typeNameCounts.TryGetValue(detail.typeName, out var count) || count <= 1) + return detail.typeName; + + return $"{detail.typeName} ({detail.typeId})"; + } + + public static async Task ExecuteWithKeyValueAsync( + string commandName, string[] args, int timeoutMs = 0, bool json = false, + bool focusEditor = true) + { + var listResult = await SendAsync("Commands.List", ""); + if (listResult.IsError) + return CliResult.Error(listResult.ErrorValue); + + var listResponse = listResult.Match( + onSuccess: r => + { + if (!r.success || string.IsNullOrEmpty(r.data)) return null; + return JsonSerializer.Deserialize(r.data, ProtocolJsonContext.Default.CommandListResponse); + }, + onError: _ => null); + + var cmd = listResponse?.commands?.FirstOrDefault( + c => c.name.Equals(commandName, StringComparison.OrdinalIgnoreCase)); + + var fields = cmd?.requestFields ?? Array.Empty(); + var pairs = ArgumentParser.ParseKeyValueArgs(args); + + foreach (var key in pairs.Keys) + { + if (fields.All(f => !f.name.Equals(key, StringComparison.OrdinalIgnoreCase))) + { + Console.Error.WriteLine($"Warning: unknown parameter '{key}' for command '{commandName}'"); + } + } + + var jsonData = ArgumentParser.BuildJsonFromKeyValues(pairs, fields); + return await ExecuteAsync(commandName, jsonData, timeoutMs, json, focusEditor); + } + + public static async Task ExecuteAsync( + string command, string data, int timeoutMs = 0, bool json = false, + bool focusEditor = true) + { + var format = json ? "json" : "text"; + var result = await SendAsync(command, data, timeoutMs, format, focusEditor: focusEditor); + + return result.Match( + onSuccess: response => + { + if (!string.IsNullOrEmpty(response.versionWarning)) + Console.Error.WriteLine($"Warning: {response.versionWarning}"); + return CliResult.FromResponse(response); + }, + onError: CliResult.Error); + } +} + +``` + +`src/UniCli.Client/Commands/Commands.Check.cs`: + +```cs +using System; +using System.Buffers; +using System.Text.Json; +using System.Threading.Tasks; +using ConsoleAppFramework; +using UniCli.Protocol; + +namespace UniCli.Client; + +public partial class Commands +{ + /// + /// Check if the UniCli server package is installed and the server is running + /// + [Command("check")] + public async Task Check(bool json = false) + { + var explicitPath = Environment.GetEnvironmentVariable("UNICLI_PROJECT"); + var projectRoot = explicitPath ?? ProjectIdentifier.FindUnityProjectRoot(); + + if (projectRoot == null) + { + var result = CliResult.Error("Unity project not found."); + return OutputWriter.Write(result, json); + } + + var manifestPath = ManifestEditor.GetManifestPath(projectRoot); + var source = ManifestEditor.FindPackageSource(manifestPath); + var installed = source != null; + + var serverRunning = false; + string? serverVersion = null; + if (installed) + { + (serverRunning, serverVersion) = await ProbeServerAsync(projectRoot); + } + + var clientVersion = VersionInfo.Version; + var versionMatch = serverVersion != null && serverVersion == clientVersion; + + var cliResult = BuildCheckResult(installed, source, serverRunning, clientVersion, serverVersion, versionMatch); + return OutputWriter.Write(cliResult, json); + } + + private static async Task<(bool running, string? serverVersion)> ProbeServerAsync(string projectRoot) + { + var pipeName = ProjectIdentifier.GetPipeName(projectRoot); + + using var client = new PipeClient(pipeName); + var connectResult = await client.ConnectAsync(timeoutMs: 2000); + if (connectResult.IsError) + return (false, null); + + var request = new CommandRequest + { + command = "Project.Inspect", + data = "", + format = "json" + }; + + var sendResult = await client.SendCommandAsync(request, timeoutMs: 2000); + return sendResult.Match( + onSuccess: response => + { + if (!response.success) + return (false, (string?)null); + + string? version = null; + if (!string.IsNullOrEmpty(response.data)) + { + try + { + using var doc = JsonDocument.Parse(response.data); + if (doc.RootElement.TryGetProperty("serverVersion", out var versionEl)) + version = versionEl.GetString(); + } + catch + { + // ignore parse failures + } + } + + return (true, version); + }, + onError: _ => (false, null)); + } + + private static CliResult BuildCheckResult( + bool installed, string? source, bool serverRunning, + string clientVersion, string? serverVersion, bool versionMatch) + { + var jsonData = BuildCheckJson(installed, source, serverRunning, clientVersion, serverVersion, versionMatch); + var formattedText = BuildCheckText(installed, source, serverRunning, clientVersion, serverVersion, versionMatch); + var message = installed ? "Package is installed" : "Package is not installed"; + + return CliResult.Ok(message, jsonData, formattedText); + } + + private static string BuildCheckJson( + bool installed, string? source, bool serverRunning, + string clientVersion, string? serverVersion, bool versionMatch) + { + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Indented = false }); + + writer.WriteStartObject(); + writer.WriteBoolean("installed", installed); + + if (source != null) + writer.WriteString("source", source); + else + writer.WriteNull("source"); + + writer.WriteBoolean("serverRunning", serverRunning); + writer.WriteString("clientVersion", clientVersion); + + if (serverVersion != null) + writer.WriteString("serverVersion", serverVersion); + else + writer.WriteNull("serverVersion"); + + writer.WriteBoolean("versionMatch", versionMatch); + writer.WriteEndObject(); + writer.Flush(); + + return System.Text.Encoding.UTF8.GetString(buffer.WrittenSpan); + } + + private static string BuildCheckText( + bool installed, string? source, bool serverRunning, + string clientVersion, string? serverVersion, bool versionMatch) + { + var sb = new System.Text.StringBuilder(); + + if (installed) + sb.AppendLine($"Package: installed ({source})"); + else + sb.AppendLine("Package: not installed"); + + sb.AppendLine($"Server: {(serverRunning ? "running" : "not running")}"); + sb.Append($"Client Version: {clientVersion}"); + + if (serverVersion != null) + { + sb.AppendLine(); + sb.Append($"Server Version: {serverVersion}"); + if (!versionMatch) + sb.Append(" (mismatch! run 'unicli install --update')"); + } + else if (serverRunning) + { + sb.AppendLine(); + sb.Append("Server Version: unknown"); + } + + return sb.ToString(); + } +} + +``` + +`src/UniCli.Client/Commands/Commands.Commands.cs`: + +```cs +using ConsoleAppFramework; +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Client; + +public partial class Commands +{ + /// + /// List all available commands from the Unity Editor server + /// + [Command("commands")] + public async Task ListCommands(bool json = false, bool noFocus = false) + { + var focus = UnityProcessActivator.ShouldFocus(noFocus); + var sendResult = await CommandExecutor.SendAsync("Commands.List", "", focusEditor: focus); + + var cliResult = sendResult.Match( + onSuccess: response => + { + if (!response.success) + return CliResult.Error(response.message); + + if (string.IsNullOrEmpty(response.data)) + return CliResult.Ok("No commands available.", "[]", "No commands available."); + + var listResponse = JsonSerializer.Deserialize( + response.data, ProtocolJsonContext.Default.CommandListResponse); + if (listResponse?.commands == null || listResponse.commands.Length == 0) + return CliResult.Ok("No commands available.", "[]", "No commands available."); + + var projectRoot = Environment.GetEnvironmentVariable("UNICLI_PROJECT") + ?? ProjectIdentifier.FindUnityProjectRoot(); + if (projectRoot != null) + CompletionCache.Save(projectRoot, listResponse.commands); + + var commands = listResponse.commands + .OrderBy(c => c.name) + .ToArray(); + + var jsonData = JsonSerializer.Serialize( + commands, ProtocolJsonContext.Default.CommandInfoArray); + var formattedText = FormatCommandsTable(commands); + + return CliResult.Ok( + $"{commands.Length} commands available", + jsonData, + formattedText); + }, + onError: CliResult.Error); + + return OutputWriter.Write(cliResult, json); + } + + private static string FormatCommandsTable(CommandInfo[] commands) + { + var maxNameLen = commands.Max(c => c.name.Length); + var sb = new System.Text.StringBuilder(); + + sb.AppendLine($"Available commands ({commands.Length}):"); + sb.AppendLine(); + + foreach (var cmd in commands) + { + var paddedName = cmd.name.PadRight(maxNameLen); + var modulePart = $" [{(string.IsNullOrEmpty(cmd.module) ? "Core" : cmd.module)}]"; + sb.AppendLine($" {paddedName} {cmd.description}{modulePart}"); + + if (cmd.requestFields.Length > 0) + { + foreach (var field in cmd.requestFields) + { + var defaultPart = string.IsNullOrEmpty(field.defaultValue) + ? "" + : $" = {field.defaultValue}"; + sb.AppendLine($" {"".PadRight(maxNameLen)} {field.name} ({field.type}{defaultPart})"); + } + } + } + + return sb.ToString().TrimEnd(); + } +} + +``` + +`src/UniCli.Client/Commands/Commands.Complete.cs`: + +```cs +using ConsoleAppFramework; +using System; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Client; + +public partial class Commands +{ + /// + /// Output completion candidates for shell completion + /// + public async Task Complete([Argument] string line = "") + { + var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + + // Strip program name if present (COMP_LINE / $words includes it) + if (parts.Length > 0 && !IsKnownSubcommand(parts[0]) && !IsSubcommandPrefix(parts[0])) + parts = parts.Skip(1).ToArray(); + + // "" or partial subcommand → subcommand candidates + if (parts.Length == 0 || (parts.Length == 1 && !line.EndsWith(' '))) + { + var prefix = parts.Length > 0 ? parts[0] : ""; + foreach (var sub in SubcommandNames) + { + if (sub.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + Console.WriteLine(sub); + } + return 0; + } + + var subcommand = parts[0]; + + if (subcommand == "exec") + { + var commands = await GetCommandsAsync(); + if (commands == null) return 0; + + // "exec " → command name candidates + if (parts.Length == 1 || (parts.Length == 2 && !line.EndsWith(' '))) + { + var prefix = parts.Length >= 2 ? parts[1] : ""; + foreach (var cmd in commands) + { + if (cmd.name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + Console.WriteLine(cmd.name); + } + return 0; + } + + // "exec SomeCommand --" → parameter candidates + if (parts.Length >= 2) + { + var cmdName = parts[1]; + var cmd = commands.FirstOrDefault( + c => c.name.Equals(cmdName, StringComparison.OrdinalIgnoreCase)); + + if (cmd?.requestFields != null) + { + var lastPart = parts[^1]; + var isTypingParam = lastPart.StartsWith("--") && !line.EndsWith(' '); + var prefix = isTypingParam ? lastPart.Substring(2) : ""; + + foreach (var field in cmd.requestFields) + { + if (field.name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + Console.WriteLine($"--{field.name}"); + } + } + return 0; + } + } + + // "completions " → shell candidates + if (subcommand == "completions") + { + var prefix = parts.Length >= 2 ? parts[1] : ""; + foreach (var shell in new[] { "bash", "zsh", "fish" }) + { + if (shell.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + Console.WriteLine(shell); + } + return 0; + } + + return 0; + } + + private static readonly string[] SubcommandNames = ["exec", "commands", "status", "check", "install", "complete", "completions"]; + + private static bool IsKnownSubcommand(string word) + { + return SubcommandNames.Any(s => s.Equals(word, StringComparison.OrdinalIgnoreCase)); + } + + private static bool IsSubcommandPrefix(string word) + { + return SubcommandNames.Any(s => s.StartsWith(word, StringComparison.OrdinalIgnoreCase)); + } + + private static async Task GetCommandsAsync() + { + var projectRoot = Environment.GetEnvironmentVariable("UNICLI_PROJECT") + ?? ProjectIdentifier.FindUnityProjectRoot(); + + if (projectRoot == null) + return null; + + var result = await CommandExecutor.SendAsync( + "Commands.List", "", timeoutMs: 500, maxRetries: 1); + var serverCommands = result.Match( + onSuccess: response => + { + if (!response.success || string.IsNullOrEmpty(response.data)) return null; + var listResponse = JsonSerializer.Deserialize( + response.data, ProtocolJsonContext.Default.CommandListResponse); + return listResponse?.commands; + }, + onError: _ => (CommandInfo[]?)null); + + if (serverCommands != null) + { + CompletionCache.Save(projectRoot, serverCommands); + return serverCommands; + } + + return CompletionCache.Load(projectRoot); + } +} + +``` + +`src/UniCli.Client/Commands/Commands.Completions.cs`: + +```cs +using ConsoleAppFramework; +using System; +using System.Threading.Tasks; + +namespace UniCli.Client; + +public partial class Commands +{ + /// + /// Output shell completion script for the specified shell + /// + public Task Completions([Argument] string shell = "bash") + { + var script = shell.ToLowerInvariant() switch + { + "bash" => BashScript, + "zsh" => ZshScript, + "fish" => FishScript, + _ => null + }; + + if (script == null) + { + Console.Error.WriteLine($"Unsupported shell: {shell}. Supported: bash, zsh, fish"); + return Task.FromResult(1); + } + + Console.Write(script); + return Task.FromResult(0); + } + + private const string BashScript = @"_unicli_completions() { + local cur_line=""${COMP_LINE}"" + local candidates + candidates=$(unicli complete ""$cur_line"" 2>/dev/null) + COMPREPLY=( $(compgen -W ""$candidates"" -- ""${COMP_WORDS[COMP_CWORD]}"") ) +} +complete -F _unicli_completions unicli +"; + + private const string ZshScript = @"_unicli_completions() { + local -a candidates + candidates=(""${(@f)$(unicli complete ""${words[*]}"" 2>/dev/null)}"") + compadd -a candidates +} +compdef _unicli_completions unicli +"; + + private const string FishScript = @"complete -c unicli -f -a '(unicli complete (commandline -cp) 2>/dev/null)' +"; +} + +``` + +`src/UniCli.Client/Commands/Commands.Exec.cs`: + +```cs +using ConsoleAppFramework; +using System.Threading.Tasks; + +namespace UniCli.Client; + +public partial class Commands +{ + /// + /// Execute a command on the Unity Editor server + /// + public async Task Exec( + [Argument] string command, + [Argument] string data = "", + int timeout = 0, + bool json = false, + bool noFocus = false) + { + var focus = UnityProcessActivator.ShouldFocus(noFocus); + var result = await CommandExecutor.ExecuteAsync(command, data, timeout, json, focusEditor: focus); + return OutputWriter.Write(result, json); + } +} + +``` + +`src/UniCli.Client/Commands/Commands.Install.cs`: + +```cs +using System; +using System.Buffers; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using ConsoleAppFramework; + +namespace UniCli.Client; + +public partial class Commands +{ + private const string DefaultGitUrl = + "https://github.com/yucchiy/UniCli.git?path=src/UniCli.Unity/Packages/com.yucchiy.unicli-server"; + + private static string GetVersionedGitUrl() => + $"{DefaultGitUrl}#v{VersionInfo.Version}"; + + /// + /// Install the UniCli server package into a Unity project + /// + [Command("install")] + public Task Install(string source = "", bool update = false, bool json = false) + { + var explicitPath = Environment.GetEnvironmentVariable("UNICLI_PROJECT"); + var projectRoot = explicitPath ?? ProjectIdentifier.FindUnityProjectRoot(); + + if (projectRoot == null) + { + var result = CliResult.Error("Unity project not found."); + return Task.FromResult(OutputWriter.Write(result, json)); + } + + var manifestPath = ManifestEditor.GetManifestPath(projectRoot); + + if (!File.Exists(manifestPath)) + { + var result = CliResult.Error($"manifest.json not found: {manifestPath}"); + return Task.FromResult(OutputWriter.Write(result, json)); + } + + var effectiveSource = string.IsNullOrEmpty(source) ? GetVersionedGitUrl() : source; + + if (update) + { + return Task.FromResult(HandleUpdate(manifestPath, effectiveSource, json)); + } + + bool added; + try + { + added = ManifestEditor.AddPackage(manifestPath, effectiveSource); + } + catch (Exception ex) + { + var result = CliResult.Error($"Failed to update manifest.json: {ex.Message}"); + return Task.FromResult(OutputWriter.Write(result, json)); + } + + var cliResult = BuildInstallResult(added, effectiveSource); + return Task.FromResult(OutputWriter.Write(cliResult, json)); + } + + private static int HandleUpdate(string manifestPath, string newSource, bool json) + { + try + { + var updated = ManifestEditor.UpdatePackageSource(manifestPath, newSource); + if (!updated) + { + var result = CliResult.Error( + $"{ManifestEditor.PackageName} is not installed. Run 'unicli install' first."); + return OutputWriter.Write(result, json); + } + + var jsonData = BuildInstallJson(true, newSource); + var cliResult = CliResult.Ok( + $"Updated {ManifestEditor.PackageName}", + jsonData, + $"Updated {ManifestEditor.PackageName} source to {newSource}"); + return OutputWriter.Write(cliResult, json); + } + catch (Exception ex) + { + var result = CliResult.Error($"Failed to update manifest.json: {ex.Message}"); + return OutputWriter.Write(result, json); + } + } + + private static CliResult BuildInstallResult(bool added, string source) + { + var jsonData = BuildInstallJson(added, source); + var message = added + ? $"Installed {ManifestEditor.PackageName}" + : $"{ManifestEditor.PackageName} is already installed"; + var formattedText = added + ? $"Installed {ManifestEditor.PackageName} from {source}" + : $"{ManifestEditor.PackageName} is already installed"; + + return CliResult.Ok(message, jsonData, formattedText); + } + + private static string BuildInstallJson(bool added, string source) + { + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Indented = false }); + + writer.WriteStartObject(); + writer.WriteBoolean("added", added); + writer.WriteString("package", ManifestEditor.PackageName); + writer.WriteString("source", source); + writer.WriteEndObject(); + writer.Flush(); + + return System.Text.Encoding.UTF8.GetString(buffer.WrittenSpan); + } +} + +``` + +`src/UniCli.Client/Commands/Commands.Status.cs`: + +```cs +using System; +using System.Buffers; +using System.Text.Json; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Client; + +public partial class Commands +{ + /// + /// Show the connection status of the Unity Editor server + /// + public async Task Status(bool json = false) + { + var explicitPath = Environment.GetEnvironmentVariable("UNICLI_PROJECT"); + var projectRoot = explicitPath ?? ProjectIdentifier.FindUnityProjectRoot(); + + if (projectRoot == null) + { + var result = BuildStatusResult(false, null, null, "Unity project not found", 0, default); + return OutputWriter.Write(result, json); + } + + var pipeName = ProjectIdentifier.GetPipeName(projectRoot); + + int commandCount; + { + using var client = new PipeClient(pipeName); + var connectResult = await client.ConnectAsync(timeoutMs: 2000); + + if (connectResult.IsError) + { + var pid = UnityProcessActivator.ReadPidFile(projectRoot); + var unityRunning = UnityProcessActivator.IsUnityRunning(projectRoot); + var error = unityRunning + ? "Server is not responding (Unity is running, may be reloading assemblies)" + : "Server is not running"; + var result = BuildStatusResult(false, projectRoot, pipeName, error, 0, + new ServerInfo(pid, null, null, 0)); + return OutputWriter.Write(result, json); + } + + var listRequest = new CommandRequest + { + command = "Commands.List", + data = "" + }; + + var sendResult = await client.SendCommandAsync(listRequest, timeoutMs: 2000); + + var listResult = sendResult.Match( + onSuccess: response => + { + if (!response.success || string.IsNullOrEmpty(response.data)) + return 0; + + var listResponse = JsonSerializer.Deserialize( + response.data, ProtocolJsonContext.Default.CommandListResponse); + return listResponse?.commands?.Length ?? 0; + }, + onError: _ => -1); + + if (listResult < 0) + { + var result = BuildStatusResult(false, projectRoot, pipeName, "Server is not running", 0, default); + return OutputWriter.Write(result, json); + } + + commandCount = listResult; + } + + var processId = UnityProcessActivator.ReadPidFile(projectRoot); + var serverInfo = await GetServerInfoAsync(pipeName); + var cliResult = BuildStatusResult(true, projectRoot, pipeName, null, commandCount, + serverInfo with { ProcessId = processId > 0 ? processId : serverInfo.ProcessId }); + return OutputWriter.Write(cliResult, json); + } + + private readonly record struct ServerInfo( + int ProcessId, + string? ServerId, + string? StartedAt, + double UptimeSeconds); + + private static async Task GetServerInfoAsync(string pipeName) + { + try + { + using var client = new PipeClient(pipeName); + var connectResult = await client.ConnectAsync(timeoutMs: 2000); + if (connectResult.IsError) + return default; + + var request = new CommandRequest + { + command = "Project.Inspect", + data = "", + format = "json" + }; + + var result = await client.SendCommandAsync(request, timeoutMs: 2000); + if (result.IsError) + return default; + + var response = result.Match( + onSuccess: r => r, + onError: _ => (CommandResponse?)null); + + if (response == null || !response.success || string.IsNullOrEmpty(response.data)) + return default; + + using var doc = JsonDocument.Parse(response.data); + var root = doc.RootElement; + + var processId = root.TryGetProperty("processId", out var pidEl) ? pidEl.GetInt32() : 0; + var serverId = root.TryGetProperty("serverId", out var sidEl) ? sidEl.GetString() : null; + var startedAt = root.TryGetProperty("startedAt", out var saEl) ? saEl.GetString() : null; + var uptime = root.TryGetProperty("uptimeSeconds", out var utEl) ? utEl.GetDouble() : 0; + + return new ServerInfo(processId, serverId, startedAt, uptime); + } + catch + { + return default; + } + } + + private static CliResult BuildStatusResult( + bool running, string? project, string? pipe, string? error, int commandCount, ServerInfo serverInfo) + { + var jsonData = BuildStatusJson(running, project, pipe, error, commandCount, serverInfo); + var formattedText = BuildStatusText(running, project, pipe, error, commandCount, serverInfo); + + return running + ? CliResult.Ok("Server is running", jsonData, formattedText) + : new CliResult + { + Success = false, + Message = error ?? "Server is not running", + JsonData = jsonData, + FormattedText = formattedText + }; + } + + private static string BuildStatusText( + bool running, string? project, string? pipe, string? error, int commandCount, ServerInfo serverInfo) + { + var sb = new System.Text.StringBuilder(); + + sb.AppendLine($"Project: {project ?? "not found"}"); + + if (pipe != null) + sb.AppendLine($"Pipe: {pipe}"); + + if (running) + { + sb.AppendLine($"Server: running ({commandCount} commands available)"); + if (serverInfo.ProcessId > 0) + sb.AppendLine($"PID: {serverInfo.ProcessId}"); + if (serverInfo.ServerId != null) + sb.AppendLine($"Server ID: {serverInfo.ServerId}"); + if (serverInfo.StartedAt != null) + sb.AppendLine($"Started: {serverInfo.StartedAt}"); + if (serverInfo.UptimeSeconds > 0) + sb.AppendLine($"Uptime: {serverInfo.UptimeSeconds:F1}s"); + + // Remove trailing newline + if (sb.Length >= Environment.NewLine.Length) + sb.Length -= Environment.NewLine.Length; + } + else + { + sb.Append($"Server: not running"); + } + + return sb.ToString(); + } + + private static string BuildStatusJson( + bool running, string? project, string? pipe, string? error, int commandCount, ServerInfo serverInfo) + { + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Indented = false }); + + writer.WriteStartObject(); + writer.WriteBoolean("running", running); + + if (project != null) + writer.WriteString("project", project); + else + writer.WriteNull("project"); + + if (pipe != null) + writer.WriteString("pipe", pipe); + else + writer.WriteNull("pipe"); + + if (running) + { + writer.WriteNumber("commandCount", commandCount); + if (serverInfo.ProcessId > 0) + writer.WriteNumber("processId", serverInfo.ProcessId); + if (serverInfo.ServerId != null) + writer.WriteString("serverId", serverInfo.ServerId); + if (serverInfo.StartedAt != null) + writer.WriteString("startedAt", serverInfo.StartedAt); + if (serverInfo.UptimeSeconds > 0) + writer.WriteNumber("uptimeSeconds", serverInfo.UptimeSeconds); + } + + if (error != null) + writer.WriteString("error", error); + + writer.WriteEndObject(); + writer.Flush(); + + return System.Text.Encoding.UTF8.GetString(buffer.WrittenSpan); + } +} + +``` + +`src/UniCli.Client/CompletionCache.cs`: + +```cs +using System; +using System.IO; +using System.Text.Json; +using UniCli.Protocol; + +namespace UniCli.Client; + +internal static class CompletionCache +{ + public static string GetCacheDir(string projectRoot) + { + var normalized = ProjectIdentifier.NormalizeToDataPath(projectRoot); + var hash = ProjectIdentifier.GetProjectHash(normalized); + + if (OperatingSystem.IsWindows()) + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "unicli", hash); + } + + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".cache", "unicli", hash); + } + + public static void Save(string projectRoot, CommandInfo[] commands) + { + var cacheDir = GetCacheDir(projectRoot); + Directory.CreateDirectory(cacheDir); + + var cachePath = Path.Combine(cacheDir, "commands.json"); + var json = JsonSerializer.Serialize(commands, ProtocolJsonContext.Default.CommandInfoArray); + File.WriteAllText(cachePath, json); + } + + public static CommandInfo[]? Load(string projectRoot) + { + var cacheDir = GetCacheDir(projectRoot); + var cachePath = Path.Combine(cacheDir, "commands.json"); + + if (!File.Exists(cachePath)) + return null; + + var json = File.ReadAllText(cachePath); + return JsonSerializer.Deserialize(json, ProtocolJsonContext.Default.CommandInfoArray); + } +} + +``` + +`src/UniCli.Client/FocusScope.cs`: + +```cs +using System; +using System.Threading.Tasks; + +namespace UniCli.Client; + +internal sealed class FocusScope : IAsyncDisposable +{ + public static readonly FocusScope Noop = new(0); + + private readonly long _savedState; + + private FocusScope(long savedState) + { + _savedState = savedState; + } + + public static async Task ActivateAsync(string projectRoot) + { + var savedState = await UnityProcessActivator.TryActivateAsync(projectRoot); + return new FocusScope(savedState); + } + + public async ValueTask DisposeAsync() + { + if (_savedState != 0) + await UnityProcessActivator.TryRestoreFocusAsync(_savedState); + } +} + +``` + +`src/UniCli.Client/IProcessActivator.cs`: + +```cs +using System.Threading.Tasks; + +namespace UniCli.Client; + +internal interface IProcessActivator +{ + Task ActivateProcessAsync(int pid); + Task RestoreFocusAsync(long savedState); +} + +``` + +`src/UniCli.Client/MacOSProcessActivator.cs`: + +```cs +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace UniCli.Client; + +internal sealed class MacOSProcessActivator : IProcessActivator +{ + private const int TimeoutMs = 2000; + + public async Task ActivateProcessAsync(int pid) + { + var previousPid = await GetFrontmostProcessIdAsync(); + await SetFrontmostAsync(pid); + return previousPid; + } + + public async Task RestoreFocusAsync(long savedState) + { + var pid = (int)savedState; + if (pid > 0) + await SetFrontmostAsync(pid); + } + + private async Task GetFrontmostProcessIdAsync() + { + var output = await RunOsascriptAsync( + "tell application \"System Events\" to get unix id of first process whose frontmost is true"); + + if (int.TryParse(output?.Trim(), out var pid)) + return pid; + + return 0; + } + + private async Task SetFrontmostAsync(int pid) + { + await RunOsascriptAsync( + $"tell application \"System Events\" to set frontmost of (first process whose unix id is {pid}) to true"); + } + + private static async Task RunOsascriptAsync(string script) + { + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = "osascript", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + process.StartInfo.ArgumentList.Add("-e"); + process.StartInfo.ArgumentList.Add(script); + + process.Start(); + + using var cts = new CancellationTokenSource(TimeoutMs); + try + { + var output = await process.StandardOutput.ReadToEndAsync(cts.Token); + await process.WaitForExitAsync(cts.Token); + return process.ExitCode == 0 ? output : null; + } + catch (OperationCanceledException) + { + process.Kill(); + return null; + } + } +} + +``` + +`src/UniCli.Client/ManifestEditor.cs`: + +```cs +using System; +using System.IO; +using System.Text.Json.Nodes; + +namespace UniCli.Client; + +internal static class ManifestEditor +{ + public const string PackageName = "com.yucchiy.unicli-server"; + + public static string GetManifestPath(string projectRoot) + { + var normalized = projectRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + if (Path.GetFileName(normalized) == "Assets") + normalized = Path.GetDirectoryName(normalized)!; + + return Path.Combine(normalized, "Packages", "manifest.json"); + } + + /// + /// Returns the source string for the package from manifest.json, or null if not installed. + /// + public static string? FindPackageSource(string manifestPath) + { + if (!File.Exists(manifestPath)) + return null; + + var text = File.ReadAllText(manifestPath); + var root = JsonNode.Parse(text); + var deps = root?["dependencies"]; + var value = deps?[PackageName]; + + return value?.GetValue(); + } + + /// + /// Adds the package to manifest.json dependencies. + /// Returns true if added, false if already present. + /// + public static bool AddPackage(string manifestPath, string source) + { + if (!File.Exists(manifestPath)) + throw new FileNotFoundException($"manifest.json not found: {manifestPath}"); + + var existing = FindPackageSource(manifestPath); + if (existing != null) + return false; + + var text = File.ReadAllText(manifestPath); + var insertIndex = FindDependenciesInsertPosition(text); + if (insertIndex < 0) + throw new InvalidOperationException("Could not find \"dependencies\" block in manifest.json"); + + var indent = DetectEntryIndent(text, insertIndex); + var newEntry = $",{Environment.NewLine}{indent}\"{PackageName}\": \"{source}\""; + + var result = text.Insert(insertIndex, newEntry); + File.WriteAllText(manifestPath, result); + return true; + } + + /// + /// Updates the source URL for the package in manifest.json. + /// Returns true if updated, false if the package is not installed. + /// + public static bool UpdatePackageSource(string manifestPath, string newSource) + { + if (!File.Exists(manifestPath)) + throw new FileNotFoundException($"manifest.json not found: {manifestPath}"); + + var oldSource = FindPackageSource(manifestPath); + if (oldSource == null) + return false; + + var text = File.ReadAllText(manifestPath); + var oldEntry = $"\"{PackageName}\": \"{oldSource}\""; + var newEntry = $"\"{PackageName}\": \"{newSource}\""; + + var index = text.IndexOf(oldEntry, StringComparison.Ordinal); + if (index < 0) + return false; + + var result = text.Remove(index, oldEntry.Length).Insert(index, newEntry); + File.WriteAllText(manifestPath, result); + return true; + } + + /// + /// Finds the position after the last entry value in the dependencies block + /// (right after the closing quote of the last value, before any trailing whitespace/closing brace). + /// + private static int FindDependenciesInsertPosition(string text) + { + var depsKey = "\"dependencies\""; + var depsStart = text.IndexOf(depsKey, StringComparison.Ordinal); + if (depsStart < 0) + return -1; + + var braceOpen = text.IndexOf('{', depsStart + depsKey.Length); + if (braceOpen < 0) + return -1; + + // Find the matching closing brace for the dependencies block + var depth = 1; + var braceClose = -1; + for (var i = braceOpen + 1; i < text.Length; i++) + { + if (text[i] == '{') depth++; + else if (text[i] == '}') + { + depth--; + if (depth == 0) + { + braceClose = i; + break; + } + } + } + + if (braceClose < 0) + return -1; + + // Find the last quote before the closing brace (end of the last entry value) + var lastQuote = text.LastIndexOf('"', braceClose - 1, braceClose - braceOpen); + if (lastQuote < 0) + return -1; + + return lastQuote + 1; + } + + /// + /// Detects the indentation of entries inside the dependencies block + /// by looking at the line preceding the insert position. + /// + private static string DetectEntryIndent(string text, int positionInBlock) + { + // Walk back from the position to find the start of the current line + var lineStart = positionInBlock; + while (lineStart > 0 && text[lineStart - 1] != '\n') + lineStart--; + + // Extract leading whitespace (skip '\r' from CRLF but don't include it in indent) + var indent = ""; + for (var i = lineStart; i < text.Length && (text[i] == ' ' || text[i] == '\t' || text[i] == '\r'); i++) + { + if (text[i] != '\r') + indent += text[i]; + } + + return indent.Length > 0 ? indent : " "; + } +} + +``` + +`src/UniCli.Client/NullProcessActivator.cs`: + +```cs +using System.Threading.Tasks; + +namespace UniCli.Client; + +internal sealed class NullProcessActivator : IProcessActivator +{ + public Task ActivateProcessAsync(int pid) => Task.FromResult(0L); + public Task RestoreFocusAsync(long savedState) => Task.CompletedTask; +} + +``` + +`src/UniCli.Client/OutputWriter.cs`: + +```cs +using System; +using System.Buffers; +using System.Text.Json; + +namespace UniCli.Client; + +internal static class OutputWriter +{ + public static int Write(CliResult result, bool json) + { + if (json) + WriteJson(result); + else + WriteHuman(result); + + return result.Success ? 0 : 1; + } + + private static void WriteJson(CliResult result) + { + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Indented = true }); + + writer.WriteStartObject(); + writer.WriteBoolean("success", result.Success); + writer.WriteString("message", result.Message); + + var data = result.JsonData ?? result.FormattedText; + if (data == null) + { + writer.WriteNull("data"); + } + else if (result.JsonData != null) + { + writer.WritePropertyName("data"); + try + { + using var doc = JsonDocument.Parse(result.JsonData); + doc.RootElement.WriteTo(writer); + } + catch + { + writer.WriteStringValue(result.JsonData); + } + } + else + { + writer.WriteString("data", result.FormattedText); + } + + writer.WriteEndObject(); + writer.Flush(); + + using var stdout = Console.OpenStandardOutput(); + stdout.Write(buffer.WrittenSpan); + stdout.Flush(); + Console.WriteLine(); + } + + private static void WriteHuman(CliResult result) + { + if (result.FormattedText != null) + { + if (result.Success) + Console.WriteLine(result.FormattedText); + else + Console.Error.WriteLine(result.FormattedText); + return; + } + + if (!result.Success) + Console.Error.WriteLine(result.Message); + + if (result.JsonData != null) + WritePrettyJson(result.JsonData); + else if (result.Success) + Console.WriteLine(result.Message); + } + + private static void WritePrettyJson(string json) + { + using var doc = JsonDocument.Parse(json); + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Indented = true }); + doc.RootElement.WriteTo(writer); + writer.Flush(); + + using var stdout = Console.OpenStandardOutput(); + stdout.Write(buffer.WrittenSpan); + stdout.Flush(); + Console.WriteLine(); + } +} + +``` + +`src/UniCli.Client/PipeClient.cs`: + +```cs +using System; +using System.IO; +using System.IO.Pipes; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Client +{ + public sealed class PipeClient : IDisposable + { + private readonly string _pipeName; + private NamedPipeClientStream? _pipeStream; + + public PipeClient(string pipeName) + { + if (string.IsNullOrWhiteSpace(pipeName)) + { + throw new ArgumentException("Pipe name cannot be null or empty", nameof(pipeName)); + } + + _pipeName = pipeName; + } + + /// + /// Connects to the server and performs protocol handshake + /// + public async Task> ConnectAsync( + int timeoutMs = 5000, + CancellationToken cancellationToken = default) + { + if (_pipeStream != null) + return Result.Error("Client is already connected"); + + try + { + _pipeStream = new NamedPipeClientStream( + ".", + _pipeName, + PipeDirection.InOut, + PipeOptions.Asynchronous); + + await _pipeStream.ConnectAsync(timeoutMs, cancellationToken); + + // Perform handshake within the remaining timeout + using var handshakeCts = timeoutMs > 0 + ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken) + : null; + handshakeCts?.CancelAfter(timeoutMs); + var handshakeToken = handshakeCts?.Token ?? cancellationToken; + + var handshakeResult = await PerformHandshakeAsync(handshakeToken); + if (!handshakeResult.IsSuccess) + { + _pipeStream.Dispose(); + _pipeStream = null; + return handshakeResult; + } + + return Result.Success(true); + } + catch (TimeoutException) + { + _pipeStream?.Dispose(); + _pipeStream = null; + return Result.Error("Connection timeout - Unity server may not be running"); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + _pipeStream?.Dispose(); + _pipeStream = null; + return Result.Error("Connection timeout - Unity server may not be running"); + } + catch (IOException ex) + { + _pipeStream?.Dispose(); + _pipeStream = null; + return Result.Error($"Connection failed: {ex.Message}"); + } + catch (Exception ex) + { + _pipeStream?.Dispose(); + _pipeStream = null; + return Result.Error($"Unexpected error: {ex.Message}"); + } + } + + private async Task> PerformHandshakeAsync(CancellationToken cancellationToken) + { + var sendBuffer = ProtocolConstants.BuildHandshakeBuffer(); + + await _pipeStream!.WriteAsync(sendBuffer.AsMemory(0, ProtocolConstants.HandshakeSize), cancellationToken); + await _pipeStream.FlushAsync(cancellationToken); + + var recvBuffer = new byte[ProtocolConstants.HandshakeSize]; + if (!await ReadExactAsync(recvBuffer, ProtocolConstants.HandshakeSize, cancellationToken)) + return Result.Error("Server closed connection during handshake"); + + if (!ProtocolConstants.ValidateMagicBytes(recvBuffer)) + return Result.Error("Handshake failed: not a UniCli server (invalid magic bytes)"); + + var serverVersion = BitConverter.ToUInt16(recvBuffer, 4); + if (serverVersion != ProtocolConstants.ProtocolVersion) + { + return Result.Error( + $"Protocol version mismatch (client: {ProtocolConstants.ProtocolVersion}, server: {serverVersion}). " + + "Please update unicli or the Unity server package."); + } + + return Result.Success(true); + } + + /// + /// Sends a command and receives the response. + /// Uses a two-phase timeout: the timeout applies only to sending the request + /// and receiving the ACK. After ACK, the response is awaited without timeout. + /// + public async Task> SendCommandAsync( + CommandRequest request, + int timeoutMs = 0, + CancellationToken cancellationToken = default) + { + if (_pipeStream == null || !_pipeStream.IsConnected) + return Result.Error("Not connected to server"); + + try + { + // --- Phase 1: Send request + read ACK (with timeout) --- + using var timeoutCts = timeoutMs > 0 + ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken) + : null; + timeoutCts?.CancelAfter(timeoutMs); + var phase1Token = timeoutCts?.Token ?? cancellationToken; + + var requestJson = JsonSerializer.Serialize(request, ProtocolJsonContext.Default.CommandRequest); + var requestBytes = Encoding.UTF8.GetBytes(requestJson); + var lengthBytes = BitConverter.GetBytes(requestBytes.Length); + + await _pipeStream.WriteAsync(lengthBytes.AsMemory(0, 4), phase1Token); + await _pipeStream.WriteAsync(requestBytes, phase1Token); + await _pipeStream.FlushAsync(phase1Token); + + // Read ACK byte (0x01) + var ackBuffer = new byte[1]; + var ackBytesRead = await _pipeStream.ReadAsync(ackBuffer.AsMemory(0, 1), phase1Token); + if (ackBytesRead == 0) + return Result.Error( + $"Server closed connection before sending ACK\n" + + $" Command: {request.command}\n" + + $" Pipe: {_pipeName}\n" + + $" The server may have crashed or does not support the ACK protocol."); + + if (ackBuffer[0] != 0x01) + return Result.Error( + $"Unexpected ACK byte from server: 0x{ackBuffer[0]:X2}\n" + + $" Command: {request.command}\n" + + $" Pipe: {_pipeName}\n" + + $" Expected: 0x01"); + + // --- Phase 2: Read response (no timeout, only external cancellation) --- + var responseLengthBytes = new byte[4]; + if (!await ReadExactAsync(responseLengthBytes, 4, cancellationToken)) + return Result.Error( + $"Server closed connection while reading response length header\n" + + $" Command: {request.command}\n" + + $" Pipe: {_pipeName}\n" + + $" The server may have crashed or restarted during command execution."); + + var responseLength = BitConverter.ToInt32(responseLengthBytes, 0); + if (responseLength <= 0 || responseLength > ProtocolConstants.MaxMessageSize) + return Result.Error( + $"Invalid response length: {responseLength} bytes\n" + + $" Command: {request.command}\n" + + $" Pipe: {_pipeName}\n" + + $" Expected: 1 to {ProtocolConstants.MaxMessageSize} bytes\n" + + $" Raw header: [{responseLengthBytes[0]:X2} {responseLengthBytes[1]:X2} {responseLengthBytes[2]:X2} {responseLengthBytes[3]:X2}]"); + + var responseBytes = new byte[responseLength]; + if (!await ReadExactAsync(responseBytes, responseLength, cancellationToken)) + return Result.Error( + $"Server closed connection while reading response body\n" + + $" Command: {request.command}\n" + + $" Pipe: {_pipeName}"); + + var responseJson = Encoding.UTF8.GetString(responseBytes); + var response = JsonSerializer.Deserialize(responseJson, ProtocolJsonContext.Default.CommandResponse); + + if (response == null) + return Result.Error( + $"Failed to deserialize response JSON\n" + + $" Command: {request.command}\n" + + $" Response size: {responseLength} bytes"); + + return Result.Success(response); + } + catch (OperationCanceledException) when (timeoutMs > 0 && !cancellationToken.IsCancellationRequested) + { + return Result.Error( + $"Command '{request.command}' timed out after {timeoutMs}ms while waiting for server to accept the request.\n" + + $" Pipe: {_pipeName}\n" + + $" Use --timeout to increase the limit."); + } + catch (IOException ex) + { + return Result.Error( + $"Communication error during '{request.command}': {ex.Message}\n" + + $" Pipe: {_pipeName}"); + } + catch (Exception ex) + { + return Result.Error( + $"Unexpected error during '{request.command}': {ex.Message}\n" + + $" Pipe: {_pipeName}"); + } + } + + private async Task ReadExactAsync(byte[] buffer, int count, CancellationToken cancellationToken) + { + var totalRead = 0; + while (totalRead < count) + { + var bytesRead = await _pipeStream!.ReadAsync( + buffer.AsMemory(totalRead, count - totalRead), cancellationToken); + if (bytesRead == 0) + return false; + totalRead += bytesRead; + } + return true; + } + + public void Dispose() + { + _pipeStream?.Dispose(); + } + } +} + + +``` + +`src/UniCli.Client/Program.cs`: + +```cs +using ConsoleAppFramework; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace UniCli.Client; + +internal sealed class Program +{ + static async Task Main(string[] args) + { + if (args is ["exec", var commandName, .. var remaining]) + { + if (remaining.Contains("--help")) + { + Environment.ExitCode = await CommandExecutor.PrintCommandHelpAsync(commandName); + return; + } + + var timeoutMs = ExtractTimeout(ref remaining); + var jsonFlag = ExtractFlag(ref remaining, "--json"); + var noFocusFlag = ExtractFlag(ref remaining, "--no-focus"); + var focusEditor = UnityProcessActivator.ShouldFocus(noFocusFlag); + + CliResult result; + if (remaining.Any(a => a.StartsWith("--"))) + { + result = await CommandExecutor.ExecuteWithKeyValueAsync( + commandName, remaining, timeoutMs, jsonFlag, focusEditor); + } + else + { + var data = remaining.Length > 0 ? remaining[0] : ""; + result = await CommandExecutor.ExecuteAsync( + commandName, data, timeoutMs, jsonFlag, focusEditor); + } + + Environment.ExitCode = OutputWriter.Write(result, jsonFlag); + return; + } + + if (args is ["eval", .. var evalArgs]) + { + Environment.ExitCode = await RunEvalAsync(evalArgs); + return; + } + + var app = ConsoleApp.Create(); + app.Add(); + await app.RunAsync(args); + } + + private static async Task RunEvalAsync(string[] args) + { + var timeoutMs = ExtractTimeout(ref args); + var jsonFlag = ExtractFlag(ref args, "--json"); + var noFocusFlag = ExtractFlag(ref args, "--no-focus"); + var declarations = ExtractValue(ref args, "--declarations"); + + string code; + if (args.Length > 0 && !args[0].StartsWith("--")) + { + code = args[0]; + } + else + { + Console.Error.WriteLine("Error: code argument is required"); + Console.Error.WriteLine("Usage: unicli eval '' [--json] [--declarations '']"); + return 1; + } + + var buffer = new ArrayBufferWriter(); + using (var writer = new Utf8JsonWriter(buffer)) + { + writer.WriteStartObject(); + writer.WriteString("code", code); + if (declarations != null) + writer.WriteString("declarations", declarations); + writer.WriteEndObject(); + } + + var requestJson = Encoding.UTF8.GetString(buffer.WrittenSpan); + var focusEditor = UnityProcessActivator.ShouldFocus(noFocusFlag); + var result = await CommandExecutor.ExecuteAsync("Eval", requestJson, timeoutMs, jsonFlag, focusEditor); + return OutputWriter.Write(result, jsonFlag); + } + + private static int ExtractTimeout(ref string[] args) + { + var timeoutMs = 0; + var remaining = new List(); + + for (var i = 0; i < args.Length; i++) + { + if (args[i] == "--timeout" && i + 1 < args.Length && int.TryParse(args[i + 1], out var value)) + { + timeoutMs = value; + i++; + } + else + { + remaining.Add(args[i]); + } + } + + args = remaining.ToArray(); + return timeoutMs; + } + + private static bool ExtractFlag(ref string[] args, string flag) + { + var found = args.Contains(flag); + if (found) + args = args.Where(a => a != flag).ToArray(); + return found; + } + + private static string ExtractValue(ref string[] args, string flag) + { + string? value = null; + var remaining = new List(); + + for (var i = 0; i < args.Length; i++) + { + if (args[i] == flag && i + 1 < args.Length) + { + value = args[i + 1]; + i++; + } + else + { + remaining.Add(args[i]); + } + } + + args = remaining.ToArray(); + return value!; + } +} + +``` + +`src/UniCli.Client/ProjectIdentifier.cs`: + +```cs +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; + +namespace UniCli.Client +{ + public static class ProjectIdentifier + { + public static string GetProjectHash(string projectPath) + { + using var sha256 = SHA256.Create(); + + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(projectPath)); + var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + + return hash.Substring(0, 8); + } + + /// + /// Generates a project-specific pipe name. + /// Normalizes the path to match Unity's Application.dataPath format + /// (absolute path to the Assets directory). + /// + public static string GetPipeName(string projectPath) + { + var normalized = NormalizeToDataPath(projectPath); + return $"unicli-{GetProjectHash(normalized)}"; + } + + /// + /// Normalizes a project path to match Unity's Application.dataPath + /// (absolute path ending with /Assets). + /// + internal static string NormalizeToDataPath(string projectPath) + { + var fullPath = Path.GetFullPath(projectPath); + var trimmed = fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + if (Path.GetFileName(trimmed) != "Assets") + { + fullPath = Path.Combine(trimmed, "Assets"); + } + + return NormalizePathForHash(fullPath); + } + + internal static string NormalizePathForHash(string path) + { + path = path.Replace('\\', '/'); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + path = path.ToLowerInvariant(); + + return path; + } + + /// + /// Detects the Unity project root from the current directory + /// + public static string? FindUnityProjectRoot(string? startPath = null) + { + var currentDir = startPath ?? Directory.GetCurrentDirectory(); + + // Traverse up to 10 parent directories + for (int i = 0; i < 10; i++) + { + var assetsDir = Path.Combine(currentDir, "Assets"); + if (Directory.Exists(assetsDir)) + { + // Unity's dataPath points to the Assets directory + return assetsDir; + } + + var parentDir = Directory.GetParent(currentDir); + if (parentDir == null) + { + break; + } + + currentDir = parentDir.FullName; + } + + return null; + } + } +} + +``` + +`src/UniCli.Client/Protocol/CommandInfo.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandInfo + { + public string name; + public string description; + public bool builtIn; + public string module; + public CommandFieldInfo[] requestFields; + public CommandFieldInfo[] responseFields; + public CommandTypeDetail[] requestTypeDetails; + public CommandTypeDetail[] responseTypeDetails; + } + + [Serializable] + public class CommandFieldInfo + { + public string name; + public string type; + public string typeId; + public string defaultValue; + } + + [Serializable] + public class CommandTypeDetail + { + public string typeName; + public string typeId; + public CommandFieldInfo[] fields; + } + + [Serializable] + public class CommandListResponse + { + public CommandInfo[] commands; + } +} + +``` + +`src/UniCli.Client/Protocol/CommandRequest.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandRequest + { + public string command; + public string data; + public string format; // "json" or "text"; empty/null treated as "json" + public string cwd; // client's working directory for resolving relative paths + public string clientVersion; // client application version for compatibility check + } +} + +``` + +`src/UniCli.Client/Protocol/CommandResponse.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandResponse + { + public bool success; + public string message; + public string data; // Structured data (JSON string) or formatted text + public string format; // "json" or "text"; empty/null treated as "json" + public string serverVersion; // server package version (always set) + public string versionWarning; // warning message when client/server versions differ + } +} + +``` + +`src/UniCli.Client/Protocol/ProtocolConstants.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + public static class ProtocolConstants + { + public static readonly byte[] MagicBytes = { 0x55, 0x43, 0x4C, 0x49 }; // "UCLI" + public const ushort ProtocolVersion = 1; + public const int HandshakeSize = 6; // 4 (magic) + 2 (version) + public const int MaxMessageSize = 1024 * 1024; // 1 MB + + public static bool ValidateMagicBytes(byte[] buffer) + { + return buffer.Length >= 4 + && buffer[0] == MagicBytes[0] + && buffer[1] == MagicBytes[1] + && buffer[2] == MagicBytes[2] + && buffer[3] == MagicBytes[3]; + } + + public static byte[] BuildHandshakeBuffer() + { + var buffer = new byte[HandshakeSize]; + Array.Copy(MagicBytes, 0, buffer, 0, 4); + BitConverter.GetBytes(ProtocolVersion).CopyTo(buffer, 4); + return buffer; + } + } +} + +``` + +`src/UniCli.Client/Protocol/Result.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + /// + /// Represents either a success or failure result + /// + /// Type of the success value + /// Type of the error value + public readonly struct Result + { + private readonly bool _isSuccess; + private readonly TSuccess? _successValue; + private readonly TError? _errorValue; + + private Result(bool isSuccess, TSuccess? successValue, TError? errorValue) + { + _isSuccess = isSuccess; + _successValue = successValue; + _errorValue = errorValue; + } + + /// + /// Whether the result is a success + /// + public bool IsSuccess => _isSuccess; + + /// + /// Whether the result is an error + /// + public bool IsError => !_isSuccess; + + /// + /// The success value (throws if IsSuccess is false) + /// + public TSuccess SuccessValue + { + get + { + if (!_isSuccess) + throw new InvalidOperationException("Cannot access SuccessValue when result is an error"); + return _successValue!; + } + } + + /// + /// The error value (throws if IsSuccess is true) + /// + public TError ErrorValue + { + get + { + if (_isSuccess) + throw new InvalidOperationException("Cannot access ErrorValue when result is a success"); + return _errorValue!; + } + } + + /// + /// Creates a success result + /// + public static Result Success(TSuccess value) + { + return new Result(true, value, default); + } + + /// + /// Creates an error result + /// + public static Result Error(TError error) + { + return new Result(false, default, error); + } + + /// + /// Pattern matches on success or error, returning a value + /// + public TResult Match( + Func onSuccess, + Func onError) + { + if (onSuccess == null) + throw new ArgumentNullException(nameof(onSuccess)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + return _isSuccess ? onSuccess(_successValue!) : onError(_errorValue!); + } + + /// + /// Pattern matches on success or error, executing an action + /// + public void Match( + Action onSuccess, + Action onError) + { + if (onSuccess == null) + throw new ArgumentNullException(nameof(onSuccess)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + if (_isSuccess) + onSuccess(_successValue!); + else + onError(_errorValue!); + } + } +} + +``` + +`src/UniCli.Client/Protocol/Unit.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + /// + /// Represents a type with no value + /// + [Serializable] + public struct Unit + { + public static readonly Unit Value = new Unit(); + } +} + +``` + +`src/UniCli.Client/ProtocolJsonContext.cs`: + +```cs +using System.Text.Json; +using System.Text.Json.Serialization; +using UniCli.Protocol; + +namespace UniCli.Client; + +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, + IncludeFields = true, + WriteIndented = true)] +[JsonSerializable(typeof(CommandRequest))] +[JsonSerializable(typeof(CommandResponse))] +[JsonSerializable(typeof(CommandListResponse))] +[JsonSerializable(typeof(CommandInfo[]))] +[JsonSerializable(typeof(JsonElement))] +internal partial class ProtocolJsonContext : JsonSerializerContext +{ +} + +``` + +`src/UniCli.Client/UniCli.Client.csproj`: + +```csproj + + + + Exe + unicli + net9.0 + latest + enable + true + true + LatestMajor + 1.2.0 + + + + + + + + + + + + + + +``` + +`src/UniCli.Client/UnityLauncher.cs`: + +```cs +using System; +using System.Diagnostics; +using System.IO; +using UniCli.Protocol; + +namespace UniCli.Client; + +internal static class UnityLauncher +{ + public static string? FindUnityEditorPath(string projectRoot) + { + var version = ReadEditorVersion(projectRoot); + if (version == null) + return null; + + string editorPath; + if (OperatingSystem.IsMacOS()) + { + editorPath = $"/Applications/Unity/Hub/Editor/{version}/Unity.app/Contents/MacOS/Unity"; + } + else if (OperatingSystem.IsWindows()) + { + editorPath = $@"C:\Program Files\Unity\Hub\Editor\{version}\Editor\Unity.exe"; + } + else if (OperatingSystem.IsLinux()) + { + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + editorPath = Path.Combine(home, "Unity", "Hub", "Editor", version, "Editor", "Unity"); + } + else + { + return null; + } + + return File.Exists(editorPath) ? editorPath : null; + } + + public static Result Launch(string projectRoot) + { + var editorPath = FindUnityEditorPath(projectRoot); + if (editorPath == null) + { + var version = ReadEditorVersion(projectRoot); + var versionMsg = version != null + ? $"Unity {version} is not installed at the expected Hub location." + : "Could not read editor version from ProjectSettings/ProjectVersion.txt."; + return Result.Error(versionMsg); + } + + var normalizedRoot = NormalizeProjectRoot(projectRoot); + var startInfo = CreateStartInfo(editorPath, normalizedRoot, OperatingSystem.IsMacOS()); + + using var process = Process.Start(startInfo); + if (process == null) + return Result.Error("Failed to start Unity Editor process."); + + return Result.Success(true); + } + + internal static ProcessStartInfo CreateStartInfo(string editorPath, string projectRoot, bool isMacOS) + { + if (isMacOS) + { + var appBundlePath = TryGetMacOSAppBundlePath(editorPath); + if (appBundlePath != null) + return CreateMacOSOpenStartInfo(appBundlePath, projectRoot); + } + + return CreateDirectStartInfo(editorPath, projectRoot); + } + + internal static string? ReadEditorVersion(string projectRoot) + { + var normalizedRoot = NormalizeProjectRoot(projectRoot); + var versionFilePath = Path.Combine(normalizedRoot, "ProjectSettings", "ProjectVersion.txt"); + + if (!File.Exists(versionFilePath)) + return null; + + try + { + foreach (var line in File.ReadLines(versionFilePath)) + { + if (!line.StartsWith("m_EditorVersion:")) + continue; + + var colonIndex = line.IndexOf(':'); + if (colonIndex < 0) + continue; + + return line.Substring(colonIndex + 1).Trim(); + } + } + catch + { + // Best-effort: silently ignore read errors + } + + return null; + } + + internal static string? TryGetMacOSAppBundlePath(string editorPath) + { + var macOsDirectory = Path.GetDirectoryName(editorPath); + var contentsDirectory = macOsDirectory != null + ? Path.GetDirectoryName(macOsDirectory) + : null; + var appBundlePath = contentsDirectory != null + ? Path.GetDirectoryName(contentsDirectory) + : null; + + if (string.IsNullOrEmpty(appBundlePath)) + return null; + + return string.Equals(Path.GetExtension(appBundlePath), ".app", StringComparison.OrdinalIgnoreCase) + ? appBundlePath + : null; + } + + internal static string NormalizeProjectRoot(string projectRoot) + { + var trimmed = projectRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + var normalized = Path.GetFileName(trimmed) == "Assets" + ? Path.GetDirectoryName(trimmed) ?? trimmed + : trimmed; + return Path.GetFullPath(normalized); + } + + private static ProcessStartInfo CreateMacOSOpenStartInfo(string appBundlePath, string projectRoot) + { + var startInfo = new ProcessStartInfo + { + FileName = "open", + UseShellExecute = false, + }; + startInfo.ArgumentList.Add("-a"); + startInfo.ArgumentList.Add(appBundlePath); + startInfo.ArgumentList.Add("--args"); + startInfo.ArgumentList.Add("-projectPath"); + startInfo.ArgumentList.Add(projectRoot); + return startInfo; + } + + private static ProcessStartInfo CreateDirectStartInfo(string editorPath, string projectRoot) + { + var startInfo = new ProcessStartInfo + { + FileName = editorPath, + UseShellExecute = true, + }; + startInfo.ArgumentList.Add("-projectPath"); + startInfo.ArgumentList.Add(projectRoot); + return startInfo; + } +} + +``` + +`src/UniCli.Client/UnityProcessActivator.cs`: + +```cs +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace UniCli.Client; + +internal static class UnityProcessActivator +{ + private static readonly IProcessActivator Activator = CreateActivator(); + + private static IProcessActivator CreateActivator() + { + if (OperatingSystem.IsMacOS()) return new MacOSProcessActivator(); + if (OperatingSystem.IsWindows()) return new WindowsProcessActivator(); + return new NullProcessActivator(); + } + + public static bool ShouldFocus(bool noFocusFlag) + { + if (noFocusFlag) + return false; + + var env = Environment.GetEnvironmentVariable("UNICLI_FOCUS"); + if (env != null) + return env is "1" or "true"; + + return true; + } + + public static int ReadPidFile(string projectPath) + { + try + { + var root = StripAssetsSuffix(projectPath); + var pidFilePath = Path.Combine(root, "Library", "UniCli", "server.pid"); + if (!File.Exists(pidFilePath)) + return 0; + + var content = File.ReadAllText(pidFilePath).Trim(); + return int.TryParse(content, out var pid) ? pid : 0; + } + catch + { + return 0; + } + } + + private static string StripAssetsSuffix(string path) + { + var trimmed = path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + if (Path.GetFileName(trimmed) == "Assets") + return Path.GetDirectoryName(trimmed) ?? trimmed; + return path; + } + + public static bool IsUnityRunning(string projectRoot) + { + var pid = ReadPidFile(projectRoot); + if (pid <= 0) + return false; + + try + { + using var process = Process.GetProcessById(pid); + return !process.HasExited; + } + catch (ArgumentException) + { + return false; + } + } + + public static async Task TryActivateAsync(string projectRoot) + { + try + { + var unityPid = ReadPidFile(projectRoot); + if (unityPid <= 0) + return 0; + + return await Activator.ActivateProcessAsync(unityPid); + } + catch + { + return 0; + } + } + + public static async Task TryRestoreFocusAsync(long savedState) + { + if (savedState == 0) + return; + + try + { + await Activator.RestoreFocusAsync(savedState); + } + catch + { + // Best-effort: silently ignore all errors + } + } +} + +``` + +`src/UniCli.Client/VersionInfo.cs`: + +```cs +using System.Reflection; + +namespace UniCli.Client; + +internal static class VersionInfo +{ + public static string Version => typeof(VersionInfo).Assembly + .GetCustomAttribute() + ?.InformationalVersion?.Split('+')[0] ?? "unknown"; +} + +``` + +`src/UniCli.Client/WindowsProcessActivator.cs`: + +```cs +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace UniCli.Client; + +internal sealed class WindowsProcessActivator : IProcessActivator +{ + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + public Task ActivateProcessAsync(int pid) + { + var savedHwnd = GetForegroundWindow(); + + try + { + using var process = Process.GetProcessById(pid); + if (process.MainWindowHandle != IntPtr.Zero) + SetForegroundWindow(process.MainWindowHandle); + } + catch + { + // Best-effort: process may have exited + } + + return Task.FromResult(savedHwnd.ToInt64()); + } + + public Task RestoreFocusAsync(long savedState) + { + var hwnd = new IntPtr(savedState); + if (hwnd != IntPtr.Zero) + SetForegroundWindow(hwnd); + + return Task.CompletedTask; + } +} + +``` + +`src/UniCli.Protocol/CommandInfo.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandInfo + { + public string name; + public string description; + public bool builtIn; + public string module; + public CommandFieldInfo[] requestFields; + public CommandFieldInfo[] responseFields; + public CommandTypeDetail[] requestTypeDetails; + public CommandTypeDetail[] responseTypeDetails; + } + + [Serializable] + public class CommandFieldInfo + { + public string name; + public string type; + public string typeId; + public string defaultValue; + } + + [Serializable] + public class CommandTypeDetail + { + public string typeName; + public string typeId; + public CommandFieldInfo[] fields; + } + + [Serializable] + public class CommandListResponse + { + public CommandInfo[] commands; + } +} + +``` + +`src/UniCli.Protocol/CommandRequest.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandRequest + { + public string command; + public string data; + public string format; // "json" or "text"; empty/null treated as "json" + public string cwd; // client's working directory for resolving relative paths + public string clientVersion; // client application version for compatibility check + } +} + +``` + +`src/UniCli.Protocol/CommandResponse.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandResponse + { + public bool success; + public string message; + public string data; // Structured data (JSON string) or formatted text + public string format; // "json" or "text"; empty/null treated as "json" + public string serverVersion; // server package version (always set) + public string versionWarning; // warning message when client/server versions differ + } +} + +``` + +`src/UniCli.Protocol/ProtocolConstants.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + public static class ProtocolConstants + { + public static readonly byte[] MagicBytes = { 0x55, 0x43, 0x4C, 0x49 }; // "UCLI" + public const ushort ProtocolVersion = 1; + public const int HandshakeSize = 6; // 4 (magic) + 2 (version) + public const int MaxMessageSize = 1024 * 1024; // 1 MB + + public static bool ValidateMagicBytes(byte[] buffer) + { + return buffer.Length >= 4 + && buffer[0] == MagicBytes[0] + && buffer[1] == MagicBytes[1] + && buffer[2] == MagicBytes[2] + && buffer[3] == MagicBytes[3]; + } + + public static byte[] BuildHandshakeBuffer() + { + var buffer = new byte[HandshakeSize]; + Array.Copy(MagicBytes, 0, buffer, 0, 4); + BitConverter.GetBytes(ProtocolVersion).CopyTo(buffer, 4); + return buffer; + } + } +} + +``` + +`src/UniCli.Protocol/Result.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + /// + /// Represents either a success or failure result + /// + /// Type of the success value + /// Type of the error value + public readonly struct Result + { + private readonly bool _isSuccess; + private readonly TSuccess? _successValue; + private readonly TError? _errorValue; + + private Result(bool isSuccess, TSuccess? successValue, TError? errorValue) + { + _isSuccess = isSuccess; + _successValue = successValue; + _errorValue = errorValue; + } + + /// + /// Whether the result is a success + /// + public bool IsSuccess => _isSuccess; + + /// + /// Whether the result is an error + /// + public bool IsError => !_isSuccess; + + /// + /// The success value (throws if IsSuccess is false) + /// + public TSuccess SuccessValue + { + get + { + if (!_isSuccess) + throw new InvalidOperationException("Cannot access SuccessValue when result is an error"); + return _successValue!; + } + } + + /// + /// The error value (throws if IsSuccess is true) + /// + public TError ErrorValue + { + get + { + if (_isSuccess) + throw new InvalidOperationException("Cannot access ErrorValue when result is a success"); + return _errorValue!; + } + } + + /// + /// Creates a success result + /// + public static Result Success(TSuccess value) + { + return new Result(true, value, default); + } + + /// + /// Creates an error result + /// + public static Result Error(TError error) + { + return new Result(false, default, error); + } + + /// + /// Pattern matches on success or error, returning a value + /// + public TResult Match( + Func onSuccess, + Func onError) + { + if (onSuccess == null) + throw new ArgumentNullException(nameof(onSuccess)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + return _isSuccess ? onSuccess(_successValue!) : onError(_errorValue!); + } + + /// + /// Pattern matches on success or error, executing an action + /// + public void Match( + Action onSuccess, + Action onError) + { + if (onSuccess == null) + throw new ArgumentNullException(nameof(onSuccess)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + if (_isSuccess) + onSuccess(_successValue!); + else + onError(_errorValue!); + } + } +} + +``` + +`src/UniCli.Protocol/UniCli.Protocol.csproj`: + +```csproj + + + + net9.0;netstandard2.1 + latest + enable + true + + + + + $(MSBuildProjectDirectory)/../UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol + $(MSBuildProjectDirectory)/../UniCli.Client/Protocol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +`src/UniCli.Protocol/Unit.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + /// + /// Represents a type with no value + /// + [Serializable] + public struct Unit + { + public static readonly Unit Value = new Unit(); + } +} + +``` + +`src/UniCli.SourceGenerator/Analysis/InstanceTypeAnalyzer.cs`: + +```cs +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace UniCli.SourceGenerator.Analysis +{ + internal static class InstanceTypeAnalyzer + { + public static InstanceTypeInfo Analyze( + INamedTypeSymbol type, + string commandPrefix, + SettingsCommandGenerator.ResolveMode resolveMode) + { + var properties = CollectProperties(type, commandPrefix); + + return new InstanceTypeInfo( + type, + commandPrefix, + resolveMode, + properties); + } + + private static ImmutableArray CollectProperties( + INamedTypeSymbol type, string commandPrefix) + { + var result = new List(); + + foreach (var member in type.GetMembers()) + { + if (member is not IPropertySymbol property) + continue; + + if (!IsValidInstanceProperty(property)) + continue; + + if (SettingsTypeAnalyzer.HasObsolete(property)) + continue; + + if (!TypeSerializabilityChecker.IsSerializableType(property.Type)) + continue; + + result.Add(new InstancePropertyInfo( + property, + commandPrefix, + TypeSerializabilityChecker.IsEnumType(property.Type))); + } + + return result.ToImmutableArray(); + } + + private static bool IsValidInstanceProperty(IPropertySymbol property) + { + if (property.IsStatic) + return false; + + if (property.DeclaredAccessibility != Accessibility.Public) + return false; + + if (property.IsIndexer) + return false; + + if (property.GetMethod == null) + return false; + + return true; + } + } +} + +``` + +`src/UniCli.SourceGenerator/Analysis/InstanceTypeInfo.cs`: + +```cs +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace UniCli.SourceGenerator.Analysis +{ + internal sealed class InstanceTypeInfo + { + public INamedTypeSymbol Type { get; } + public string CommandPrefix { get; } + public SettingsCommandGenerator.ResolveMode ResolveMode { get; } + public ImmutableArray Properties { get; } + + public InstanceTypeInfo( + INamedTypeSymbol type, + string commandPrefix, + SettingsCommandGenerator.ResolveMode resolveMode, + ImmutableArray properties) + { + Type = type; + CommandPrefix = commandPrefix; + ResolveMode = resolveMode; + Properties = properties; + } + } + + internal sealed class InstancePropertyInfo + { + public IPropertySymbol Symbol { get; } + public string CommandPrefix { get; } + public bool IsEnum { get; } + public bool HasSetter => Symbol.SetMethod != null; + + public string PascalCaseName + { + get + { + var name = Symbol.Name; + if (name.Length == 0) return name; + return char.ToUpperInvariant(name[0]) + name.Substring(1); + } + } + + public InstancePropertyInfo( + IPropertySymbol symbol, + string commandPrefix, + bool isEnum) + { + Symbol = symbol; + CommandPrefix = commandPrefix; + IsEnum = isEnum; + } + } +} + +``` + +`src/UniCli.SourceGenerator/Analysis/SettingsTypeAnalyzer.cs`: + +```cs +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace UniCli.SourceGenerator.Analysis +{ + internal sealed class SettingsTypeAnalyzer + { + public static SettingsTypeInfo Analyze(INamedTypeSymbol settingsType, string commandPrefix) + { + var properties = CollectProperties(settingsType, commandPrefix); + var nestedTypes = CollectNestedSettingsTypes(settingsType, commandPrefix); + + return new SettingsTypeInfo( + settingsType, + commandPrefix, + properties, + nestedTypes); + } + + private static ImmutableArray CollectProperties( + INamedTypeSymbol type, string commandPrefix) + { + var result = new List(); + + foreach (var member in type.GetMembers()) + { + if (member is not IPropertySymbol property) + continue; + + if (!IsValidProperty(property)) + continue; + + // Skip all Obsolete properties + if (HasObsolete(property)) + continue; + + var propertyType = property.Type; + + if (!TypeSerializabilityChecker.IsSerializableType(propertyType)) + continue; + + result.Add(new SettingsPropertyInfo( + property, + commandPrefix, + TypeSerializabilityChecker.IsEnumType(propertyType))); + } + + return result.ToImmutableArray(); + } + + private static ImmutableArray CollectNestedSettingsTypes( + INamedTypeSymbol parentType, string parentPrefix) + { + var result = new List(); + + var allTypeMembers = parentType.GetTypeMembers(); + + foreach (var nestedType in allTypeMembers) + { + // Accept public nested types (both static classes and regular classes with static members) + if (nestedType.DeclaredAccessibility != Accessibility.Public && + nestedType.DeclaredAccessibility != Accessibility.NotApplicable) + continue; + + // Skip enum types (they're not settings containers) + if (nestedType.TypeKind == TypeKind.Enum) + continue; + + // Skip Obsolete nested types + if (HasObsolete(nestedType)) + continue; + + // Check if this nested type has any serializable properties + var nestedPrefix = $"{parentPrefix}.{nestedType.Name}"; + var properties = CollectProperties(nestedType, nestedPrefix); + + if (properties.Length == 0) + continue; + + result.Add(new NestedSettingsTypeInfo( + nestedType, + nestedPrefix, + parentPrefix, + properties)); + } + + return result.ToImmutableArray(); + } + + private static bool IsValidProperty(IPropertySymbol property) + { + if (!property.IsStatic) + return false; + + if (property.DeclaredAccessibility != Accessibility.Public) + return false; + + if (property.IsIndexer) + return false; + + if (property.GetMethod == null) + return false; + + return true; + } + + internal static bool HasObsolete(ISymbol symbol) + { + return symbol.GetAttributes().Any(a => + a.AttributeClass != null && + TypeSerializabilityChecker.GetFullMetadataName(a.AttributeClass) == + "System.ObsoleteAttribute"); + } + + } + + internal sealed class SettingsTypeInfo + { + public INamedTypeSymbol Type { get; } + public string CommandPrefix { get; } + public ImmutableArray Properties { get; } + public ImmutableArray NestedTypes { get; } + + public SettingsTypeInfo( + INamedTypeSymbol type, + string commandPrefix, + ImmutableArray properties, + ImmutableArray nestedTypes) + { + Type = type; + CommandPrefix = commandPrefix; + Properties = properties; + NestedTypes = nestedTypes; + } + } + + internal sealed class SettingsPropertyInfo + { + public IPropertySymbol Symbol { get; } + public string CommandPrefix { get; } + public bool IsEnum { get; } + public bool HasSetter => Symbol.SetMethod != null; + + public string PascalCaseName + { + get + { + var name = Symbol.Name; + if (name.Length == 0) return name; + return char.ToUpperInvariant(name[0]) + name.Substring(1); + } + } + + public SettingsPropertyInfo( + IPropertySymbol symbol, + string commandPrefix, + bool isEnum) + { + Symbol = symbol; + CommandPrefix = commandPrefix; + IsEnum = isEnum; + } + } + + internal sealed class NestedSettingsTypeInfo + { + public INamedTypeSymbol Type { get; } + public string CommandPrefix { get; } + public string ParentPrefix { get; } + public ImmutableArray Properties { get; } + + public NestedSettingsTypeInfo( + INamedTypeSymbol type, + string commandPrefix, + string parentPrefix, + ImmutableArray properties) + { + Type = type; + CommandPrefix = commandPrefix; + ParentPrefix = parentPrefix; + Properties = properties; + } + } +} + +``` + +`src/UniCli.SourceGenerator/Analysis/TypeSerializabilityChecker.cs`: + +```cs +using Microsoft.CodeAnalysis; + +namespace UniCli.SourceGenerator.Analysis +{ + internal static class TypeSerializabilityChecker + { + private static readonly string[] SupportedPrimitiveTypes = + { + "System.String", + "System.Boolean", + "System.Int32", + "System.Int64", + "System.Single", + "System.Double", + "System.Byte", + "System.SByte", + "System.Int16", + "System.UInt16", + "System.UInt32", + "System.UInt64", + "System.Char", + }; + + private static readonly string[] SupportedUnityValueTypes = + { + "UnityEngine.Vector2", + "UnityEngine.Vector3", + "UnityEngine.Vector4", + "UnityEngine.Vector2Int", + "UnityEngine.Vector3Int", + "UnityEngine.Color", + "UnityEngine.Color32", + "UnityEngine.Rect", + "UnityEngine.RectInt", + "UnityEngine.Bounds", + "UnityEngine.BoundsInt", + "UnityEngine.Quaternion", + }; + + public static bool IsSerializableType(ITypeSymbol type) + { + if (type == null) + return false; + + if (type is INamedTypeSymbol namedType) + { + if (namedType.IsGenericType) + return false; + + if (IsNullableType(namedType)) + return false; + } + + if (IsPrimitiveType(type)) + return true; + + if (type.TypeKind == TypeKind.Enum) + return true; + + if (IsUnityValueType(type)) + return true; + + return false; + } + + public static bool IsPrimitiveType(ITypeSymbol type) + { + var fullName = GetFullMetadataName(type); + foreach (var primitive in SupportedPrimitiveTypes) + { + if (fullName == primitive) + return true; + } + return false; + } + + public static bool IsEnumType(ITypeSymbol type) + { + return type.TypeKind == TypeKind.Enum; + } + + public static bool IsUnityValueType(ITypeSymbol type) + { + var fullName = GetFullMetadataName(type); + foreach (var unityType in SupportedUnityValueTypes) + { + if (fullName == unityType) + return true; + } + return false; + } + + public static bool IsUnityObjectType(ITypeSymbol type) + { + var current = type; + while (current != null) + { + if (GetFullMetadataName(current) == "UnityEngine.Object") + return true; + current = current.BaseType; + } + return false; + } + + public static bool IsNullableType(INamedTypeSymbol type) + { + return type.IsGenericType && + GetFullMetadataName(type.OriginalDefinition) == "System.Nullable`1"; + } + + public static bool IsDelegateType(ITypeSymbol type) + { + var current = type; + while (current != null) + { + if (GetFullMetadataName(current) == "System.Delegate" || + GetFullMetadataName(current) == "System.MulticastDelegate") + return true; + current = current.BaseType; + } + return type.TypeKind == TypeKind.Delegate; + } + + public static string GetFullMetadataName(ITypeSymbol type) + { + if (type is IArrayTypeSymbol) + return type.ToDisplayString(); + + var parts = new System.Collections.Generic.List(); + var current = type; + + while (current != null) + { + if (current is INamedTypeSymbol named && named.Arity > 0) + parts.Insert(0, $"{current.Name}`{named.Arity}"); + else + parts.Insert(0, current.Name); + + if (current.ContainingType != null) + { + current = current.ContainingType; + } + else + { + break; + } + } + + var ns = type.ContainingNamespace; + if (ns != null && !ns.IsGlobalNamespace) + { + return $"{ns.ToDisplayString()}.{string.Join(".", parts)}"; + } + + return string.Join(".", parts); + } + } +} + +``` + +`src/UniCli.SourceGenerator/Emitters/EmitHelper.cs`: + +```cs +using System.Text; +using Microsoft.CodeAnalysis; +using UniCli.SourceGenerator.Analysis; + +namespace UniCli.SourceGenerator.Emitters +{ + internal static class EmitHelper + { + public static string GetResponseFieldType(ITypeSymbol type) + { + if (TypeSerializabilityChecker.IsEnumType(type)) + return "string"; + + return GetTypeDisplayName(type); + } + + public static string GetTypeDisplayName(ITypeSymbol type) + { + var fullName = TypeSerializabilityChecker.GetFullMetadataName(type); + + switch (fullName) + { + case "System.String": return "string"; + case "System.Boolean": return "bool"; + case "System.Int32": return "int"; + case "System.Int64": return "long"; + case "System.Single": return "float"; + case "System.Double": return "double"; + case "System.Byte": return "byte"; + case "System.SByte": return "sbyte"; + case "System.Int16": return "short"; + case "System.UInt16": return "ushort"; + case "System.UInt32": return "uint"; + case "System.UInt64": return "ulong"; + case "System.Char": return "char"; + default: return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + } + + public static string GetValueReadExpression(IPropertySymbol property, string accessPrefix) + { + if (TypeSerializabilityChecker.IsEnumType(property.Type)) + return $"{accessPrefix}.{property.Name}.ToString()"; + + return $"{accessPrefix}.{property.Name}"; + } + + public static string ToCamelCase(string name) + { + if (string.IsNullOrEmpty(name)) + return name; + return "@" + char.ToLowerInvariant(name[0]) + name.Substring(1); + } + + public static void AppendAutoGeneratedHeader(StringBuilder sb) + { + sb.AppendLine("// "); + sb.AppendLine("// This file is generated by UniCli.SourceGenerator. Do not edit manually."); + sb.AppendLine(); + } + + public static void AppendUsings(StringBuilder sb) + { + sb.AppendLine("using System;"); + sb.AppendLine("using System.Threading;"); + sb.AppendLine("using System.Threading.Tasks;"); + sb.AppendLine("using UniCli.Protocol;"); + sb.AppendLine("using UniCli.Server.Editor.Handlers;"); + sb.AppendLine("using UnityEngine;"); + sb.AppendLine(); + } + + public static void AppendInstanceResolveCode( + StringBuilder sb, + string indent, + SettingsCommandGenerator.ResolveMode mode, + string typeFullName) + { + switch (mode) + { + case SettingsCommandGenerator.ResolveMode.Guid: + sb.AppendLine($"{indent}var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(request.guid);"); + sb.AppendLine($"{indent}if (string.IsNullOrEmpty(assetPath))"); + sb.AppendLine($"{indent} throw new CommandFailedException($\"Asset not found for GUID: {{request.guid}}\", null);"); + sb.AppendLine($"{indent}var instance = UnityEditor.AssetDatabase.LoadAssetAtPath<{typeFullName}>(assetPath);"); + sb.AppendLine($"{indent}if (instance == null)"); + sb.AppendLine($"{indent} throw new CommandFailedException($\"Failed to load asset at: {{assetPath}}\", null);"); + break; + + case SettingsCommandGenerator.ResolveMode.InstanceId: + sb.AppendLine($"{indent}var obj = UnityEditor.EditorUtility.InstanceIDToObject(request.instanceId);"); + sb.AppendLine($"{indent}if (obj is not {typeFullName} instance)"); + sb.AppendLine($"{indent} throw new CommandFailedException($\"Object not found for instanceId: {{request.instanceId}}\", null);"); + break; + } + } + + public static void AppendInstanceResolveRequestFields( + StringBuilder sb, + string indent, + SettingsCommandGenerator.ResolveMode mode) + { + switch (mode) + { + case SettingsCommandGenerator.ResolveMode.Guid: + sb.AppendLine($"{indent}public string guid;"); + break; + case SettingsCommandGenerator.ResolveMode.InstanceId: + sb.AppendLine($"{indent}public int instanceId;"); + break; + } + } + + public static void AppendModuleAttribute(StringBuilder sb, string indent, string module) + { + if (!string.IsNullOrEmpty(module)) + sb.AppendLine($"{indent}[UniCli.Server.Editor.ModuleAttribute(\"{module}\")]"); + } + } +} + +``` + +`src/UniCli.SourceGenerator/Emitters/GetCommandEmitter.cs`: + +```cs +using System.Text; +using Microsoft.CodeAnalysis; +using UniCli.SourceGenerator.Analysis; + +namespace UniCli.SourceGenerator.Emitters +{ + internal static class GetCommandEmitter + { + public static string Emit(SettingsTypeInfo info, string module) + { + var sb = new StringBuilder(); + EmitHelper.AppendAutoGeneratedHeader(sb); + EmitHelper.AppendUsings(sb); + + var className = info.CommandPrefix.Replace(".", "") + "InspectHandler"; + var responseName = info.CommandPrefix.Replace(".", "") + "InspectResponse"; + var settingsFullName = info.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + sb.AppendLine("namespace UniCli.Server.Editor.Handlers"); + sb.AppendLine("{"); + + // Handler class + EmitHelper.AppendModuleAttribute(sb, " ", module); + sb.AppendLine($" public sealed class {className} : CommandHandler"); + sb.AppendLine(" {"); + sb.AppendLine($" public override string CommandName => \"{info.CommandPrefix}.Inspect\";"); + sb.AppendLine($" public override string Description => \"Inspect all {info.CommandPrefix} values\";"); + sb.AppendLine(); + sb.AppendLine($" protected override ValueTask<{responseName}> ExecuteAsync(Unit request, CancellationToken cancellationToken)"); + sb.AppendLine(" {"); + sb.AppendLine($" var response = new {responseName}();"); + + // Assign flat properties (skip obsolete) + foreach (var prop in info.Properties) + { + if (prop.Symbol.GetMethod == null) continue; + var fieldName = EmitHelper.ToCamelCase(prop.PascalCaseName); + sb.AppendLine($" try {{ response.{fieldName} = {EmitHelper.GetValueReadExpression(prop.Symbol, settingsFullName)}; }}"); + sb.AppendLine(" catch (System.Exception) { }"); + } + + // Assign nested type properties + foreach (var nested in info.NestedTypes) + { + var nestedFieldName = EmitHelper.ToCamelCase(nested.Type.Name); + var nestedResponseName = nested.Type.Name + "Settings"; + var nestedFullName = nested.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + sb.AppendLine($" response.{nestedFieldName} = new {responseName}.{nestedResponseName}();"); + + foreach (var prop in nested.Properties) + { + if (prop.Symbol.GetMethod == null) continue; + var fieldName = EmitHelper.ToCamelCase(prop.PascalCaseName); + sb.AppendLine($" try {{ response.{nestedFieldName}.{fieldName} = {EmitHelper.GetValueReadExpression(prop.Symbol, nestedFullName)}; }}"); + sb.AppendLine(" catch (System.Exception) { }"); + } + } + + sb.AppendLine($" return new ValueTask<{responseName}>(response);"); + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine(); + + // Response class + EmitResponseClass(sb, info, responseName); + + sb.AppendLine("}"); + return sb.ToString(); + } + + private static void EmitResponseClass( + StringBuilder sb, SettingsTypeInfo info, string responseName) + { + sb.AppendLine(" [Serializable]"); + sb.AppendLine($" public class {responseName}"); + sb.AppendLine(" {"); + + // Flat properties (skip obsolete) + foreach (var prop in info.Properties) + { + if (prop.Symbol.GetMethod == null) continue; + var fieldType = EmitHelper.GetResponseFieldType(prop.Symbol.Type); + var fieldName = EmitHelper.ToCamelCase(prop.PascalCaseName); + sb.AppendLine($" public {fieldType} {fieldName};"); + } + + // Nested type fields + foreach (var nested in info.NestedTypes) + { + var nestedFieldName = EmitHelper.ToCamelCase(nested.Type.Name); + var nestedResponseName = nested.Type.Name + "Settings"; + sb.AppendLine($" public {nestedResponseName} {nestedFieldName};"); + } + + // Nested response classes + foreach (var nested in info.NestedTypes) + { + var nestedResponseName = nested.Type.Name + "Settings"; + sb.AppendLine(); + sb.AppendLine(" [Serializable]"); + sb.AppendLine($" public class {nestedResponseName}"); + sb.AppendLine(" {"); + + foreach (var prop in nested.Properties) + { + if (prop.Symbol.GetMethod == null) continue; + var fieldType = EmitHelper.GetResponseFieldType(prop.Symbol.Type); + var fieldName = EmitHelper.ToCamelCase(prop.PascalCaseName); + sb.AppendLine($" public {fieldType} {fieldName};"); + } + + sb.AppendLine(" }"); + } + + sb.AppendLine(" }"); + } + } +} + +``` + +`src/UniCli.SourceGenerator/Emitters/InstanceInspectCommandEmitter.cs`: + +```cs +using System.Text; +using Microsoft.CodeAnalysis; +using UniCli.SourceGenerator.Analysis; + +namespace UniCli.SourceGenerator.Emitters +{ + internal static class InstanceInspectCommandEmitter + { + public static string Emit(InstanceTypeInfo info, string module) + { + var sb = new StringBuilder(); + EmitHelper.AppendAutoGeneratedHeader(sb); + EmitHelper.AppendUsings(sb); + + var className = info.CommandPrefix.Replace(".", "") + "InspectHandler"; + var requestName = info.CommandPrefix.Replace(".", "") + "InspectRequest"; + var responseName = info.CommandPrefix.Replace(".", "") + "InspectResponse"; + var typeFullName = info.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + sb.AppendLine("namespace UniCli.Server.Editor.Handlers"); + sb.AppendLine("{"); + + // Handler class + EmitHelper.AppendModuleAttribute(sb, " ", module); + sb.AppendLine($" public sealed class {className} : CommandHandler<{requestName}, {responseName}>"); + sb.AppendLine(" {"); + sb.AppendLine($" public override string CommandName => \"{info.CommandPrefix}.Inspect\";"); + sb.AppendLine($" public override string Description => \"Inspect {info.CommandPrefix} instance\";"); + sb.AppendLine(); + sb.AppendLine($" protected override ValueTask<{responseName}> ExecuteAsync({requestName} request, CancellationToken cancellationToken)"); + sb.AppendLine(" {"); + + EmitHelper.AppendInstanceResolveCode(sb, " ", info.ResolveMode, typeFullName); + sb.AppendLine(); + + sb.AppendLine($" var response = new {responseName}();"); + + foreach (var prop in info.Properties) + { + if (prop.Symbol.GetMethod == null) continue; + var fieldName = EmitHelper.ToCamelCase(prop.PascalCaseName); + var readExpr = prop.IsEnum + ? $"instance.{prop.Symbol.Name}.ToString()" + : $"instance.{prop.Symbol.Name}"; + sb.AppendLine($" try {{ response.{fieldName} = {readExpr}; }}"); + sb.AppendLine(" catch (System.Exception) { }"); + } + + sb.AppendLine($" return new ValueTask<{responseName}>(response);"); + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine(); + + // Request class + sb.AppendLine(" [Serializable]"); + sb.AppendLine($" public class {requestName}"); + sb.AppendLine(" {"); + EmitHelper.AppendInstanceResolveRequestFields(sb, " ", info.ResolveMode); + sb.AppendLine(" }"); + sb.AppendLine(); + + // Response class + sb.AppendLine(" [Serializable]"); + sb.AppendLine($" public class {responseName}"); + sb.AppendLine(" {"); + + foreach (var prop in info.Properties) + { + if (prop.Symbol.GetMethod == null) continue; + var fieldType = EmitHelper.GetResponseFieldType(prop.Symbol.Type); + var fieldName = EmitHelper.ToCamelCase(prop.PascalCaseName); + sb.AppendLine($" public {fieldType} {fieldName};"); + } + + sb.AppendLine(" }"); + + sb.AppendLine("}"); + return sb.ToString(); + } + } +} + +``` + +`src/UniCli.SourceGenerator/SettingsCommandGenerator.cs`: + +```cs +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using UniCli.SourceGenerator.Analysis; +using UniCli.SourceGenerator.Emitters; + +namespace UniCli.SourceGenerator +{ + [Generator(LanguageNames.CSharp)] + public sealed class SettingsCommandGenerator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var compilationProvider = context.CompilationProvider; + + context.RegisterSourceOutput(compilationProvider, (spc, compilation) => + { + Execute(spc, compilation); + }); + } + + internal enum ResolveMode + { + Static = 0, + Guid = 1, + InstanceId = 2, + } + + private static List<(string TypeName, string CommandPrefix, ResolveMode Mode, string Module)> CollectTargetTypes( + Compilation compilation) + { + var targets = new List<(string TypeName, string CommandPrefix, ResolveMode Mode, string Module)>(); + + foreach (var attr in compilation.Assembly.GetAttributes()) + { + if (attr.AttributeClass == null || + attr.AttributeClass.Name != "GenerateCommandsAttribute") + continue; + + if (attr.ConstructorArguments.Length < 2) + continue; + + var typeName = attr.ConstructorArguments[0].Value as string; + var commandPrefix = attr.ConstructorArguments[1].Value as string; + + if (string.IsNullOrEmpty(typeName) || string.IsNullOrEmpty(commandPrefix)) + continue; + + var mode = ResolveMode.Static; + var module = ""; + foreach (var named in attr.NamedArguments) + { + if (named.Key == "ResolveMode" && named.Value.Value is int modeValue) + { + mode = (ResolveMode)modeValue; + } + else if (named.Key == "Module" && named.Value.Value is string moduleValue) + { + module = moduleValue; + } + } + + targets.Add((typeName, commandPrefix, mode, module)); + } + + return targets; + } + + private static void Execute(SourceProductionContext context, Compilation compilation) + { + // Only generate for the main server assembly to avoid duplicates + // when the Source Generator is also applied to referencing assemblies (e.g. Tests) + if (compilation.AssemblyName != "UniCli.Server.Editor") + return; + + var targetTypes = CollectTargetTypes(compilation); + if (targetTypes.Count == 0) + return; + + var generatedFileNames = new HashSet(); + var generatedCommandNames = new HashSet(); + + foreach (var (typeName, commandPrefix, mode, module) in targetTypes) + { + try + { + var typeSymbol = compilation.GetTypeByMetadataName(typeName); + if (typeSymbol == null) + continue; + + if (mode != ResolveMode.Static) + { + EmitInstanceType(context, typeSymbol, commandPrefix, mode, module, + generatedFileNames, generatedCommandNames); + continue; + } + + var info = SettingsTypeAnalyzer.Analyze(typeSymbol, commandPrefix); + + // Emit Inspect handler + var inspectCommandName = $"{info.CommandPrefix}.Inspect"; + if (generatedCommandNames.Add(inspectCommandName)) + { + var inspectSource = GetCommandEmitter.Emit(info, module); + AddSourceSafe(context, generatedFileNames, + $"{commandPrefix}InspectHandler.g.cs", inspectSource); + } + } + catch (System.Exception) + { + // Skip this settings type entirely on error + } + } + } + + private static void EmitInstanceType( + SourceProductionContext context, + INamedTypeSymbol typeSymbol, + string commandPrefix, + ResolveMode mode, + string module, + HashSet generatedFileNames, + HashSet generatedCommandNames) + { + var info = InstanceTypeAnalyzer.Analyze(typeSymbol, commandPrefix, mode); + + // Emit Inspect handler + var inspectCommandName = $"{info.CommandPrefix}.Inspect"; + if (generatedCommandNames.Add(inspectCommandName)) + { + var inspectSource = InstanceInspectCommandEmitter.Emit(info, module); + AddSourceSafe(context, generatedFileNames, + $"{commandPrefix}InspectHandler.g.cs", inspectSource); + } + } + + private static void AddSourceSafe( + SourceProductionContext context, + HashSet generatedFileNames, + string hintName, + string source) + { + // Avoid duplicate file names + if (!generatedFileNames.Add(hintName)) + { + var counter = 2; + var baseName = hintName.Replace(".g.cs", ""); + while (!generatedFileNames.Add($"{baseName}_{counter}.g.cs")) + counter++; + hintName = $"{baseName}_{counter}.g.cs"; + } + + context.AddSource(hintName, SourceText.From(source, Encoding.UTF8)); + } + } +} + +``` + +`src/UniCli.SourceGenerator/UniCli.SourceGenerator.csproj`: + +```csproj + + + + netstandard2.0 + latest + true + true + + + + $(MSBuildProjectDirectory)/../UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Generators + + + + + + + + + + + + + + +``` + +`src/UniCli.Unity/Assets/NuGet.config`: + +```config + + + + + + + + + + + + + + + + + + +``` + +`src/UniCli.Unity/Assets/NuGet.config.meta`: + +```meta +fileFormatVersion: 2 +guid: 555b8925e70934b5a8878dcd7179d416 +labels: +- NuGetForUnity +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Assets/Packages.meta`: + +```meta +fileFormatVersion: 2 +guid: 4a9b26043a9d0e043801822abd806c71 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Assets/Scenes.meta`: + +```meta +fileFormatVersion: 2 +guid: 0cf9d9382fb034ac2a890797c9730fb4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Assets/Scenes/SampleScene.unity`: + +```unity +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.802082 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1494649457 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1494649458} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1494649458 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1494649457} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1820271589} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1820271588 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1820271589} + m_Layer: 0 + m_Name: GameObject (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1820271589 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1820271588} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1494649458} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 963194228} + - {fileID: 705507995} + - {fileID: 1494649458} + +``` + +`src/UniCli.Unity/Assets/Scenes/SampleScene.unity.meta`: + +```meta +fileFormatVersion: 2 +guid: 9fc0d4010bbf28b4594072e72b8655ab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Assets/packages.config`: + +```config + + +``` + +`src/UniCli.Unity/Assets/packages.config.meta`: + +```meta +fileFormatVersion: 2 +guid: 8b26764588dbe4d269d77ea786acd5e5 +labels: +- NuGetForUnity +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor.meta`: + +```meta +fileFormatVersion: 2 +guid: db7117157b6544c0bb59449ab34dfc05 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/AssemblyInfo.cs`: + +```cs +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("UniCli.Server.Editor.Tests")] +[assembly: InternalsVisibleTo("UniCli.Server.Editor.NuGetForUnity")] +[assembly: InternalsVisibleTo("UniCli.Server.Editor.BuildMagic")] +[assembly: InternalsVisibleTo("UniCli.Server.Editor.Search")] + +[assembly: GenerateCommands("UnityEditor.PlayerSettings", "PlayerSettings")] +[assembly: GenerateCommands("UnityEditor.EditorSettings", "EditorSettings")] +[assembly: GenerateCommands("UnityEditor.EditorUserBuildSettings", "EditorUserBuildSettings")] +[assembly: GenerateCommands("UnityEngine.Material", "Material", ResolveMode = InstanceResolveMode.Guid, Module = "Assets")] + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/AssemblyInfo.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 63a35380c08d141fba96dc736862c5b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Build.meta`: + +```meta +fileFormatVersion: 2 +guid: ce0d70e24c6c94d959a9c65cd20e0817 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Build/RemoteLinkerProcessor.cs`: + +```cs +using System; +using System.IO; +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEditor.UnityLinker; + +namespace UniCli.Server.Editor.Build +{ + public sealed class RemoteLinkerProcessor : IUnityLinkerProcessor + { + public int callbackOrder => 0; + + public string GenerateAdditionalLinkXmlFile(BuildReport report, UnityLinkerBuildPipelineData data) + { + if (!IsRemoteAssemblyIncluded(report)) + return null; + + var path = Path.Combine(data.inputDirectory, "UniCli.Remote.link.xml"); + File.WriteAllText(path, "\n \n\n"); + return path; + } + + private static bool IsRemoteAssemblyIncluded(BuildReport report) + { + // UniCli.Remote asmdef defineConstraints: + // "DEVELOPMENT_BUILD || UNITY_EDITOR" + // "UNICLI_REMOTE || UNITY_EDITOR" + // In player builds UNITY_EDITOR is not defined, + // so both DEVELOPMENT_BUILD and UNICLI_REMOTE must be present. + if ((report.summary.options & BuildOptions.Development) == 0) + return false; + + var targetGroup = BuildPipeline.GetBuildTargetGroup(report.summary.platform); + var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(targetGroup); + return Array.IndexOf(defines.Split(';'), "UNICLI_REMOTE") >= 0; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Build/RemoteLinkerProcessor.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b96755bcf5eea4c9aa2a7eebbf7316e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/CommandDispatcher.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UniCli.Server.Editor.Handlers; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor +{ + public sealed class CommandDispatcher + { + private readonly Dictionary _handlers = + new(StringComparer.OrdinalIgnoreCase); + private readonly StringBuilder _formatBuffer = new(); + private readonly Lazy _commandInfoCache; + + private static readonly string ServerVersion = ResolveServerVersion(); + + // Update on release: clients older than this version will be rejected + private const string MinimumClientVersion = "0.11.1"; + + private static string ResolveServerVersion() + { + var packageInfo = UnityEditor.PackageManager.PackageInfo + .FindForAssembly(typeof(CommandDispatcher).Assembly); + return packageInfo?.version ?? "unknown"; + } + + public CommandDispatcher(ServiceRegistry services) + { + services.AddSingleton(this); + RegisterClassHandlers(services); + _commandInfoCache = new Lazy( + () => _handlers.Values.Select(h => h.GetCommandInfo()).ToArray()); + } + + private void RegisterClassHandlers(ServiceRegistry services) + { + var handlerTypes = TypeCache.GetTypesDerivedFrom(); + var settings = services.Resolve(); + + foreach (var type in handlerTypes) + { + if (type.IsAbstract || type.IsInterface) + continue; + + if (type.Assembly.GetName().Name.Contains(".Tests")) + continue; + + var moduleName = ModuleRegistry.ResolveModuleName(type); + if (moduleName != null && settings != null && !settings.IsModuleEnabled(moduleName)) + continue; + + ICommandHandler handler; + try + { + var instance = services.CreateInstance(type); + if (instance == null) + { + UniCliEditorLog.LogWarning($"[UniCli] Failed to create handler instance: {type.FullName} (unresolvable constructor parameters)"); + continue; + } + handler = (ICommandHandler)instance; + } + catch (Exception ex) + { + UniCliEditorLog.LogWarning($"[UniCli] Failed to create handler instance: {type.FullName} ({ex.Message})"); + continue; + } + + if (!_handlers.TryAdd(handler.CommandName, handler)) + { + UniCliEditorLog.LogWarning($"[UniCli] Command '{handler.CommandName}' is already registered, skipping {type.FullName}"); + } + } + } + + public CommandInfo[] GetAllCommandInfo() => _commandInfoCache.Value; + + public async ValueTask DispatchAsync(CommandRequest request, CancellationToken cancellationToken) + { + if (!_handlers.TryGetValue(request.command, out var handler)) + return MakeResponse(false, $"Unknown command: {request.command}"); + + var versionCheck = CheckVersionCompatibility(request.clientVersion); + if (versionCheck.IsError) + return MakeResponse(false, versionCheck.Message); + + var wantsText = request.format == "text"; + + try + { + var result = await handler.ExecuteAsync(request, cancellationToken); + var response = BuildResponse(true, $"Command '{request.command}' succeeded", result, handler, wantsText); + response.versionWarning = versionCheck.Warning; + return response; + } + catch (CommandFailedException ex) + { + var response = BuildResponse(false, $"Command failed: {ex.Message}", ex.ResponseData, handler, wantsText); + response.versionWarning = versionCheck.Warning; + return response; + } + catch (Exception ex) + { + var response = MakeResponse(false, $"Command failed: {ex.Message}"); + response.versionWarning = versionCheck.Warning; + return response; + } + } + + private readonly struct VersionCheckResult + { + public readonly bool IsError; + public readonly string Message; + public readonly string Warning; + + public VersionCheckResult(bool isError, string message, string warning) + { + IsError = isError; + Message = message; + Warning = warning; + } + } + + private static VersionCheckResult CheckVersionCompatibility(string clientVersion) + { + if (string.IsNullOrEmpty(clientVersion)) + return new VersionCheckResult(false, "", + $"Unknown client version. Server is v{ServerVersion} (minimum client: v{MinimumClientVersion}). Please update unicli CLI."); + + if (!Version.TryParse(clientVersion, out var client) || + !Version.TryParse(MinimumClientVersion, out var minimum)) + return default; + + if (client < minimum) + return new VersionCheckResult(true, + $"Client v{clientVersion} is below minimum supported v{MinimumClientVersion} (server v{ServerVersion}). Please update unicli CLI.", + ""); + + return default; + } + + internal CommandResponse BuildResponse(bool success, string message, object data, ICommandHandler handler, bool wantsText) + { + if (data is Unit or null) + return MakeResponse(success, message); + + if (wantsText && handler is IResponseFormatter formatter) + { + _formatBuffer.Clear(); + var writer = new StringFormatWriter(_formatBuffer); + if (formatter.TryWriteFormatted(data, success, writer)) + return MakeResponse(success, message, _formatBuffer.ToString(), "text"); + } + + var json = data is IRawJsonResponse rawJson + ? rawJson.ToJson() + : JsonUtility.ToJson(data); + + return MakeResponse(success, message, json); + } + + internal static CommandResponse MakeResponse(bool success, string message, string data = "", string format = "json") + { + return new CommandResponse + { + success = success, + message = message, + data = data, + format = format, + serverVersion = ServerVersion + }; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/CommandDispatcher.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 45913bf7aac59432fbccaaead9b26a40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/CoreServiceInstaller.cs`: + +```cs +namespace UniCli.Server.Editor +{ + public sealed class CoreServiceInstaller : IServiceInstaller + { + public void Install(ServiceRegistry services) + { + services.AddSingleton(new EditorLogManager(maxBufferSize: 10000)); + + var pipeName = ProjectIdentifier.GetPipeName(); + services.AddSingleton(new ServerContext(pipeName)); + + var settings = new UniCliSettings(); + services.AddSingleton(settings); + var enableLogs = settings.IsEditorLoggingEnabled(); + UniCliSettings.ApplyEditorLoggingEnabled(enableLogs); + services.AddSingleton(new EditorStateGuard()); + services.AddSingleton(new BootstrapDispatcherReloader()); + } + + private sealed class BootstrapDispatcherReloader : IDispatcherReloader + { + public void Reload() => UniCliServerBootstrap.ReloadDispatcher(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/CoreServiceInstaller.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 02a2b7296ea2b4c5f948b4556afe9ea0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/EditorLogManager.cs`: + +```cs +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniCli.Server.Editor +{ + [Serializable] + public sealed class LogEntry + { + public string message; + public string stackTrace; + public string type; + public string timestamp; + } + + [Serializable] + public sealed class EditorLogResponse + { + public LogEntry[] logs; + public int totalCount; + public int displayedCount; + } + + public sealed class EditorLogManager + { + private readonly object _lock = new(); + private readonly Queue _logBuffer = new(); + private readonly int _maxBufferSize; + + public EditorLogManager(int maxBufferSize = 10000) + { + _maxBufferSize = maxBufferSize; + Application.logMessageReceivedThreaded += OnLogMessageReceived; + } + + private void OnLogMessageReceived(string message, string stackTrace, LogType type) + { + var entry = new LogEntry + { + message = message, + stackTrace = stackTrace, + type = type.ToString(), + timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + }; + + lock (_lock) + { + _logBuffer.Enqueue(entry); + if (_logBuffer.Count > _maxBufferSize) + { + _logBuffer.Dequeue(); + } + } + } + + public int GetLogCount() + { + lock (_lock) + { + return _logBuffer.Count; + } + } + + public LogEntry[] GetLogs() + { + lock (_lock) + { + return _logBuffer.ToArray(); + } + } + + public LogEntry[] GetLogs(string logType, string searchText, int maxCount) + { + var filterByType = !string.IsNullOrEmpty(logType) + && !logType.Equals("All", StringComparison.OrdinalIgnoreCase); + var filterBySearch = !string.IsNullOrEmpty(searchText); + + string[] allowedTypes = null; + if (filterByType) + { + allowedTypes = logType.Split(','); + for (var i = 0; i < allowedTypes.Length; i++) + allowedTypes[i] = allowedTypes[i].Trim(); + } + + var result = new List(); + + lock (_lock) + { + foreach (var entry in _logBuffer) + { + if (filterByType && !MatchesAnyType(entry.type, allowedTypes)) + continue; + + if (filterBySearch && !entry.message.Contains(searchText, StringComparison.OrdinalIgnoreCase)) + continue; + + result.Add(entry); + } + } + + if (maxCount > 0 && result.Count > maxCount) + { + result.RemoveRange(0, result.Count - maxCount); + } + + return result.ToArray(); + } + + private static bool MatchesAnyType(string entryType, string[] allowedTypes) + { + foreach (var type in allowedTypes) + { + if (entryType.Equals(type, StringComparison.OrdinalIgnoreCase)) + return true; + } + return false; + } + + public void ClearLogs() + { + lock (_lock) + { + _logBuffer.Clear(); + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/EditorLogManager.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: a7f630ac131af491e82950ec409310c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/EditorStateGuard.cs`: + +```cs +using System; +using UnityEditor; + +namespace UniCli.Server.Editor +{ + [Flags] + public enum GuardCondition + { + NotPlaying = 1, + NotCompiling = 2, + NotPlayingOrCompiling = NotPlaying | NotCompiling, + } + + public sealed class EditorStateGuard + { + private string _activeCommand; + + public GuardScope BeginScope(string commandName, GuardCondition condition) + { + if ((condition & GuardCondition.NotPlaying) != 0 && EditorApplication.isPlayingOrWillChangePlaymode) + throw new InvalidOperationException($"Cannot execute '{commandName}' while in Play Mode."); + + if ((condition & GuardCondition.NotCompiling) != 0 && EditorApplication.isCompiling) + throw new InvalidOperationException($"Cannot execute '{commandName}' while compiling."); + + _activeCommand = commandName; + return new GuardScope(this); + } + + public readonly struct GuardScope : IDisposable + { + private readonly EditorStateGuard _guard; + + internal GuardScope(EditorStateGuard guard) => _guard = guard; + + public void Dispose() + { + if (_guard != null) + _guard._activeCommand = null; + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/EditorStateGuard.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 38beed57737f44d748b161d5c3f94ebe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/GameObjectResolver.cs`: + +```cs +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor +{ + internal static class GameObjectResolver + { + public static string BuildPath(Transform transform) + { + var parts = new List(); + var current = transform; + while (current != null) + { + parts.Add(current.name); + current = current.parent; + } + + parts.Reverse(); + return string.Join("/", parts); + } + + public static GameObject ResolveByIdOrPath(int instanceId, string path) + { + if (instanceId != 0) + return EditorUtility.InstanceIDToObject(instanceId) as GameObject; + + return ResolveByPath(path); + } + + public static GameObject ResolveByPath(string path) + { + return !string.IsNullOrEmpty(path) ? FindByPath(path) : null; + } + + private static GameObject FindByPath(string path) + { + var parts = path.Split('/'); + if (parts.Length == 0) return null; + + var rootName = parts[0]; + GameObject root = null; + + for (var i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (!scene.isLoaded) continue; + + foreach (var go in scene.GetRootGameObjects()) + { + if (go.name == rootName) + { + root = go; + break; + } + } + + if (root != null) break; + } + + if (root == null) return null; + + var current = root.transform; + for (var i = 1; i < parts.Length; i++) + { + var child = current.Find(parts[i]); + if (child == null) return null; + current = child; + } + + return current.gameObject; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/GameObjectResolver.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/GenerateCommandsAttribute.cs`: + +```cs +using System; + +internal enum InstanceResolveMode +{ + Static = 0, + Guid = 1, + InstanceId = 2, +} + +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +internal sealed class GenerateCommandsAttribute : Attribute +{ + public string TypeName { get; } + public string CommandPrefix { get; } + public InstanceResolveMode ResolveMode { get; set; } = InstanceResolveMode.Static; + public string Module { get; set; } = ""; + + public GenerateCommandsAttribute(string typeName, string commandPrefix) + { + TypeName = typeName; + CommandPrefix = commandPrefix; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/GenerateCommandsAttribute.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 58848b6a5b56442bb1859f7d680a4e07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Generators.meta`: + +```meta +fileFormatVersion: 2 +guid: fb8f7ce99e3f84319b3e4c6a1a96d639 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Generators/UniCli.SourceGenerator.dll.meta`: + +```meta +fileFormatVersion: 2 +guid: 26ca7e2d29bba403b90f80baf4a2728d +labels: +- RoslynAnalyzer +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers.meta`: + +```meta +fileFormatVersion: 2 +guid: b440a110b3a6945dfa66666a4acd1741 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator.meta`: + +```meta +fileFormatVersion: 2 +guid: f762d293a103c4388b3b53afe5d8578f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/CrossFadeHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorCrossFadeHandler + : CommandHandler + { + public override string CommandName => "Animator.CrossFade"; + public override string Description => "Cross-fade to a state on an Animator (requires PlayMode)"; + + protected override bool TryWriteFormatted(AnimatorCrossFadeResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine( + $"Cross-fading to \"{response.stateName}\" (duration={response.transitionDuration}) on \"{response.gameObjectName}\""); + else + writer.WriteLine("Failed to cross-fade"); + + return true; + } + + protected override ValueTask ExecuteAsync(AnimatorCrossFadeRequest request, CancellationToken cancellationToken) + { + if (!EditorApplication.isPlaying) + { + throw new CommandFailedException( + "This command requires PlayMode", + new AnimatorCrossFadeResponse()); + } + + if (string.IsNullOrEmpty(request.stateName)) + throw new ArgumentException("stateName is required"); + + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new AnimatorCrossFadeResponse()); + } + + var animator = go.GetComponent(); + if (animator == null) + { + throw new CommandFailedException( + $"Animator component not found on \"{go.name}\"", + new AnimatorCrossFadeResponse()); + } + + animator.CrossFade(request.stateName, request.transitionDuration, request.layer, + request.normalizedTime); + + return new ValueTask(new AnimatorCrossFadeResponse + { + gameObjectName = go.name, + stateName = request.stateName, + layer = request.layer, + transitionDuration = request.transitionDuration, + normalizedTime = request.normalizedTime + }); + } + } + + [Serializable] + public class AnimatorCrossFadeRequest + { + public int instanceId; + public string path = ""; + public string stateName = ""; + public int layer; + public float transitionDuration = 0.25f; + public float normalizedTime; + } + + [Serializable] + public class AnimatorCrossFadeResponse + { + public string gameObjectName; + public string stateName; + public int layer; + public float transitionDuration; + public float normalizedTime; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/CrossFadeHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b41cde692b98b43f38d366ad06332b50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/InspectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorInspectHandler + : CommandHandler + { + public override string CommandName => "Animator.Inspect"; + public override string Description => "Inspect an Animator component (parameters, current state, controller info)"; + + protected override bool TryWriteFormatted(AnimatorInspectResponse response, bool success, + IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to inspect Animator"); + return true; + } + + writer.WriteLine($"Animator on \"{response.gameObjectName}\""); + writer.WriteLine($" Controller: {response.controllerAssetPath}"); + writer.WriteLine($" Enabled: {response.enabled}"); + + if (response.parameters?.Length > 0) + { + writer.WriteLine($" Parameters ({response.parameters.Length}):"); + foreach (var p in response.parameters) + writer.WriteLine($" {p.name} ({p.type}) = {p.value}"); + } + + if (response.isPlaying && response.currentStateName != null) + writer.WriteLine($" Current State: {response.currentStateName} (layer 0)"); + + return true; + } + + protected override ValueTask ExecuteAsync(AnimatorInspectRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new AnimatorInspectResponse()); + } + + var animator = go.GetComponent(); + if (animator == null) + { + throw new CommandFailedException( + $"Animator component not found on \"{go.name}\"", + new AnimatorInspectResponse()); + } + + var controllerPath = ""; + if (animator.runtimeAnimatorController != null) + controllerPath = AssetDatabase.GetAssetPath(animator.runtimeAnimatorController); + + var parameters = new List(); + var isPlaying = EditorApplication.isPlaying; + + if (animator.runtimeAnimatorController != null) + { + for (var i = 0; i < animator.parameterCount; i++) + { + var param = animator.GetParameter(i); + var info = new AnimatorRuntimeParameterInfo + { + name = param.name, + type = param.type.ToString() + }; + + if (isPlaying && animator.isActiveAndEnabled) + { + info.value = param.type switch + { + AnimatorControllerParameterType.Float => + animator.GetFloat(param.name).ToString("F3"), + AnimatorControllerParameterType.Int => + animator.GetInteger(param.name).ToString(), + AnimatorControllerParameterType.Bool => + animator.GetBool(param.name).ToString(), + AnimatorControllerParameterType.Trigger => "(trigger)", + _ => "?" + }; + } + else + { + info.value = param.type switch + { + AnimatorControllerParameterType.Float => param.defaultFloat.ToString("F3"), + AnimatorControllerParameterType.Int => param.defaultInt.ToString(), + AnimatorControllerParameterType.Bool => param.defaultBool.ToString(), + AnimatorControllerParameterType.Trigger => "(trigger)", + _ => "?" + }; + } + + parameters.Add(info); + } + } + + var response = new AnimatorInspectResponse + { + gameObjectName = go.name, + gameObjectPath = GameObjectResolver.BuildPath(go.transform), + instanceId = animator.GetInstanceID(), + enabled = animator.enabled, + controllerAssetPath = controllerPath, + parameters = parameters.ToArray(), + isPlaying = isPlaying + }; + + if (isPlaying && animator.isActiveAndEnabled) + { + var stateInfo = animator.GetCurrentAnimatorStateInfo(0); + response.currentStateName = GetStateName(animator, stateInfo, 0); + response.currentStateNormalizedTime = stateInfo.normalizedTime; + } + + return new ValueTask(response); + } + + private static string GetStateName(UnityEngine.Animator animator, + UnityEngine.AnimatorStateInfo stateInfo, int layerIndex) + { + if (animator.runtimeAnimatorController is UnityEditor.Animations.AnimatorController ac) + { + if (layerIndex < ac.layers.Length) + { + foreach (var childState in ac.layers[layerIndex].stateMachine.states) + { + if (stateInfo.IsName(childState.state.name)) + return childState.state.name; + } + } + } + + return $"(hash={stateInfo.fullPathHash})"; + } + } + + [Serializable] + public class AnimatorInspectRequest + { + public int instanceId; + public string path = ""; + } + + [Serializable] + public class AnimatorInspectResponse + { + public string gameObjectName; + public string gameObjectPath; + public int instanceId; + public bool enabled; + public string controllerAssetPath; + public AnimatorRuntimeParameterInfo[] parameters; + public bool isPlaying; + public string currentStateName; + public float currentStateNormalizedTime; + } + + [Serializable] + public class AnimatorRuntimeParameterInfo + { + public string name; + public string type; + public string value; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/InspectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 86ccd49e6bca942f89be22d758964d23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/PlayHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorPlayHandler + : CommandHandler + { + public override string CommandName => "Animator.Play"; + public override string Description => "Play a state immediately on an Animator (requires PlayMode)"; + + protected override bool TryWriteFormatted(AnimatorPlayResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine($"Playing state \"{response.stateName}\" on \"{response.gameObjectName}\""); + else + writer.WriteLine("Failed to play state"); + + return true; + } + + protected override ValueTask ExecuteAsync(AnimatorPlayRequest request, CancellationToken cancellationToken) + { + if (!EditorApplication.isPlaying) + { + throw new CommandFailedException( + "This command requires PlayMode", + new AnimatorPlayResponse()); + } + + if (string.IsNullOrEmpty(request.stateName)) + throw new ArgumentException("stateName is required"); + + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new AnimatorPlayResponse()); + } + + var animator = go.GetComponent(); + if (animator == null) + { + throw new CommandFailedException( + $"Animator component not found on \"{go.name}\"", + new AnimatorPlayResponse()); + } + + animator.Play(request.stateName, request.layer, request.normalizedTime); + + return new ValueTask(new AnimatorPlayResponse + { + gameObjectName = go.name, + stateName = request.stateName, + layer = request.layer, + normalizedTime = request.normalizedTime + }); + } + } + + [Serializable] + public class AnimatorPlayRequest + { + public int instanceId; + public string path = ""; + public string stateName = ""; + public int layer; + public float normalizedTime; + } + + [Serializable] + public class AnimatorPlayResponse + { + public string gameObjectName; + public string stateName; + public int layer; + public float normalizedTime; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/PlayHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 0ae262e693aff46efa2884e231f75d01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/SetControllerHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorSetControllerHandler + : CommandHandler + { + public override string CommandName => "Animator.SetController"; + public override string Description => "Assign an AnimatorController to an Animator component"; + + protected override bool TryWriteFormatted(AnimatorSetControllerResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine( + $"Set controller \"{response.controllerAssetPath}\" on \"{response.gameObjectName}\""); + else + writer.WriteLine("Failed to set controller"); + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorSetControllerRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.controllerAssetPath)) + throw new ArgumentException("controllerAssetPath is required"); + + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new AnimatorSetControllerResponse()); + } + + var animator = go.GetComponent(); + if (animator == null) + { + throw new CommandFailedException( + $"Animator component not found on \"{go.name}\"", + new AnimatorSetControllerResponse()); + } + + var controller = + AssetDatabase.LoadAssetAtPath(request.controllerAssetPath); + if (controller == null) + { + throw new CommandFailedException( + $"AnimatorController not found at \"{request.controllerAssetPath}\"", + new AnimatorSetControllerResponse()); + } + + Undo.RecordObject(animator, "Set Animator Controller"); + animator.runtimeAnimatorController = controller; + EditorUtility.SetDirty(animator); + + return new ValueTask(new AnimatorSetControllerResponse + { + gameObjectName = go.name, + gameObjectPath = GameObjectResolver.BuildPath(go.transform), + controllerAssetPath = request.controllerAssetPath + }); + } + } + + [Serializable] + public class AnimatorSetControllerRequest + { + public int instanceId; + public string path = ""; + public string controllerAssetPath = ""; + } + + [Serializable] + public class AnimatorSetControllerResponse + { + public string gameObjectName; + public string gameObjectPath; + public string controllerAssetPath; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/SetControllerHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: beac5aca2f40d4254ab7cef4c58e4d12 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/SetParameterHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Globalization; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorSetParameterHandler + : CommandHandler + { + public override string CommandName => "Animator.SetParameter"; + public override string Description => "Set an Animator parameter value (requires PlayMode)"; + + protected override bool TryWriteFormatted(AnimatorSetParameterResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine( + $"Set parameter \"{response.name}\" = {response.value} on \"{response.gameObjectName}\""); + else + writer.WriteLine("Failed to set parameter"); + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorSetParameterRequest request, CancellationToken cancellationToken) + { + if (!EditorApplication.isPlaying) + { + throw new CommandFailedException( + "This command requires PlayMode", + new AnimatorSetParameterResponse()); + } + + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + if (string.IsNullOrEmpty(request.value)) + throw new ArgumentException("value is required"); + + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new AnimatorSetParameterResponse()); + } + + var animator = go.GetComponent(); + if (animator == null) + { + throw new CommandFailedException( + $"Animator component not found on \"{go.name}\"", + new AnimatorSetParameterResponse()); + } + + var param = FindParameter(animator, request.name); + if (param == null) + { + throw new CommandFailedException( + $"Parameter \"{request.name}\" not found", + new AnimatorSetParameterResponse()); + } + + switch (param.type) + { + case AnimatorControllerParameterType.Float: + if (!float.TryParse(request.value, NumberStyles.Float, CultureInfo.InvariantCulture, + out var f)) + throw new CommandFailedException($"Invalid float value \"{request.value}\"", + new AnimatorSetParameterResponse()); + animator.SetFloat(request.name, f); + break; + case AnimatorControllerParameterType.Int: + if (!int.TryParse(request.value, NumberStyles.Integer, CultureInfo.InvariantCulture, + out var n)) + throw new CommandFailedException($"Invalid int value \"{request.value}\"", + new AnimatorSetParameterResponse()); + animator.SetInteger(request.name, n); + break; + case AnimatorControllerParameterType.Bool: + if (!bool.TryParse(request.value, out var b)) + throw new CommandFailedException($"Invalid bool value \"{request.value}\"", + new AnimatorSetParameterResponse()); + animator.SetBool(request.name, b); + break; + case AnimatorControllerParameterType.Trigger: + animator.SetTrigger(request.name); + break; + } + + return new ValueTask(new AnimatorSetParameterResponse + { + gameObjectName = go.name, + name = request.name, + type = param.type.ToString(), + value = request.value + }); + } + + private static AnimatorControllerParameter FindParameter(UnityEngine.Animator animator, string name) + { + for (var i = 0; i < animator.parameterCount; i++) + { + var p = animator.GetParameter(i); + if (p.name == name) + return p; + } + + return null; + } + } + + [Serializable] + public class AnimatorSetParameterRequest + { + public int instanceId; + public string path = ""; + public string name = ""; + public string value = ""; + } + + [Serializable] + public class AnimatorSetParameterResponse + { + public string gameObjectName; + public string name; + public string type; + public string value; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Animator/SetParameterHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: e8cb5044731754d36b15ae7ceb6dc3b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController.meta`: + +```meta +fileFormatVersion: 2 +guid: 4c851d0ff3f1b47eb8a71a6dac0393cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddParameterHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorControllerAddParameterHandler + : CommandHandler + { + public override string CommandName => "AnimatorController.AddParameter"; + public override string Description => "Add a parameter to an AnimatorController"; + + protected override bool TryWriteFormatted(AnimatorControllerAddParameterResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine($"Added parameter \"{response.name}\" ({response.type}) to {response.assetPath}"); + else + writer.WriteLine("Failed to add parameter"); + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorControllerAddParameterRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + if (string.IsNullOrEmpty(request.type)) + throw new ArgumentException("type is required"); + + if (!Enum.TryParse(request.type, true, out var paramType)) + { + throw new CommandFailedException( + $"Invalid parameter type \"{request.type}\". Valid types: Float, Int, Bool, Trigger", + new AnimatorControllerAddParameterResponse()); + } + + var controller = AnimatorControllerResolver.Resolve(request.assetPath); + + foreach (var existing in controller.parameters) + { + if (existing.name == request.name) + { + throw new CommandFailedException( + $"Parameter \"{request.name}\" already exists", + new AnimatorControllerAddParameterResponse()); + } + } + + controller.AddParameter(request.name, paramType); + EditorUtility.SetDirty(controller); + AssetDatabase.SaveAssets(); + + return new ValueTask( + new AnimatorControllerAddParameterResponse + { + assetPath = request.assetPath, + name = request.name, + type = paramType.ToString(), + parameterCount = controller.parameters.Length + }); + } + } + + [Serializable] + public class AnimatorControllerAddParameterRequest + { + public string assetPath = ""; + public string name = ""; + public string type = ""; + } + + [Serializable] + public class AnimatorControllerAddParameterResponse + { + public string assetPath; + public string name; + public string type; + public int parameterCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddParameterHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 42540e7ea260c4d619de6e61f1d93c9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddStateHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorControllerAddStateHandler + : CommandHandler + { + public override string CommandName => "AnimatorController.AddState"; + public override string Description => "Add a state to an AnimatorController layer"; + + protected override bool TryWriteFormatted(AnimatorControllerAddStateResponse response, bool success, + IFormatWriter writer) + { + if (success) + { + var motion = string.IsNullOrEmpty(response.motionName) ? "" : $" (motion={response.motionName})"; + writer.WriteLine( + $"Added state \"{response.name}\" to layer {response.layerIndex} of {response.assetPath}{motion}"); + } + else + { + writer.WriteLine("Failed to add state"); + } + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorControllerAddStateRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var controller = AnimatorControllerResolver.Resolve(request.assetPath); + + if (request.layerIndex < 0 || request.layerIndex >= controller.layers.Length) + { + throw new CommandFailedException( + $"Layer index {request.layerIndex} is out of range (0..{controller.layers.Length - 1})", + new AnimatorControllerAddStateResponse()); + } + + var stateMachine = controller.layers[request.layerIndex].stateMachine; + + foreach (var childState in stateMachine.states) + { + if (childState.state.name == request.name) + { + throw new CommandFailedException( + $"State \"{request.name}\" already exists in layer {request.layerIndex}", + new AnimatorControllerAddStateResponse()); + } + } + + var state = stateMachine.AddState(request.name); + + if (!string.IsNullOrEmpty(request.motionAssetPath)) + { + var motion = AssetDatabase.LoadAssetAtPath(request.motionAssetPath); + if (motion == null) + { + throw new CommandFailedException( + $"Motion asset not found at \"{request.motionAssetPath}\"", + new AnimatorControllerAddStateResponse()); + } + + state.motion = motion; + } + + EditorUtility.SetDirty(controller); + AssetDatabase.SaveAssets(); + + return new ValueTask(new AnimatorControllerAddStateResponse + { + assetPath = request.assetPath, + name = state.name, + layerIndex = request.layerIndex, + motionName = state.motion != null ? state.motion.name : "", + stateCount = stateMachine.states.Length + }); + } + } + + [Serializable] + public class AnimatorControllerAddStateRequest + { + public string assetPath = ""; + public string name = ""; + public int layerIndex; + public string motionAssetPath = ""; + } + + [Serializable] + public class AnimatorControllerAddStateResponse + { + public string assetPath; + public string name; + public int layerIndex; + public string motionName; + public int stateCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddStateHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d18f344f65806495db59129cd8a2f159 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddTransitionConditionHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Animations; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorControllerAddTransitionConditionHandler + : CommandHandler + { + public override string CommandName => "AnimatorController.AddTransitionCondition"; + + public override string Description => + "Add a condition to a transition between two states in an AnimatorController"; + + protected override bool TryWriteFormatted(AnimatorControllerAddTransitionConditionResponse response, + bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine( + $"Added condition ({response.parameter} {response.mode} {response.threshold}) to transition {response.sourceStateName} -> {response.destinationStateName}"); + else + writer.WriteLine("Failed to add transition condition"); + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorControllerAddTransitionConditionRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.sourceStateName)) + throw new ArgumentException("sourceStateName is required"); + + if (string.IsNullOrEmpty(request.destinationStateName)) + throw new ArgumentException("destinationStateName is required"); + + if (string.IsNullOrEmpty(request.parameter)) + throw new ArgumentException("parameter is required"); + + if (string.IsNullOrEmpty(request.mode)) + throw new ArgumentException("mode is required"); + + if (!Enum.TryParse(request.mode, true, out var conditionMode)) + { + throw new CommandFailedException( + $"Invalid condition mode \"{request.mode}\". Valid modes: If, IfNot, Greater, Less, Equals, NotEqual", + new AnimatorControllerAddTransitionConditionResponse()); + } + + var controller = AnimatorControllerResolver.Resolve(request.assetPath); + + if (request.layerIndex < 0 || request.layerIndex >= controller.layers.Length) + { + throw new CommandFailedException( + $"Layer index {request.layerIndex} is out of range (0..{controller.layers.Length - 1})", + new AnimatorControllerAddTransitionConditionResponse()); + } + + var stateMachine = controller.layers[request.layerIndex].stateMachine; + + var sourceState = FindState(stateMachine, request.sourceStateName); + if (sourceState == null) + { + throw new CommandFailedException( + $"Source state \"{request.sourceStateName}\" not found in layer {request.layerIndex}", + new AnimatorControllerAddTransitionConditionResponse()); + } + + var transition = FindTransition(sourceState, request.destinationStateName, request.transitionIndex); + if (transition == null) + { + throw new CommandFailedException( + $"Transition from \"{request.sourceStateName}\" to \"{request.destinationStateName}\" not found", + new AnimatorControllerAddTransitionConditionResponse()); + } + + transition.AddCondition(conditionMode, request.threshold, request.parameter); + + EditorUtility.SetDirty(controller); + AssetDatabase.SaveAssets(); + + return new ValueTask( + new AnimatorControllerAddTransitionConditionResponse + { + assetPath = request.assetPath, + sourceStateName = request.sourceStateName, + destinationStateName = request.destinationStateName, + parameter = request.parameter, + mode = conditionMode.ToString(), + threshold = request.threshold, + conditionCount = transition.conditions.Length + }); + } + + private static AnimatorState FindState(AnimatorStateMachine stateMachine, string name) + { + foreach (var childState in stateMachine.states) + { + if (childState.state.name == name) + return childState.state; + } + + return null; + } + + private static AnimatorStateTransition FindTransition(AnimatorState sourceState, + string destinationStateName, int transitionIndex) + { + var matchIndex = 0; + foreach (var transition in sourceState.transitions) + { + if (transition.destinationState != null && + transition.destinationState.name == destinationStateName) + { + if (matchIndex == transitionIndex) + return transition; + matchIndex++; + } + } + + return null; + } + } + + [Serializable] + public class AnimatorControllerAddTransitionConditionRequest + { + public string assetPath = ""; + public string sourceStateName = ""; + public string destinationStateName = ""; + public int layerIndex; + public int transitionIndex; + public string parameter = ""; + public string mode = ""; + public float threshold; + } + + [Serializable] + public class AnimatorControllerAddTransitionConditionResponse + { + public string assetPath; + public string sourceStateName; + public string destinationStateName; + public string parameter; + public string mode; + public float threshold; + public int conditionCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddTransitionConditionHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: abc50439cdce4463fbbc1f921aff6b5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddTransitionHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Animations; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorControllerAddTransitionHandler + : CommandHandler + { + public override string CommandName => "AnimatorController.AddTransition"; + public override string Description => "Add a transition between two states in an AnimatorController"; + + protected override bool TryWriteFormatted(AnimatorControllerAddTransitionResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine( + $"Added transition {response.sourceStateName} -> {response.destinationStateName} in {response.assetPath}"); + else + writer.WriteLine("Failed to add transition"); + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorControllerAddTransitionRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.sourceStateName)) + throw new ArgumentException("sourceStateName is required"); + + if (string.IsNullOrEmpty(request.destinationStateName)) + throw new ArgumentException("destinationStateName is required"); + + var controller = AnimatorControllerResolver.Resolve(request.assetPath); + + if (request.layerIndex < 0 || request.layerIndex >= controller.layers.Length) + { + throw new CommandFailedException( + $"Layer index {request.layerIndex} is out of range (0..{controller.layers.Length - 1})", + new AnimatorControllerAddTransitionResponse()); + } + + var stateMachine = controller.layers[request.layerIndex].stateMachine; + + var sourceState = FindState(stateMachine, request.sourceStateName); + if (sourceState == null) + { + throw new CommandFailedException( + $"Source state \"{request.sourceStateName}\" not found in layer {request.layerIndex}", + new AnimatorControllerAddTransitionResponse()); + } + + var destinationState = FindState(stateMachine, request.destinationStateName); + if (destinationState == null) + { + throw new CommandFailedException( + $"Destination state \"{request.destinationStateName}\" not found in layer {request.layerIndex}", + new AnimatorControllerAddTransitionResponse()); + } + + var transition = sourceState.AddTransition(destinationState); + transition.hasExitTime = request.hasExitTime; + transition.exitTime = request.exitTime; + transition.duration = request.duration; + + EditorUtility.SetDirty(controller); + AssetDatabase.SaveAssets(); + + return new ValueTask( + new AnimatorControllerAddTransitionResponse + { + assetPath = request.assetPath, + sourceStateName = request.sourceStateName, + destinationStateName = request.destinationStateName, + layerIndex = request.layerIndex, + hasExitTime = transition.hasExitTime, + exitTime = transition.exitTime, + duration = transition.duration + }); + } + + private static AnimatorState FindState(AnimatorStateMachine stateMachine, string name) + { + foreach (var childState in stateMachine.states) + { + if (childState.state.name == name) + return childState.state; + } + + return null; + } + } + + [Serializable] + public class AnimatorControllerAddTransitionRequest + { + public string assetPath = ""; + public string sourceStateName = ""; + public string destinationStateName = ""; + public int layerIndex; + public bool hasExitTime; + public float exitTime = 0.9f; + public float duration = 0.25f; + } + + [Serializable] + public class AnimatorControllerAddTransitionResponse + { + public string assetPath; + public string sourceStateName; + public string destinationStateName; + public int layerIndex; + public bool hasExitTime; + public float exitTime; + public float duration; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AddTransitionHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8fa19a11b67584b4aad4511640332957 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AnimatorControllerResolver.cs`: + +```cs +using System; +using UnityEditor; +using UnityEditor.Animations; + +namespace UniCli.Server.Editor.Handlers +{ + internal static class AnimatorControllerResolver + { + public static UnityEditor.Animations.AnimatorController Resolve(string assetPath) + { + if (string.IsNullOrEmpty(assetPath)) + throw new ArgumentException("assetPath is required"); + + var controller = AssetDatabase.LoadAssetAtPath(assetPath); + if (controller == null) + { + throw new CommandFailedException( + $"AnimatorController not found at \"{assetPath}\"", + new { assetPath }); + } + + return controller; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/AnimatorControllerResolver.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ffa0fc8640dcc4616b65cedb0eb37390 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/CreateHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor.Animations; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorControllerCreateHandler + : CommandHandler + { + public override string CommandName => "AnimatorController.Create"; + public override string Description => "Create a new AnimatorController asset"; + + protected override bool TryWriteFormatted(AnimatorControllerCreateResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine($"Created AnimatorController at {response.assetPath}"); + else + writer.WriteLine("Failed to create AnimatorController"); + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorControllerCreateRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.assetPath)) + throw new ArgumentException("assetPath is required"); + + if (!request.assetPath.EndsWith(".controller")) + { + throw new CommandFailedException( + $"assetPath must end with .controller (got \"{request.assetPath}\")", + new AnimatorControllerCreateResponse()); + } + + var controller = AnimatorController.CreateAnimatorControllerAtPath(request.assetPath); + if (controller == null) + { + throw new CommandFailedException( + $"Failed to create AnimatorController at \"{request.assetPath}\"", + new AnimatorControllerCreateResponse()); + } + + return new ValueTask(new AnimatorControllerCreateResponse + { + assetPath = request.assetPath, + layerCount = controller.layers.Length, + parameterCount = controller.parameters.Length + }); + } + } + + [Serializable] + public class AnimatorControllerCreateRequest + { + public string assetPath = ""; + } + + [Serializable] + public class AnimatorControllerCreateResponse + { + public string assetPath; + public int layerCount; + public int parameterCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/CreateHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d3243629cbb3f4e439477c96438a6199 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/InspectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorControllerInspectHandler + : CommandHandler + { + public override string CommandName => "AnimatorController.Inspect"; + public override string Description => "Inspect an AnimatorController asset (layers, parameters, states)"; + + protected override bool TryWriteFormatted(AnimatorControllerInspectResponse response, bool success, + IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to inspect AnimatorController"); + return true; + } + + writer.WriteLine($"AnimatorController: {response.assetPath}"); + + if (response.parameters?.Length > 0) + { + writer.WriteLine($"Parameters ({response.parameters.Length}):"); + foreach (var p in response.parameters) + writer.WriteLine($" {p.name} ({p.type}) = {FormatDefaultValue(p)}"); + } + + if (response.layers?.Length > 0) + { + foreach (var layer in response.layers) + { + writer.WriteLine($"Layer: {layer.name} (weight={layer.defaultWeight})"); + if (layer.states?.Length > 0) + { + foreach (var state in layer.states) + { + var defaultMarker = state.isDefault ? " [default]" : ""; + writer.WriteLine( + $" State: {state.name}{defaultMarker} motion={state.motionName} speed={state.speed} transitions={state.transitionCount}"); + } + } + } + } + + return true; + } + + private static string FormatDefaultValue(AnimatorParameterInfo p) + { + return p.type switch + { + "Float" => p.defaultFloat.ToString("F2"), + "Int" => p.defaultInt.ToString(), + "Bool" => p.defaultBool.ToString(), + "Trigger" => "(trigger)", + _ => "?" + }; + } + + protected override ValueTask ExecuteAsync( + AnimatorControllerInspectRequest request, CancellationToken cancellationToken) + { + var controller = AnimatorControllerResolver.Resolve(request.assetPath); + + var parameters = new List(); + foreach (var param in controller.parameters) + { + parameters.Add(new AnimatorParameterInfo + { + name = param.name, + type = param.type.ToString(), + defaultFloat = param.defaultFloat, + defaultInt = param.defaultInt, + defaultBool = param.defaultBool + }); + } + + var layers = new List(); + foreach (var layer in controller.layers) + { + var states = new List(); + var defaultState = layer.stateMachine.defaultState; + + foreach (var childState in layer.stateMachine.states) + { + var state = childState.state; + states.Add(new AnimatorStateInfo + { + name = state.name, + motionName = state.motion != null ? state.motion.name : "(none)", + isDefault = defaultState == state, + speed = state.speed, + transitionCount = state.transitions.Length + }); + } + + layers.Add(new AnimatorLayerInfo + { + name = layer.name, + defaultWeight = layer.defaultWeight, + blendingMode = layer.blendingMode.ToString(), + states = states.ToArray() + }); + } + + return new ValueTask(new AnimatorControllerInspectResponse + { + assetPath = request.assetPath, + parameters = parameters.ToArray(), + layers = layers.ToArray() + }); + } + } + + [Serializable] + public class AnimatorControllerInspectRequest + { + public string assetPath = ""; + } + + [Serializable] + public class AnimatorControllerInspectResponse + { + public string assetPath; + public AnimatorParameterInfo[] parameters; + public AnimatorLayerInfo[] layers; + } + + [Serializable] + public class AnimatorParameterInfo + { + public string name; + public string type; + public float defaultFloat; + public int defaultInt; + public bool defaultBool; + } + + [Serializable] + public class AnimatorLayerInfo + { + public string name; + public float defaultWeight; + public string blendingMode; + public AnimatorStateInfo[] states; + } + + [Serializable] + public class AnimatorStateInfo + { + public string name; + public string motionName; + public bool isDefault; + public float speed; + public int transitionCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/InspectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b7f07b8e0507b4a2e804afc4d46ba7ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/RemoveParameterHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Animation")] + public sealed class AnimatorControllerRemoveParameterHandler + : CommandHandler + { + public override string CommandName => "AnimatorController.RemoveParameter"; + public override string Description => "Remove a parameter from an AnimatorController"; + + protected override bool TryWriteFormatted(AnimatorControllerRemoveParameterResponse response, bool success, + IFormatWriter writer) + { + if (success) + writer.WriteLine($"Removed parameter \"{response.name}\" from {response.assetPath}"); + else + writer.WriteLine("Failed to remove parameter"); + + return true; + } + + protected override ValueTask ExecuteAsync( + AnimatorControllerRemoveParameterRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var controller = AnimatorControllerResolver.Resolve(request.assetPath); + + var index = -1; + for (var i = 0; i < controller.parameters.Length; i++) + { + if (controller.parameters[i].name == request.name) + { + index = i; + break; + } + } + + if (index < 0) + { + throw new CommandFailedException( + $"Parameter \"{request.name}\" not found in \"{request.assetPath}\"", + new AnimatorControllerRemoveParameterResponse()); + } + + controller.RemoveParameter(index); + EditorUtility.SetDirty(controller); + AssetDatabase.SaveAssets(); + + return new ValueTask( + new AnimatorControllerRemoveParameterResponse + { + assetPath = request.assetPath, + name = request.name, + parameterCount = controller.parameters.Length + }); + } + } + + [Serializable] + public class AnimatorControllerRemoveParameterRequest + { + public string assetPath = ""; + public string name = ""; + } + + [Serializable] + public class AnimatorControllerRemoveParameterResponse + { + public string assetPath; + public string name; + public int parameterCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AnimatorController/RemoveParameterHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b2d20de984ccd4d4f98fde4b2339f0e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition.meta`: + +```meta +fileFormatVersion: 2 +guid: a80b5920b82e1454e85e79b3c07ee783 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionAddReferenceHandler.cs`: + +```cs +using System.Threading; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class AssemblyDefinitionAddReferenceHandler : CommandHandler + { + public override string CommandName => "AssemblyDefinition.AddReference"; + public override string Description => "Add an assembly reference to an existing assembly definition"; + + protected override bool TryWriteFormatted(AssemblyDefinitionAddReferenceResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine($"Added reference '{response.addedReference}' to {response.name} ({response.references.Length} references total)"); + return true; + } + + protected override ValueTask ExecuteAsync(AssemblyDefinitionAddReferenceRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("Assembly definition name is required"); + + if (string.IsNullOrEmpty(request.reference)) + throw new ArgumentException("Reference assembly name is required"); + + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(request.name); + if (string.IsNullOrEmpty(asmdefPath)) + throw new ArgumentException($"Assembly definition not found: {request.name}"); + + var json = File.ReadAllText(asmdefPath); + var data = JsonUtility.FromJson(json); + + var guidReference = ConvertToGuidReference(request.reference); + + var existingReferences = data.references ?? Array.Empty(); + if (existingReferences.Contains(guidReference)) + throw new ArgumentException($"Reference '{request.reference}' already exists in {request.name}"); + + // Also check by name in case existing refs use name format + if (existingReferences.Contains(request.reference)) + throw new ArgumentException($"Reference '{request.reference}' already exists in {request.name}"); + + var newReferences = new string[existingReferences.Length + 1]; + Array.Copy(existingReferences, newReferences, existingReferences.Length); + newReferences[existingReferences.Length] = guidReference; + data.references = newReferences; + + var updatedJson = JsonUtility.ToJson(data, true); + File.WriteAllText(asmdefPath, updatedJson); + + AssetDatabase.ImportAsset(asmdefPath); + + var resolvedReferences = ResolveReferenceNames(newReferences); + + return new ValueTask(new AssemblyDefinitionAddReferenceResponse + { + name = request.name, + path = asmdefPath, + addedReference = request.reference, + references = resolvedReferences + }); + } + + private static string ConvertToGuidReference(string assemblyName) + { + if (assemblyName.StartsWith("GUID:")) + return assemblyName; + + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(assemblyName); + if (string.IsNullOrEmpty(asmdefPath)) + throw new ArgumentException($"Referenced assembly definition not found: {assemblyName}"); + + var guid = AssetDatabase.AssetPathToGUID(asmdefPath); + if (string.IsNullOrEmpty(guid)) + throw new ArgumentException($"Could not resolve GUID for assembly: {assemblyName}"); + + return $"GUID:{guid}"; + } + + private static string[] ResolveReferenceNames(string[] references) + { + var resolved = new string[references.Length]; + for (var i = 0; i < references.Length; i++) + { + var r = references[i]; + if (r.StartsWith("GUID:")) + { + var guid = r.Substring(5); + var path = AssetDatabase.GUIDToAssetPath(guid); + if (!string.IsNullOrEmpty(path)) + { + try + { + var refJson = File.ReadAllText(path); + var refData = JsonUtility.FromJson(refJson); + if (refData != null && !string.IsNullOrEmpty(refData.name)) + { + resolved[i] = refData.name; + continue; + } + } + catch + { + // Fall through to raw value + } + } + } + + resolved[i] = r; + } + + return resolved; + } + } + + [Serializable] + public class AssemblyDefinitionAddReferenceRequest + { + public string name; + public string reference; + } + + [Serializable] + public class AssemblyDefinitionAddReferenceResponse + { + public string name; + public string path; + public string addedReference; + public string[] references; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionAddReferenceHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 0b6598a3df64e4d12aad512b04998944 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionCreateHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class AssemblyDefinitionCreateHandler : CommandHandler + { + public override string CommandName => "AssemblyDefinition.Create"; + public override string Description => "Create a new assembly definition file"; + + protected override bool TryWriteFormatted(AssemblyDefinitionCreateResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine($"Created {response.path}"); + return true; + } + + protected override ValueTask ExecuteAsync(AssemblyDefinitionCreateRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("Assembly definition name is required"); + + if (string.IsNullOrEmpty(request.directory)) + throw new ArgumentException("Directory is required"); + + var references = ConvertToGuidReferences(request.references ?? Array.Empty()); + + var data = new AssemblyDefinitionData + { + name = request.name, + rootNamespace = request.rootNamespace ?? "", + references = references, + includePlatforms = request.includePlatforms ?? Array.Empty(), + excludePlatforms = request.excludePlatforms ?? Array.Empty(), + allowUnsafeCode = request.allowUnsafeCode, + autoReferenced = request.autoReferenced, + defineConstraints = request.defineConstraints ?? Array.Empty(), + noEngineReferences = request.noEngineReferences + }; + + Directory.CreateDirectory(request.directory); + + var filePath = Path.Combine(request.directory, $"{request.name}.asmdef"); + var json = JsonUtility.ToJson(data, true); + File.WriteAllText(filePath, json); + + AssetDatabase.ImportAsset(filePath); + + return new ValueTask(new AssemblyDefinitionCreateResponse + { + name = request.name, + path = filePath + }); + } + + private static string[] ConvertToGuidReferences(string[] assemblyNames) + { + var result = new List(assemblyNames.Length); + foreach (var name in assemblyNames) + { + if (name.StartsWith("GUID:")) + { + result.Add(name); + continue; + } + + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(name); + if (string.IsNullOrEmpty(asmdefPath)) + throw new ArgumentException($"Referenced assembly definition not found: {name}"); + + var guid = AssetDatabase.AssetPathToGUID(asmdefPath); + if (string.IsNullOrEmpty(guid)) + throw new ArgumentException($"Could not resolve GUID for assembly: {name}"); + + result.Add($"GUID:{guid}"); + } + + return result.ToArray(); + } + } + + [Serializable] + public class AssemblyDefinitionCreateRequest + { + public string name; + public string directory; + public string rootNamespace = ""; + public string[] references; + public string[] includePlatforms; + public string[] excludePlatforms; + public bool allowUnsafeCode; + public bool autoReferenced = true; + public string[] defineConstraints; + public bool noEngineReferences; + } + + [Serializable] + public class AssemblyDefinitionCreateResponse + { + public string name; + public string path; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionCreateHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 742ce2b0857134d2288d2870ad077ffd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionData.cs`: + +```cs +using System; + +namespace UniCli.Server.Editor.Handlers +{ + [Serializable] + public class AssemblyDefinitionData + { + public string name = ""; + public string rootNamespace = ""; + public string[] references = Array.Empty(); + public string[] includePlatforms = Array.Empty(); + public string[] excludePlatforms = Array.Empty(); + public bool allowUnsafeCode; + public bool overrideReferences; + public string[] precompiledReferences = Array.Empty(); + public bool autoReferenced = true; + public string[] defineConstraints = Array.Empty(); + public VersionDefineEntry[] versionDefines = Array.Empty(); + public bool noEngineReferences; + } + + [Serializable] + public class VersionDefineEntry + { + public string name; + public string expression; + public string define; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionData.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c85de0777653d4726956657cba1e48a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionGetHandler.cs`: + +```cs +using System.Threading; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class AssemblyDefinitionGetHandler : CommandHandler + { + public override string CommandName => "AssemblyDefinition.Get"; + public override string Description => "Get detailed information about a specific assembly definition"; + + protected override bool TryWriteFormatted(AssemblyDefinitionGetResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine($"Name: {response.name}"); + writer.WriteLine($"Path: {response.path}"); + writer.WriteLine($"Root Namespace: {response.rootNamespace}"); + writer.WriteLine($"Allow Unsafe: {response.allowUnsafeCode}"); + writer.WriteLine($"Auto Referenced: {response.autoReferenced}"); + writer.WriteLine($"No Engine Refs: {response.noEngineReferences}"); + + if (response.references.Length > 0) + { + writer.WriteLine($"References ({response.references.Length}):"); + foreach (var r in response.references) + writer.WriteLine($" - {r}"); + } + + if (response.includePlatforms.Length > 0) + { + writer.WriteLine($"Include Platforms ({response.includePlatforms.Length}):"); + foreach (var p in response.includePlatforms) + writer.WriteLine($" - {p}"); + } + + if (response.excludePlatforms.Length > 0) + { + writer.WriteLine($"Exclude Platforms ({response.excludePlatforms.Length}):"); + foreach (var p in response.excludePlatforms) + writer.WriteLine($" - {p}"); + } + + if (response.defineConstraints.Length > 0) + { + writer.WriteLine($"Define Constraints ({response.defineConstraints.Length}):"); + foreach (var d in response.defineConstraints) + writer.WriteLine($" - {d}"); + } + + if (response.defines.Length > 0) + { + writer.WriteLine($"Defines ({response.defines.Length}):"); + foreach (var d in response.defines) + writer.WriteLine($" - {d}"); + } + + writer.WriteLine($"Source Files: {response.sourceFiles.Length}"); + + return true; + } + + protected override ValueTask ExecuteAsync(AssemblyDefinitionGetRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("Assembly definition name is required"); + + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(request.name); + if (string.IsNullOrEmpty(asmdefPath)) + throw new ArgumentException($"Assembly definition not found: {request.name}"); + + var json = File.ReadAllText(asmdefPath); + var data = JsonUtility.FromJson(json); + + var references = ResolveReferenceNames(data.references ?? Array.Empty()); + + var sourceFiles = Array.Empty(); + var defines = Array.Empty(); + + var assemblies = CompilationPipeline.GetAssemblies(AssembliesType.Editor); + var targetAssembly = assemblies.FirstOrDefault(a => a.name == request.name); + if (targetAssembly != null) + { + sourceFiles = targetAssembly.sourceFiles ?? Array.Empty(); + defines = targetAssembly.defines ?? Array.Empty(); + } + + return new ValueTask(new AssemblyDefinitionGetResponse + { + name = data.name, + path = asmdefPath, + rootNamespace = data.rootNamespace ?? "", + references = references, + includePlatforms = data.includePlatforms ?? Array.Empty(), + excludePlatforms = data.excludePlatforms ?? Array.Empty(), + allowUnsafeCode = data.allowUnsafeCode, + autoReferenced = data.autoReferenced, + defineConstraints = data.defineConstraints ?? Array.Empty(), + noEngineReferences = data.noEngineReferences, + sourceFiles = sourceFiles, + defines = defines + }); + } + + private static string[] ResolveReferenceNames(string[] references) + { + var resolved = new string[references.Length]; + for (var i = 0; i < references.Length; i++) + { + var r = references[i]; + if (r.StartsWith("GUID:")) + { + var guid = r.Substring(5); + var path = AssetDatabase.GUIDToAssetPath(guid); + if (!string.IsNullOrEmpty(path)) + { + try + { + var refJson = File.ReadAllText(path); + var refData = JsonUtility.FromJson(refJson); + if (refData != null && !string.IsNullOrEmpty(refData.name)) + { + resolved[i] = refData.name; + continue; + } + } + catch + { + // Fall through to raw value + } + } + } + + resolved[i] = r; + } + + return resolved; + } + } + + [Serializable] + public class AssemblyDefinitionGetRequest + { + public string name; + } + + [Serializable] + public class AssemblyDefinitionGetResponse + { + public string name; + public string path; + public string rootNamespace; + public string[] references; + public string[] includePlatforms; + public string[] excludePlatforms; + public bool allowUnsafeCode; + public bool autoReferenced; + public string[] defineConstraints; + public bool noEngineReferences; + public string[] sourceFiles; + public string[] defines; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionGetHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 7992a987caf73453ab3d9d7eb3578852 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionListHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor.Compilation; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class AssemblyDefinitionListHandler : CommandHandler + { + public override string CommandName => "AssemblyDefinition.List"; + public override string Description => "List all assembly definitions in the project"; + + protected override bool TryWriteFormatted(AssemblyDefinitionListResponse response, bool success, IFormatWriter writer) + { + var nameWidth = "Name".Length; + var pathWidth = "Path".Length; + var nsWidth = "Namespace".Length; + var srcWidth = "Sources".Length; + var refWidth = "Refs".Length; + + foreach (var entry in response.assemblies) + { + nameWidth = Math.Max(nameWidth, entry.name.Length); + pathWidth = Math.Max(pathWidth, entry.path.Length); + nsWidth = Math.Max(nsWidth, entry.rootNamespace.Length); + srcWidth = Math.Max(srcWidth, entry.sourceFileCount.ToString().Length); + refWidth = Math.Max(refWidth, entry.referenceCount.ToString().Length); + } + + writer.WriteLine( + $"{"Name".PadRight(nameWidth)} {"Path".PadRight(pathWidth)} {"Namespace".PadRight(nsWidth)} {"Sources".PadRight(srcWidth)} {"Refs".PadRight(refWidth)}"); + + foreach (var entry in response.assemblies) + { + writer.WriteLine( + $"{entry.name.PadRight(nameWidth)} {entry.path.PadRight(pathWidth)} {entry.rootNamespace.PadRight(nsWidth)} {entry.sourceFileCount.ToString().PadRight(srcWidth)} {entry.referenceCount.ToString().PadRight(refWidth)}"); + } + + writer.WriteLine($"{response.totalCount} assembly definition(s)"); + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var assemblies = CompilationPipeline.GetAssemblies(AssembliesType.Editor); + var entries = new List(); + + foreach (var assembly in assemblies) + { + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(assembly.name); + if (string.IsNullOrEmpty(asmdefPath)) + continue; + + var rootNamespace = ""; + var referenceCount = 0; + + try + { + var json = File.ReadAllText(asmdefPath); + var data = JsonUtility.FromJson(json); + if (data != null) + { + rootNamespace = data.rootNamespace ?? ""; + referenceCount = data.references?.Length ?? 0; + } + } + catch + { + // JSON parse failure — use defaults + } + + entries.Add(new AssemblyDefinitionEntry + { + name = assembly.name, + path = asmdefPath, + rootNamespace = rootNamespace, + sourceFileCount = assembly.sourceFiles?.Length ?? 0, + referenceCount = referenceCount + }); + } + + var sorted = entries.OrderBy(e => e.name).ToArray(); + + return new ValueTask(new AssemblyDefinitionListResponse + { + assemblies = sorted, + totalCount = sorted.Length + }); + } + } + + [Serializable] + public class AssemblyDefinitionListResponse + { + public AssemblyDefinitionEntry[] assemblies; + public int totalCount; + } + + [Serializable] + public class AssemblyDefinitionEntry + { + public string name; + public string path; + public string rootNamespace; + public int sourceFileCount; + public int referenceCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8df0fe5a77399471aa2466bfa3cf6b83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionRemoveReferenceHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class AssemblyDefinitionRemoveReferenceHandler : CommandHandler + { + public override string CommandName => "AssemblyDefinition.RemoveReference"; + public override string Description => "Remove an assembly reference from an existing assembly definition"; + + protected override bool TryWriteFormatted(AssemblyDefinitionRemoveReferenceResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine($"Removed reference '{response.removedReference}' from {response.name} ({response.references.Length} references total)"); + return true; + } + + protected override ValueTask ExecuteAsync(AssemblyDefinitionRemoveReferenceRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("Assembly definition name is required"); + + if (string.IsNullOrEmpty(request.reference)) + throw new ArgumentException("Reference assembly name is required"); + + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(request.name); + if (string.IsNullOrEmpty(asmdefPath)) + throw new ArgumentException($"Assembly definition not found: {request.name}"); + + var json = File.ReadAllText(asmdefPath); + var data = JsonUtility.FromJson(json); + + var existingReferences = data.references ?? Array.Empty(); + + var guidReference = TryConvertToGuidReference(request.reference); + var indexToRemove = FindReferenceIndex(existingReferences, request.reference, guidReference); + + if (indexToRemove < 0) + throw new ArgumentException($"Reference '{request.reference}' not found in {request.name}"); + + var newReferences = new List(existingReferences); + newReferences.RemoveAt(indexToRemove); + data.references = newReferences.ToArray(); + + var updatedJson = JsonUtility.ToJson(data, true); + File.WriteAllText(asmdefPath, updatedJson); + + AssetDatabase.ImportAsset(asmdefPath); + + var resolvedReferences = ResolveReferenceNames(data.references); + + return new ValueTask(new AssemblyDefinitionRemoveReferenceResponse + { + name = request.name, + path = asmdefPath, + removedReference = request.reference, + references = resolvedReferences + }); + } + + private static int FindReferenceIndex(string[] references, string name, string guidReference) + { + for (var i = 0; i < references.Length; i++) + { + if (references[i] == name) + return i; + + if (guidReference != null && references[i] == guidReference) + return i; + } + + return -1; + } + + private static string TryConvertToGuidReference(string assemblyName) + { + if (assemblyName.StartsWith("GUID:")) + return assemblyName; + + var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(assemblyName); + if (string.IsNullOrEmpty(asmdefPath)) + return null; + + var guid = AssetDatabase.AssetPathToGUID(asmdefPath); + if (string.IsNullOrEmpty(guid)) + return null; + + return $"GUID:{guid}"; + } + + private static string[] ResolveReferenceNames(string[] references) + { + var resolved = new string[references.Length]; + for (var i = 0; i < references.Length; i++) + { + var r = references[i]; + if (r.StartsWith("GUID:")) + { + var guid = r.Substring(5); + var path = AssetDatabase.GUIDToAssetPath(guid); + if (!string.IsNullOrEmpty(path)) + { + try + { + var refJson = File.ReadAllText(path); + var refData = JsonUtility.FromJson(refJson); + if (refData != null && !string.IsNullOrEmpty(refData.name)) + { + resolved[i] = refData.name; + continue; + } + } + catch + { + // Fall through to raw value + } + } + } + + resolved[i] = r; + } + + return resolved; + } + } + + [Serializable] + public class AssemblyDefinitionRemoveReferenceRequest + { + public string name; + public string reference; + } + + [Serializable] + public class AssemblyDefinitionRemoveReferenceResponse + { + public string name; + public string path; + public string removedReference; + public string[] references; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssemblyDefinition/AssemblyDefinitionRemoveReferenceHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 807dcd49b65e54e43b4414669d080649 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase.meta`: + +```meta +fileFormatVersion: 2 +guid: b3cdf4b5ffc3b487d951d611e3b12f47 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetDeleteHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class AssetDeleteHandler : CommandHandler + { + public override string CommandName => "AssetDatabase.Delete"; + public override string Description => "Delete an asset"; + + protected override ValueTask ExecuteAsync(AssetDeleteRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.path)) + throw new ArgumentException("path must be specified"); + + var assetType = AssetDatabase.GetMainAssetTypeAtPath(request.path); + if (assetType == null) + { + throw new CommandFailedException( + $"Asset not found: {request.path}", + new AssetDeleteResponse { path = request.path, type = "" }); + } + + var typeName = assetType.Name; + + if (!AssetDatabase.DeleteAsset(request.path)) + { + throw new CommandFailedException( + $"Failed to delete asset: {request.path}", + new AssetDeleteResponse { path = request.path, type = typeName }); + } + + return new ValueTask(new AssetDeleteResponse + { + path = request.path, + type = typeName + }); + } + } + + [Serializable] + public class AssetDeleteRequest + { + public string path = ""; + } + + [Serializable] + public class AssetDeleteResponse + { + public string path; + public string type; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetDeleteHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 02f8b4af9a31c4884b521d67a2fbbce9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetFindHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class AssetFindHandler : CommandHandler + { + public override string CommandName => "AssetDatabase.Find"; + public override string Description => "Find assets by filter (e.g. t:Texture, l:MyLabel)"; + + protected override ValueTask ExecuteAsync(AssetFindRequest request, CancellationToken cancellationToken) + { + var guids = request.searchInFolders != null && request.searchInFolders.Length > 0 + ? AssetDatabase.FindAssets(request.filter, request.searchInFolders) + : AssetDatabase.FindAssets(request.filter); + + var totalFound = guids.Length; + var count = Math.Min(guids.Length, request.maxResults); + var assets = new List(count); + + for (var i = 0; i < count; i++) + { + var guid = guids[i]; + var path = AssetDatabase.GUIDToAssetPath(guid); + var assetType = AssetDatabase.GetMainAssetTypeAtPath(path); + + assets.Add(new AssetInfo + { + guid = guid, + path = path, + type = assetType?.Name ?? "Unknown" + }); + } + + return new ValueTask(new AssetFindResponse + { + assets = assets.ToArray(), + totalFound = totalFound + }); + } + } + + [Serializable] + public class AssetFindRequest + { + public string filter = ""; + public string[] searchInFolders; + public int maxResults = 100; + } + + [Serializable] + public class AssetFindResponse + { + public AssetInfo[] assets; + public int totalFound; + } + + [Serializable] + public class AssetInfo + { + public string guid; + public string path; + public string type; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetFindHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetGetPathHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class AssetGetPathHandler : CommandHandler + { + public override string CommandName => "AssetDatabase.GetPath"; + public override string Description => "Convert between asset GUID and path"; + + protected override ValueTask ExecuteAsync(AssetGetPathRequest request, CancellationToken cancellationToken) + { + string guid; + string path; + + if (!string.IsNullOrEmpty(request.guid)) + { + guid = request.guid; + path = AssetDatabase.GUIDToAssetPath(guid); + } + else if (!string.IsNullOrEmpty(request.path)) + { + path = request.path; + guid = AssetDatabase.AssetPathToGUID(path); + } + else + { + throw new CommandFailedException( + "Either guid or path must be specified", + new AssetGetPathResponse { guid = "", path = "", type = "", exists = false }); + } + + var exists = !string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(guid); + var assetType = exists ? AssetDatabase.GetMainAssetTypeAtPath(path) : null; + + return new ValueTask(new AssetGetPathResponse + { + guid = guid ?? "", + path = path ?? "", + type = assetType?.Name ?? "", + exists = exists + }); + } + } + + [Serializable] + public class AssetGetPathRequest + { + public string guid = ""; + public string path = ""; + } + + [Serializable] + public class AssetGetPathResponse + { + public string guid; + public string path; + public string type; + public bool exists; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetGetPathHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetImportHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class AssetImportHandler : CommandHandler + { + public override string CommandName => "AssetDatabase.Import"; + public override string Description => "Reimport an asset or refresh the AssetDatabase"; + + protected override ValueTask ExecuteAsync(AssetImportRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.path)) + { + AssetDatabase.Refresh(); + return new ValueTask(new AssetImportResponse + { + path = "", + refreshed = true + }); + } + + var options = request.forceUpdate + ? ImportAssetOptions.ForceUpdate + : ImportAssetOptions.Default; + + AssetDatabase.ImportAsset(request.path, options); + + return new ValueTask(new AssetImportResponse + { + path = request.path, + refreshed = true + }); + } + } + + [Serializable] + public class AssetImportRequest + { + public string path = ""; + public bool forceUpdate; + } + + [Serializable] + public class AssetImportResponse + { + public string path; + public bool refreshed; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/AssetDatabase/AssetImportHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class BuildHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public BuildHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "BuildPlayer.Build"; + public override string Description => "Build the player using BuildPipeline.BuildPlayer"; + + protected override bool TryWriteFormatted(BuildResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Build succeeded for {response.target} → {response.locationPathName}"); + else + writer.WriteLine($"Build failed for {response.target} ({response.totalErrorCount} errors, {response.totalWarningCount} warnings)"); + + if (response.totalBuildTimeSec > 0) + writer.WriteLine($" Total time: {response.totalBuildTimeSec:F1}s"); + + if (response.totalSizeBytes > 0) + writer.WriteLine($" Total size: {FormatBytes(response.totalSizeBytes)}"); + + WriteMessages(writer, "Errors", response.errors); + WriteMessages(writer, "Warnings", response.warnings); + + return true; + } + + private static string FormatBytes(long bytes) + { + if (bytes >= 1024 * 1024 * 1024) + return $"{bytes / (1024.0 * 1024.0 * 1024.0):F2} GB"; + if (bytes >= 1024 * 1024) + return $"{bytes / (1024.0 * 1024.0):F2} MB"; + if (bytes >= 1024) + return $"{bytes / 1024.0:F2} KB"; + return $"{bytes} B"; + } + + private static void WriteMessages(IFormatWriter writer, string label, BuildMessageInfo[] messages) + { + if (messages == null || messages.Length == 0) return; + + writer.WriteLine($" {label}:"); + foreach (var msg in messages) + writer.WriteLine($" {msg.message}"); + } + + protected override ValueTask ExecuteAsync(BuildRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + if (string.IsNullOrEmpty(request.locationPathName)) + throw new ArgumentException("locationPathName is required."); + + var target = ResolveTarget(request.target); + var targetGroup = BuildPipeline.GetBuildTargetGroup(target); + var scenes = ResolveScenes(request.scenes); + var options = ResolveOptions(request.options); + + var buildPlayerOptions = new BuildPlayerOptions + { + scenes = scenes, + locationPathName = request.locationPathName, + target = target, + targetGroup = targetGroup, + options = options, + extraScriptingDefines = request.extraScriptingDefines ?? Array.Empty() + }; + + var report = BuildPipeline.BuildPlayer(buildPlayerOptions); + var response = MapResponse(report, target, targetGroup, request.locationPathName); + + if (report.summary.result != BuildResult.Succeeded) + throw new CommandFailedException($"Build failed for {target}: {report.summary.result}", response); + + return new ValueTask(response); + } + + private static BuildTarget ResolveTarget(string target) + { + if (string.IsNullOrEmpty(target)) + return EditorUserBuildSettings.activeBuildTarget; + + if (Enum.TryParse(target, true, out var parsed)) + return parsed; + + throw new ArgumentException($"Invalid build target: '{target}'. Use a valid BuildTarget name (e.g. Android, iOS, StandaloneWindows64)."); + } + + private static string[] ResolveScenes(string[] scenes) + { + if (scenes != null && scenes.Length > 0) + return scenes; + + return EditorBuildSettings.scenes + .Where(s => s.enabled) + .Select(s => s.path) + .ToArray(); + } + + private static BuildOptions ResolveOptions(string[] options) + { + if (options == null || options.Length == 0) + return BuildOptions.None; + + var result = BuildOptions.None; + foreach (var option in options) + { + if (Enum.TryParse(option, true, out var parsed)) + result |= parsed; + else + throw new ArgumentException($"Invalid build option: '{option}'. Use a valid BuildOptions name (e.g. Development, ConnectWithProfiler)."); + } + + return result; + } + + private static BuildResponse MapResponse(BuildReport report, BuildTarget target, BuildTargetGroup targetGroup, string locationPathName) + { + var errors = new List(); + var warnings = new List(); + + foreach (var step in report.steps) + { + foreach (var msg in step.messages) + { + var info = new BuildMessageInfo + { + message = msg.content + }; + + if (msg.type == LogType.Error || msg.type == LogType.Exception) + errors.Add(info); + else if (msg.type == LogType.Warning) + warnings.Add(info); + } + } + + var steps = new List(); + foreach (var step in report.steps) + { + steps.Add(new BuildStepInfo + { + name = step.name, + durationSec = step.duration.TotalSeconds, + depth = step.depth + }); + } + + return new BuildResponse + { + target = target.ToString(), + targetGroup = targetGroup.ToString(), + locationPathName = locationPathName, + result = report.summary.result.ToString(), + totalErrorCount = report.summary.totalErrors, + totalWarningCount = report.summary.totalWarnings, + totalBuildTimeSec = report.summary.totalTime.TotalSeconds, + totalSizeBytes = (long)report.summary.totalSize, + steps = steps.ToArray(), + errors = errors.ToArray(), + warnings = warnings.ToArray() + }; + } + } + + [Serializable] + public class BuildRequest + { + public string target = ""; + public string locationPathName = ""; + public string[] scenes; + public string[] options; + public string[] extraScriptingDefines; + } + + [Serializable] + public class BuildResponse + { + public string target; + public string targetGroup; + public string locationPathName; + public string result; + public int totalErrorCount; + public int totalWarningCount; + public double totalBuildTimeSec; + public long totalSizeBytes; + public BuildStepInfo[] steps; + public BuildMessageInfo[] errors; + public BuildMessageInfo[] warnings; + } + + [Serializable] + public class BuildStepInfo + { + public string name; + public double durationSec; + public int depth; + } + + [Serializable] + public class BuildMessageInfo + { + public string message; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 7534c4e10fe442ae9bb1f0beab2fcfa4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic.meta`: + +```meta +fileFormatVersion: 2 +guid: 150c5fbd54097450ca3f376e5a58505f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/BuildMagicApplyHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using BuildMagicEditor; + +namespace UniCli.Server.Editor.Handlers.BuildMagic +{ + [Module("BuildMagic")] + public sealed class BuildMagicApplyHandler : CommandHandler + { + public override string CommandName => "BuildMagic.Apply"; + public override string Description => "Apply a BuildMagic build scheme (run PreBuild tasks to configure project settings)"; + + protected override bool TryWriteFormatted(BuildMagicApplyResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Applied scheme '{response.name}' ({response.appliedTaskCount} task(s))" + : $"Failed to apply scheme '{response.name}'"); + return true; + } + + protected override ValueTask ExecuteAsync(BuildMagicApplyRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var allSchemes = BuildSchemeLoader.LoadAll().ToList(); + var scheme = allSchemes.FirstOrDefault(s => s.Name == request.name); + + if (scheme == null) + throw new CommandFailedException( + $"Build scheme '{request.name}' not found", + new BuildMagicApplyResponse { name = request.name, appliedTaskCount = 0 }); + + var preBuildTasks = ResolvePreBuildTasks(scheme, allSchemes); + BuildMagicEditor.BuildPipeline.PreBuild(preBuildTasks); + + return new ValueTask(new BuildMagicApplyResponse + { + name = scheme.Name, + appliedTaskCount = preBuildTasks.Count, + }); + } + + private static IReadOnlyList> ResolvePreBuildTasks( + BuildScheme scheme, + List allSchemes) + { + var buildMagicEditorAssembly = typeof(BuildSchemeLoader).Assembly; + + var schemeUtilityType = buildMagicEditorAssembly.GetType("BuildMagicEditor.BuildSchemeUtility"); + if (schemeUtilityType == null) + throw new InvalidOperationException("BuildSchemeUtility type not found"); + + // BuildSchemeUtility.EnumerateComposedConfigurations(scheme, allSchemes) + var enumerateConfigsMethod = schemeUtilityType + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) + .FirstOrDefault(m => + m.Name == "EnumerateComposedConfigurations" && + m.IsGenericMethod && + m.GetParameters().Length == 2); + if (enumerateConfigsMethod == null) + throw new InvalidOperationException("EnumerateComposedConfigurations method not found"); + + var genericConfigsMethod = enumerateConfigsMethod.MakeGenericMethod(typeof(IPreBuildContext)); + var configurations = genericConfigsMethod.Invoke(null, new object[] { scheme, allSchemes }); + + // BuildTaskBuilderUtility.CreateBuildTasks(configurations) + var taskBuilderType = buildMagicEditorAssembly.GetType("BuildMagicEditor.BuildTaskBuilderUtility"); + if (taskBuilderType == null) + throw new InvalidOperationException("BuildTaskBuilderUtility type not found"); + + var createTasksMethod = taskBuilderType + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) + .FirstOrDefault(m => + m.Name == "CreateBuildTasks" && + m.IsGenericMethod); + if (createTasksMethod == null) + throw new InvalidOperationException("CreateBuildTasks method not found"); + + var genericCreateMethod = createTasksMethod.MakeGenericMethod(typeof(IPreBuildContext)); + var tasks = genericCreateMethod.Invoke(null, new object[] { configurations }); + + return (IReadOnlyList>)tasks; + } + } + + [Serializable] + public class BuildMagicApplyRequest + { + public string name; + } + + [Serializable] + public class BuildMagicApplyResponse + { + public string name; + public int appliedTaskCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/BuildMagicApplyHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 6c7c90407277047d18ce18dab3781cb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/BuildMagicInspectHandler.cs`: + +```cs +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BuildMagicEditor; + +namespace UniCli.Server.Editor.Handlers.BuildMagic +{ + [Module("BuildMagic")] + public sealed class BuildMagicInspectHandler : CommandHandler + { + public override string CommandName => "BuildMagic.Inspect"; + public override string Description => "Inspect a BuildMagic build scheme's configurations"; + + protected override bool TryWriteFormatted(BuildMagicInspectResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine($"Scheme: {response.name}"); + if (!string.IsNullOrEmpty(response.baseSchemeName)) + writer.WriteLine($"Base: {response.baseSchemeName}"); + + if (response.preBuildConfigurations.Length > 0) + { + writer.WriteLine(""); + writer.WriteLine("PreBuild Configurations:"); + foreach (var config in response.preBuildConfigurations) + { + writer.WriteLine($" - {config.taskType}: {config.propertyName}"); + } + } + + if (response.postBuildConfigurations.Length > 0) + { + writer.WriteLine(""); + writer.WriteLine("PostBuild Configurations:"); + foreach (var config in response.postBuildConfigurations) + { + writer.WriteLine($" - {config.taskType}: {config.propertyName}"); + } + } + + return true; + } + + protected override ValueTask ExecuteAsync(BuildMagicInspectRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var allSchemes = BuildSchemeLoader.LoadAll().ToList(); + var scheme = allSchemes.FirstOrDefault(s => s.Name == request.name); + + if (scheme == null) + throw new CommandFailedException( + $"Build scheme '{request.name}' not found", + new BuildMagicInspectResponse + { + name = request.name, + baseSchemeName = "", + preBuildConfigurations = Array.Empty(), + postBuildConfigurations = Array.Empty(), + }); + + var preBuildConfigs = scheme.PreBuildConfigurations + .Select(ExtractConfigInfo) + .ToArray(); + + var postBuildConfigs = scheme.PostBuildConfigurations + .Select(ExtractConfigInfo) + .ToArray(); + + return new ValueTask(new BuildMagicInspectResponse + { + name = scheme.Name, + baseSchemeName = scheme.BaseSchemeName ?? "", + preBuildConfigurations = preBuildConfigs, + postBuildConfigurations = postBuildConfigs, + }); + } + + private static BuildMagicConfigurationEntry ExtractConfigInfo(IBuildConfiguration config) + { + return new BuildMagicConfigurationEntry + { + taskType = config.TaskType?.Name ?? "", + propertyName = config.PropertyName ?? "", + }; + } + } + + [Serializable] + public class BuildMagicInspectRequest + { + public string name; + } + + [Serializable] + public class BuildMagicInspectResponse + { + public string name; + public string baseSchemeName; + public BuildMagicConfigurationEntry[] preBuildConfigurations; + public BuildMagicConfigurationEntry[] postBuildConfigurations; + } + + [Serializable] + public class BuildMagicConfigurationEntry + { + public string taskType; + public string propertyName; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/BuildMagicInspectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f3e90603fe4b546acb691c635ab0b4f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/BuildMagicListHandler.cs`: + +```cs +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BuildMagicEditor; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers.BuildMagic +{ + [Module("BuildMagic")] + public sealed class BuildMagicListHandler : CommandHandler + { + public override string CommandName => "BuildMagic.List"; + public override string Description => "List all BuildMagic build schemes"; + + protected override bool TryWriteFormatted(BuildMagicListResponse response, bool success, IFormatWriter writer) + { + var nameWidth = "Name".Length; + var baseWidth = "Base".Length; + + foreach (var scheme in response.schemes) + { + nameWidth = Math.Max(nameWidth, scheme.name.Length); + baseWidth = Math.Max(baseWidth, (scheme.baseSchemeName ?? "").Length); + } + + writer.WriteLine($"{"Name".PadRight(nameWidth)} {"Base".PadRight(baseWidth)} {"PreBuild".PadRight(10)} {"PostBuild".PadRight(10)}"); + + foreach (var scheme in response.schemes) + { + writer.WriteLine( + $"{scheme.name.PadRight(nameWidth)} " + + $"{(scheme.baseSchemeName ?? "").PadRight(baseWidth)} " + + $"{scheme.preBuildConfigCount.ToString().PadRight(10)} " + + $"{scheme.postBuildConfigCount.ToString().PadRight(10)}"); + } + + writer.WriteLine($"{response.totalCount} scheme(s)"); + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var schemes = BuildSchemeLoader.LoadAll() + .Select(s => new BuildMagicSchemeEntry + { + name = s.Name, + baseSchemeName = s.BaseSchemeName ?? "", + preBuildConfigCount = s.PreBuildConfigurations.Count, + postBuildConfigCount = s.PostBuildConfigurations.Count, + }) + .OrderBy(s => s.name) + .ToArray(); + + return new ValueTask(new BuildMagicListResponse + { + schemes = schemes, + totalCount = schemes.Length, + }); + } + } + + [Serializable] + public class BuildMagicListResponse + { + public BuildMagicSchemeEntry[] schemes; + public int totalCount; + } + + [Serializable] + public class BuildMagicSchemeEntry + { + public string name; + public string baseSchemeName; + public int preBuildConfigCount; + public int postBuildConfigCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/BuildMagicListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4113fc4917b03406194d41e762df8425 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/UniCli.Server.Editor.BuildMagic.asmdef`: + +```asmdef +{ + "name": "UniCli.Server.Editor.BuildMagic", + "rootNamespace": "UniCli.Server.Editor.Handlers.BuildMagic", + "references": [ + "UniCli.Server.Editor", + "BuildMagic.Editor" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [ + "UNICLI_BUILDMAGIC" + ], + "versionDefines": [ + { + "name": "jp.co.cyberagent.buildmagic", + "expression": "", + "define": "UNICLI_BUILDMAGIC" + } + ], + "noEngineReferences": false +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildMagic/UniCli.Server.Editor.BuildMagic.asmdef.meta`: + +```meta +fileFormatVersion: 2 +guid: 474b0ae7612644874a29024821578407 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile.meta`: + +```meta +fileFormatVersion: 2 +guid: 867b260d92925423baaab00109cffeb0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileGetActiveHandler.cs`: + +```cs +#if UNITY_6000_0_OR_NEWER +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; +using UnityEditor.Build.Profile; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class BuildProfileGetActiveHandler : CommandHandler + { + public override string CommandName => "BuildProfile.GetActive"; + public override string Description => "Get the active build profile"; + + protected override bool TryWriteFormatted(BuildProfileGetActiveResponse response, bool success, IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to get active build profile"); + return true; + } + + if (!response.hasActiveProfile) + { + writer.WriteLine("No active build profile (using platform profile)"); + return true; + } + + writer.WriteLine($"{response.name} ({response.path})"); + if (response.scriptingDefines != null && response.scriptingDefines.Length > 0) + writer.WriteLine($" Scripting Defines: {string.Join(";", response.scriptingDefines)}"); + writer.WriteLine($" Override Global Scenes: {response.overrideGlobalScenes}"); + if (response.scenes != null && response.scenes.Length > 0) + { + writer.WriteLine($" Scenes ({response.scenes.Length}):"); + foreach (var scene in response.scenes) + writer.WriteLine($" {scene}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var profile = BuildProfile.GetActiveBuildProfile(); + if (profile == null) + { + return new ValueTask(new BuildProfileGetActiveResponse + { + hasActiveProfile = false, + }); + } + + var path = AssetDatabase.GetAssetPath(profile); + var scenes = profile.scenes + .Where(s => s.enabled) + .Select(s => s.path) + .ToArray(); + + return new ValueTask(new BuildProfileGetActiveResponse + { + hasActiveProfile = true, + name = profile.name, + path = path, + scriptingDefines = profile.scriptingDefines, + scenes = scenes, + overrideGlobalScenes = profile.overrideGlobalScenes, + }); + } + } + + [Serializable] + public class BuildProfileGetActiveResponse + { + public bool hasActiveProfile; + public string name; + public string path; + public string[] scriptingDefines; + public string[] scenes; + public bool overrideGlobalScenes; + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileGetActiveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 0df3c9a8be83546488ebdf943bb65b71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileInspectHandler.cs`: + +```cs +#if UNITY_6000_0_OR_NEWER +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build.Profile; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class BuildProfileInspectHandler : CommandHandler + { + public override string CommandName => "BuildProfile.Inspect"; + public override string Description => "Inspect a build profile's details"; + + protected override bool TryWriteFormatted(BuildProfileInspectResponse response, bool success, IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to inspect build profile"); + return true; + } + + var active = response.isActive ? " [active]" : ""; + writer.WriteLine($"{response.name} ({response.path}){active}"); + writer.WriteLine($" Override Global Scenes: {response.overrideGlobalScenes}"); + + if (response.scriptingDefines != null && response.scriptingDefines.Length > 0) + writer.WriteLine($" Scripting Defines: {string.Join(";", response.scriptingDefines)}"); + + if (response.scenes != null && response.scenes.Length > 0) + { + writer.WriteLine($" Scenes ({response.scenes.Length}):"); + foreach (var scene in response.scenes) + { + var enabled = scene.enabled ? "" : " [disabled]"; + writer.WriteLine($" {scene.path}{enabled}"); + } + } + + if (response.scenesForBuild != null && response.scenesForBuild.Length > 0) + { + writer.WriteLine($" Scenes for Build ({response.scenesForBuild.Length}):"); + foreach (var scene in response.scenesForBuild) + writer.WriteLine($" {scene.path}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(BuildProfileInspectRequest request, CancellationToken cancellationToken) + { + BuildProfile profile; + string path; + + if (string.IsNullOrEmpty(request.path)) + { + profile = BuildProfile.GetActiveBuildProfile(); + if (profile == null) + throw new CommandFailedException("No active build profile. Specify a path or set an active profile.", new BuildProfileInspectResponse()); + path = AssetDatabase.GetAssetPath(profile); + } + else + { + path = request.path; + profile = AssetDatabase.LoadAssetAtPath(path); + if (profile == null) + throw new CommandFailedException($"Build profile not found at '{path}'", new BuildProfileInspectResponse { path = path }); + } + + var activeProfile = BuildProfile.GetActiveBuildProfile(); + + var scenes = profile.scenes + .Select(s => new BuildProfileSceneEntry { path = s.path, enabled = s.enabled }) + .ToArray(); + + var scenesForBuild = profile.GetScenesForBuild() + .Where(s => s.enabled) + .Select(s => new BuildProfileSceneEntry { path = s.path, enabled = s.enabled }) + .ToArray(); + + return new ValueTask(new BuildProfileInspectResponse + { + name = profile.name, + path = path, + isActive = activeProfile != null && profile == activeProfile, + scriptingDefines = profile.scriptingDefines, + scenes = scenes, + overrideGlobalScenes = profile.overrideGlobalScenes, + scenesForBuild = scenesForBuild, + }); + } + } + + [Serializable] + public class BuildProfileInspectRequest + { + public string path; + } + + [Serializable] + public class BuildProfileInspectResponse + { + public string name; + public string path; + public bool isActive; + public string[] scriptingDefines; + public BuildProfileSceneEntry[] scenes; + public bool overrideGlobalScenes; + public BuildProfileSceneEntry[] scenesForBuild; + } + + [Serializable] + public class BuildProfileSceneEntry + { + public string path; + public bool enabled; + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileInspectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4dbd4c18df3ed48ff9e7697c719aa89e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileListHandler.cs`: + +```cs +#if UNITY_6000_0_OR_NEWER +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; +using UnityEditor.Build.Profile; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class BuildProfileListHandler : CommandHandler + { + public override string CommandName => "BuildProfile.List"; + public override string Description => "List all build profiles"; + + protected override bool TryWriteFormatted(BuildProfileListResponse response, bool success, IFormatWriter writer) + { + if (!success || response.profiles == null || response.profiles.Length == 0) + { + writer.WriteLine("No build profiles found."); + return true; + } + + writer.WriteLine($"Build profiles ({response.profiles.Length}):"); + foreach (var profile in response.profiles) + { + var active = profile.isActive ? " [active]" : ""; + writer.WriteLine($" {profile.name} ({profile.path}){active}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var guids = AssetDatabase.FindAssets("t:BuildProfile"); + var activeProfile = BuildProfile.GetActiveBuildProfile(); + var profiles = new BuildProfileEntry[guids.Length]; + + for (var i = 0; i < guids.Length; i++) + { + var path = AssetDatabase.GUIDToAssetPath(guids[i]); + var profile = AssetDatabase.LoadAssetAtPath(path); + profiles[i] = new BuildProfileEntry + { + name = profile != null ? profile.name : System.IO.Path.GetFileNameWithoutExtension(path), + path = path, + isActive = activeProfile != null && profile == activeProfile, + }; + } + + return new ValueTask(new BuildProfileListResponse + { + profiles = profiles, + }); + } + } + + [Serializable] + public class BuildProfileListResponse + { + public BuildProfileEntry[] profiles; + } + + [Serializable] + public class BuildProfileEntry + { + public string name; + public string path; + public bool isActive; + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: a88036c1b9aa8465789d781513156eab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileSetActiveHandler.cs`: + +```cs +#if UNITY_6000_0_OR_NEWER +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build.Profile; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class BuildProfileSetActiveHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public BuildProfileSetActiveHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "BuildProfile.SetActive"; + public override string Description => "Set the active build profile"; + + protected override bool TryWriteFormatted(BuildProfileSetActiveResponse response, bool success, IFormatWriter writer) + { + if (success) + { + if (string.IsNullOrEmpty(response.path)) + writer.WriteLine("Switched to platform profile"); + else + writer.WriteLine($"Active build profile: {response.name} ({response.path})"); + } + else + { + writer.WriteLine("Failed to set active build profile"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(BuildProfileSetActiveRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlaying); + + if (string.IsNullOrEmpty(request.path) || request.path == "none") + { + BuildProfile.SetActiveBuildProfile(null); + return new ValueTask(new BuildProfileSetActiveResponse + { + name = "", + path = "", + }); + } + + var profile = AssetDatabase.LoadAssetAtPath(request.path); + if (profile == null) + throw new CommandFailedException($"Build profile not found at '{request.path}'", new BuildProfileSetActiveResponse { name = "", path = request.path }); + + BuildProfile.SetActiveBuildProfile(profile); + + return new ValueTask(new BuildProfileSetActiveResponse + { + name = profile.name, + path = request.path, + }); + } + } + + [Serializable] + public class BuildProfileSetActiveRequest + { + public string path; + } + + [Serializable] + public class BuildProfileSetActiveResponse + { + public string name; + public string path; + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildProfile/BuildProfileSetActiveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 220cc55b9cf6c4f868a65bb98dcbb4b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildTargetGetActiveHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class BuildTargetGetActiveHandler : CommandHandler + { + public override string CommandName => "BuildTarget.GetActive"; + public override string Description => "Get the active build target and build target group via EditorUserBuildSettings"; + + protected override bool TryWriteFormatted(BuildTargetGetActiveResponse response, bool success, IFormatWriter writer) + { + if (!success) + return false; + + writer.WriteLine($"Build Target: {response.buildTarget}"); + writer.WriteLine($"Target Group: {response.buildTargetGroup}"); + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var target = EditorUserBuildSettings.activeBuildTarget; + var group = BuildPipeline.GetBuildTargetGroup(target); + + return new ValueTask(new BuildTargetGetActiveResponse + { + buildTarget = target.ToString(), + buildTargetGroup = group.ToString(), + }); + } + } + + [Serializable] + public class BuildTargetGetActiveResponse + { + public string buildTarget; + public string buildTargetGroup; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildTargetGetActiveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 5177d29b49f2341fc9a7b941445def8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildTargetSwitchHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class BuildTargetSwitchHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public BuildTargetSwitchHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "BuildTarget.Switch"; + public override string Description => "Switch the active build target via EditorUserBuildSettings.SwitchActiveBuildTarget"; + + protected override bool TryWriteFormatted(BuildTargetSwitchResponse response, bool success, IFormatWriter writer) + { + if (!success) + return false; + + writer.WriteLine($"Switched to {response.buildTarget} ({response.buildTargetGroup})"); + + return true; + } + + protected override ValueTask ExecuteAsync(BuildTargetSwitchRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlaying); + + if (string.IsNullOrEmpty(request.target)) + throw new ArgumentException("target is required (e.g. Android, iOS, StandaloneWindows64, StandaloneOSX, WebGL)"); + + if (!Enum.TryParse(request.target, true, out var target)) + throw new ArgumentException($"Invalid build target: '{request.target}'. Use a valid BuildTarget name (e.g. Android, iOS, StandaloneWindows64, StandaloneOSX, WebGL)."); + + var group = BuildPipeline.GetBuildTargetGroup(target); + + var switched = EditorUserBuildSettings.SwitchActiveBuildTarget(group, target); + if (!switched) + throw new CommandFailedException($"Failed to switch build target to {target}", new { target = target.ToString() }); + + return new ValueTask(new BuildTargetSwitchResponse + { + buildTarget = target.ToString(), + buildTargetGroup = group.ToString(), + }); + } + } + + [Serializable] + public class BuildTargetSwitchRequest + { + public string target; + } + + [Serializable] + public class BuildTargetSwitchResponse + { + public string buildTarget; + public string buildTargetGroup; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/BuildTargetSwitchHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f3c1d436ee57a408d9cd51c11e56b5e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CommandFailedException.cs`: + +```cs +using System; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class CommandFailedException : Exception + { + public object ResponseData { get; } + + public CommandFailedException(string message, object responseData) + : base(message) + { + ResponseData = responseData; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CommandFailedException.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b5f2f48e16a27411d99c73c1a78477c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CommandListHandler.cs`: + +```cs +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class CommandListHandler : CommandHandler + { + private readonly CommandDispatcher _dispatcher; + + public CommandListHandler(CommandDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public override string CommandName => "Commands.List"; + public override string Description => "List all available commands with their metadata"; + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var response = new CommandListResponse + { + commands = _dispatcher.GetAllCommandInfo() + }; + + return new ValueTask(response); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CommandListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 14b98e600941a404ca75734b1896888d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CompileHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; +using UnityEditor.Compilation; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class CompileHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public CompileHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "Compile"; + public override string Description => "Trigger script compilation and return results with error details"; + + protected override bool TryWriteFormatted(CompileResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Compilation succeeded ({response.errorCount} errors, {response.warningCount} warnings)"); + else + writer.WriteLine($"Compilation failed ({response.errorCount} errors, {response.warningCount} warnings)"); + + WriteIssues(writer, response.errors); + WriteIssues(writer, response.warnings); + + return true; + } + + private static void WriteIssues(IFormatWriter writer, CompileIssue[] issues) + { + if (issues == null) return; + + foreach (var issue in issues) + { + if (!string.IsNullOrEmpty(issue.file)) + writer.WriteLine($" {issue.file}({issue.line}): {issue.message}"); + else + writer.WriteLine($" {issue.message}"); + } + } + + protected override async ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlaying); + + var errors = new List(); + var warnings = new List(); + var tcs = new TaskCompletionSource(); + + void OnCompilationFinished(object obj) + { + CompilationPipeline.compilationFinished -= OnCompilationFinished; + tcs.SetResult(true); + } + + void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages) + { + foreach (var msg in messages) + { + var issue = new CompileIssue + { + message = msg.message, + file = msg.file ?? "", + line = msg.line + }; + + if (msg.type == CompilerMessageType.Error) + errors.Add(issue); + else if (msg.type == CompilerMessageType.Warning) + warnings.Add(issue); + } + } + + CompilationPipeline.compilationFinished += OnCompilationFinished; + CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished; + + try + { + if (!EditorApplication.isCompiling) + { + CompilationPipeline.RequestScriptCompilation(); + } + + await tcs.Task.WithCancellation(cancellationToken); + } + finally + { + CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished; + } + + var response = new CompileResponse + { + errorCount = errors.Count, + warningCount = warnings.Count, + errors = errors.ToArray(), + warnings = warnings.ToArray() + }; + + if (errors.Count > 0) + throw new CommandFailedException($"Compilation failed with {errors.Count} error(s)", response); + + return response; + } + } + + [Serializable] + public class CompileResponse + { + public int errorCount; + public int warningCount; + public CompileIssue[] errors; + public CompileIssue[] warnings; + } + + [Serializable] + public class CompileIssue + { + public string message; + public string file; + public int line; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CompileHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 2035f1ba7b9bd4be1b0924cf3b02c132 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CompilePlayerHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Build.Player; +using UnityEditor.Compilation; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class CompilePlayerHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public CompilePlayerHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "BuildPlayer.Compile"; + public override string Description => "Compile player scripts for a specific build target"; + + protected override bool TryWriteFormatted(CompilePlayerResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Player compilation succeeded for {response.target} ({response.errorCount} errors, {response.warningCount} warnings, {response.assemblyCount} assemblies)"); + else + writer.WriteLine($"Player compilation failed for {response.target} ({response.errorCount} errors, {response.warningCount} warnings)"); + + WriteIssues(writer, response.errors); + WriteIssues(writer, response.warnings); + + return true; + } + + private static void WriteIssues(IFormatWriter writer, CompileIssue[] issues) + { + if (issues == null) return; + + foreach (var issue in issues) + { + if (!string.IsNullOrEmpty(issue.file)) + writer.WriteLine($" {issue.file}({issue.line}): {issue.message}"); + else + writer.WriteLine($" {issue.message}"); + } + } + + protected override ValueTask ExecuteAsync(CompilePlayerRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + var target = ResolveTarget(request.target); + var targetGroup = BuildPipeline.GetBuildTargetGroup(target); + + var settings = new ScriptCompilationSettings + { + target = target, + group = targetGroup, + extraScriptingDefines = request.extraScriptingDefines + }; + + var tempDir = Path.Combine(Path.GetTempPath(), $"unicli-compile-player-{Guid.NewGuid()}"); + Directory.CreateDirectory(tempDir); + + var errors = new List(); + var warnings = new List(); + + void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages) + { + foreach (var msg in messages) + { + var issue = new CompileIssue + { + message = msg.message, + file = msg.file ?? "", + line = msg.line + }; + + if (msg.type == CompilerMessageType.Error) + errors.Add(issue); + else if (msg.type == CompilerMessageType.Warning) + warnings.Add(issue); + } + } + + CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished; + + ScriptCompilationResult result; + try + { + result = PlayerBuildInterface.CompilePlayerScripts(settings, tempDir); + } + catch (Exception ex) + { + errors.Add(new CompileIssue { message = ex.Message, file = "", line = 0 }); + result = default; + } + finally + { + CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished; + + try { Directory.Delete(tempDir, true); } + catch { /* best-effort cleanup */ } + } + + var assemblies = result.assemblies != null + ? new List(result.assemblies).ToArray() + : Array.Empty(); + var response = new CompilePlayerResponse + { + target = target.ToString(), + targetGroup = targetGroup.ToString(), + assemblyCount = assemblies.Length, + assemblies = assemblies, + errorCount = errors.Count, + warningCount = warnings.Count, + errors = errors.ToArray(), + warnings = warnings.ToArray() + }; + + if (errors.Count > 0) + throw new CommandFailedException($"Player compilation failed for {target} with {errors.Count} error(s)", response); + + return new ValueTask(response); + } + + private static BuildTarget ResolveTarget(string target) + { + if (string.IsNullOrEmpty(target)) + return EditorUserBuildSettings.activeBuildTarget; + + if (Enum.TryParse(target, true, out var parsed)) + return parsed; + + throw new ArgumentException($"Invalid build target: '{target}'. Use a valid BuildTarget name (e.g. Android, iOS, StandaloneWindows64)."); + } + } + + [Serializable] + public class CompilePlayerRequest + { + public string target = ""; + public string[] extraScriptingDefines; + } + + [Serializable] + public class CompilePlayerResponse + { + public string target; + public string targetGroup; + public int assemblyCount; + public string[] assemblies; + public int errorCount; + public int warningCount; + public CompileIssue[] errors; + public CompileIssue[] warnings; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/CompilePlayerHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 7c11ad50700034355bc7d04717d25e80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Component.meta`: + +```meta +fileFormatVersion: 2 +guid: 2098097f5e07403ab5baa060a23bed87 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Component/SetPropertyHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Globalization; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class SetPropertyHandler : CommandHandler + { + public override string CommandName => "Component.SetProperty"; + public override string Description => "Set a component property value via SerializedProperty"; + + protected override bool TryWriteFormatted(SetPropertyResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Set {response.propertyPath} = {response.currentValue} (was {response.previousValue})"); + else + writer.WriteLine("Failed to set property"); + + return true; + } + + protected override ValueTask ExecuteAsync(SetPropertyRequest request, CancellationToken cancellationToken) + { + if (request.componentInstanceId == 0) + throw new ArgumentException("componentInstanceId is required"); + if (string.IsNullOrEmpty(request.propertyPath)) + throw new ArgumentException("propertyPath is required"); + + var obj = EditorUtility.InstanceIDToObject(request.componentInstanceId); + if (obj is not UnityEngine.Component component) + { + throw new CommandFailedException( + $"Component not found for instanceId={request.componentInstanceId}", + new SetPropertyResponse()); + } + + using var serializedObject = new SerializedObject(component); + var property = serializedObject.FindProperty(request.propertyPath); + if (property == null) + { + throw new CommandFailedException( + $"Property \"{request.propertyPath}\" not found on {component.GetType().FullName}", + new SetPropertyResponse()); + } + + var previousValue = GetPropertyValueString(property); + SetPropertyValue(property, request.value); + serializedObject.ApplyModifiedProperties(); + + var verifyProperty = serializedObject.FindProperty(request.propertyPath); + var currentValue = GetPropertyValueString(verifyProperty); + + return new ValueTask(new SetPropertyResponse + { + componentInstanceId = request.componentInstanceId, + propertyPath = request.propertyPath, + previousValue = previousValue, + currentValue = currentValue + }); + } + + private static void SetPropertyValue(SerializedProperty property, string value) + { + switch (property.propertyType) + { + case SerializedPropertyType.Integer: + property.intValue = int.Parse(value, CultureInfo.InvariantCulture); + break; + case SerializedPropertyType.Float: + property.floatValue = float.Parse(value, CultureInfo.InvariantCulture); + break; + case SerializedPropertyType.Boolean: + property.boolValue = bool.Parse(value); + break; + case SerializedPropertyType.String: + property.stringValue = value; + break; + case SerializedPropertyType.Enum: + if (int.TryParse(value, out var enumIndex)) + { + property.enumValueIndex = enumIndex; + } + else + { + var index = Array.IndexOf(property.enumNames, value); + if (index < 0) + throw new CommandFailedException( + $"Invalid enum value \"{value}\". Valid values: {string.Join(", ", property.enumNames)}", + new SetPropertyResponse()); + property.enumValueIndex = index; + } + break; + case SerializedPropertyType.Vector2: + property.vector2Value = ParseVector2(value); + break; + case SerializedPropertyType.Vector3: + property.vector3Value = ParseVector3(value); + break; + case SerializedPropertyType.Vector4: + property.vector4Value = ParseVector4(value); + break; + case SerializedPropertyType.Color: + property.colorValue = ParseColor(value); + break; + case SerializedPropertyType.Rect: + property.rectValue = ParseRect(value); + break; + case SerializedPropertyType.Bounds: + property.boundsValue = ParseBounds(value); + break; + case SerializedPropertyType.Quaternion: + property.quaternionValue = ParseQuaternion(value); + break; + case SerializedPropertyType.LayerMask: + property.intValue = int.Parse(value, CultureInfo.InvariantCulture); + break; + case SerializedPropertyType.ObjectReference: + property.objectReferenceValue = ResolveObjectReference(value); + break; + default: + throw new CommandFailedException( + $"Unsupported property type: {property.propertyType}", + new SetPropertyResponse()); + } + } + + private static Vector2 ParseVector2(string value) + { + var parts = value.Split(','); + if (parts.Length != 2) + throw new CommandFailedException("Vector2 requires 2 comma-separated values (e.g. \"1.0,2.0\")", new SetPropertyResponse()); + return new Vector2( + float.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[1].Trim(), CultureInfo.InvariantCulture)); + } + + private static Vector3 ParseVector3(string value) + { + var parts = value.Split(','); + if (parts.Length != 3) + throw new CommandFailedException("Vector3 requires 3 comma-separated values (e.g. \"1.0,2.0,3.0\")", new SetPropertyResponse()); + return new Vector3( + float.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[1].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[2].Trim(), CultureInfo.InvariantCulture)); + } + + private static Vector4 ParseVector4(string value) + { + var parts = value.Split(','); + if (parts.Length != 4) + throw new CommandFailedException("Vector4 requires 4 comma-separated values (e.g. \"1.0,2.0,3.0,4.0\")", new SetPropertyResponse()); + return new Vector4( + float.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[1].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[2].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[3].Trim(), CultureInfo.InvariantCulture)); + } + + private static Rect ParseRect(string value) + { + var parts = value.Split(','); + if (parts.Length != 4) + throw new CommandFailedException("Rect requires 4 comma-separated values (e.g. \"x,y,width,height\")", new SetPropertyResponse()); + return new Rect( + float.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[1].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[2].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[3].Trim(), CultureInfo.InvariantCulture)); + } + + private static Bounds ParseBounds(string value) + { + var parts = value.Split(','); + if (parts.Length != 6) + throw new CommandFailedException("Bounds requires 6 comma-separated values (e.g. \"cx,cy,cz,sx,sy,sz\")", new SetPropertyResponse()); + return new Bounds( + new Vector3( + float.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[1].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[2].Trim(), CultureInfo.InvariantCulture)), + new Vector3( + float.Parse(parts[3].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[4].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[5].Trim(), CultureInfo.InvariantCulture))); + } + + private static Quaternion ParseQuaternion(string value) + { + var parts = value.Split(','); + if (parts.Length != 4) + throw new CommandFailedException("Quaternion requires 4 comma-separated values (e.g. \"x,y,z,w\")", new SetPropertyResponse()); + return new Quaternion( + float.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[1].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[2].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[3].Trim(), CultureInfo.InvariantCulture)); + } + + private static Color ParseColor(string value) + { + var parts = value.Split(','); + if (parts.Length < 3 || parts.Length > 4) + throw new CommandFailedException("Color requires 3-4 comma-separated values (e.g. \"1.0,0.5,0.0\" or \"1.0,0.5,0.0,1.0\")", new SetPropertyResponse()); + return new Color( + float.Parse(parts[0].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[1].Trim(), CultureInfo.InvariantCulture), + float.Parse(parts[2].Trim(), CultureInfo.InvariantCulture), + parts.Length == 4 ? float.Parse(parts[3].Trim(), CultureInfo.InvariantCulture) : 1f); + } + + private static UnityEngine.Object ResolveObjectReference(string value) + { + if (string.IsNullOrEmpty(value) || value == "null") + return null; + + if (value.StartsWith("guid:")) + { + var guid = value.Substring("guid:".Length); + var assetPath = AssetDatabase.GUIDToAssetPath(guid); + if (string.IsNullOrEmpty(assetPath)) + throw new CommandFailedException( + $"Asset not found for GUID: {guid}", + new SetPropertyResponse()); + var asset = AssetDatabase.LoadAssetAtPath(assetPath); + if (asset == null) + throw new CommandFailedException( + $"Failed to load asset at: {assetPath}", + new SetPropertyResponse()); + return asset; + } + + if (value.StartsWith("instanceId:")) + { + var idStr = value.Substring("instanceId:".Length); + if (!int.TryParse(idStr, out var instanceId)) + throw new CommandFailedException( + $"Invalid instanceId: {idStr}", + new SetPropertyResponse()); + var obj = EditorUtility.InstanceIDToObject(instanceId); + if (obj == null) + throw new CommandFailedException( + $"Object not found for instanceId: {instanceId}", + new SetPropertyResponse()); + return obj; + } + + // Try as asset path + var assetByPath = AssetDatabase.LoadAssetAtPath(value); + if (assetByPath != null) + return assetByPath; + + throw new CommandFailedException( + $"Cannot resolve ObjectReference from \"{value}\". Use \"guid:\", \"instanceId:\", an asset path, or \"null\".", + new SetPropertyResponse()); + } + + private static string GetPropertyValueString(SerializedProperty property) + { + switch (property.propertyType) + { + case SerializedPropertyType.Integer: + return property.intValue.ToString(); + case SerializedPropertyType.Boolean: + return property.boolValue.ToString().ToLowerInvariant(); + case SerializedPropertyType.Float: + return property.floatValue.ToString("G"); + case SerializedPropertyType.String: + return property.stringValue ?? ""; + case SerializedPropertyType.Enum: + return property.enumNames.Length > property.enumValueIndex && property.enumValueIndex >= 0 + ? property.enumNames[property.enumValueIndex] + : property.enumValueIndex.ToString(); + case SerializedPropertyType.ObjectReference: + return property.objectReferenceValue != null + ? property.objectReferenceValue.name + : "(null)"; + case SerializedPropertyType.Vector2: + return property.vector2Value.ToString(); + case SerializedPropertyType.Vector3: + return property.vector3Value.ToString(); + case SerializedPropertyType.Vector4: + return property.vector4Value.ToString(); + case SerializedPropertyType.Color: + return property.colorValue.ToString(); + case SerializedPropertyType.Rect: + return property.rectValue.ToString(); + case SerializedPropertyType.Bounds: + return property.boundsValue.ToString(); + case SerializedPropertyType.Quaternion: + return property.quaternionValue.ToString(); + case SerializedPropertyType.LayerMask: + return property.intValue.ToString(); + default: + return $"({property.propertyType})"; + } + } + } + + [Serializable] + public class SetPropertyRequest + { + public int componentInstanceId; + public string propertyPath = ""; + public string value = ""; + } + + [Serializable] + public class SetPropertyResponse + { + public int componentInstanceId; + public string propertyPath; + public string previousValue; + public string currentValue; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Component/SetPropertyHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 590791decc3d4575874256ad8f3f8d09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Connection.meta`: + +```meta +fileFormatVersion: 2 +guid: 93952603e17024f04b38522e591d8873 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Connection/ConnectionConnectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Remote")] + public sealed class ConnectionConnectHandler : CommandHandler + { + public override string CommandName => "Connection.Connect"; + public override string Description => "Connect to a target player/device by ID, IP address, or device ID"; + + protected override bool TryWriteFormatted(ConnectionStatusResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Connected to: {response.name} (id={response.id})"); + else + writer.WriteLine("Failed to connect"); + + return true; + } + + protected override ValueTask ExecuteAsync(ConnectionConnectRequest request, CancellationToken cancellationToken) + { + if (!string.IsNullOrEmpty(request.deviceId)) + { + ProfilerDriver.DirectURLConnect("device://" + request.deviceId); + } + else if (request.id != 0) + { + ProfilerDriver.connectedProfiler = request.id; + } + else if (!string.IsNullOrEmpty(request.ip)) + { + ProfilerDriver.DirectIPConnect(request.ip); + } + else + { + throw new ArgumentException("Either deviceId, id, or ip must be specified"); + } + + var currentId = ProfilerDriver.connectedProfiler; + return new ValueTask(new ConnectionStatusResponse + { + id = currentId, + name = ProfilerDriver.GetConnectionIdentifier(currentId), + directConnectionUrl = ProfilerDriver.directConnectionUrl + }); + } + } + + [Serializable] + public class ConnectionConnectRequest + { + public int id; + public string ip; + public string deviceId; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Connection/ConnectionConnectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 66e9d49063b6447b7bba01ffc2fc3d9c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Connection/ConnectionListHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor.Hardware; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Remote")] + public sealed class ConnectionListHandler : CommandHandler + { + const int PlayerDirectUrlConnectGuid = 0xFEEE; + + public override string CommandName => "Connection.List"; + public override string Description => "List available connection targets (players/devices)"; + + protected override bool TryWriteFormatted(ConnectionListResponse response, bool success, IFormatWriter writer) + { + if (!success || response.targets == null || response.targets.Length == 0) + { + writer.WriteLine("No connection targets available."); + return true; + } + + writer.WriteLine($"Connection targets ({response.targets.Length}):"); + foreach (var target in response.targets) + { + var connected = target.isConnected ? " [connected]" : ""; + var source = !string.IsNullOrEmpty(target.deviceId) ? $" device={target.deviceId}" : ""; + writer.WriteLine($" id={target.id} {target.name} ({target.type}){source}{connected}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var connectedId = ProfilerDriver.connectedProfiler; + var connectedUrl = ProfilerDriver.directConnectionUrl; + var targets = new List(); + + var ids = ProfilerDriver.GetAvailableProfilers(); + foreach (var id in ids) + { + targets.Add(new ConnectionTarget + { + id = id, + name = ProfilerDriver.GetConnectionIdentifier(id), + type = "Profiler", + isConnected = id == connectedId + }); + } + + foreach (var device in DevDeviceList.GetDevices()) + { + if (!device.isConnected || (device.features & DevDeviceFeatures.PlayerConnection) == 0) + continue; + + var url = "device://" + device.id; + var isConnected = connectedId == PlayerDirectUrlConnectGuid && connectedUrl == url; + + targets.Add(new ConnectionTarget + { + id = PlayerDirectUrlConnectGuid, + name = device.name, + type = device.type, + deviceId = device.id, + isConnected = isConnected + }); + } + + return new ValueTask(new ConnectionListResponse + { + targets = targets.ToArray() + }); + } + } + + [Serializable] + public class ConnectionListResponse + { + public ConnectionTarget[] targets; + } + + [Serializable] + public class ConnectionTarget + { + public int id; + public string name; + public string type; + public string deviceId; + public bool isConnected; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Connection/ConnectionListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 55f68498335de477b957d330cfb4d5fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Connection/ConnectionStatusHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Remote")] + public sealed class ConnectionStatusHandler : CommandHandler + { + public override string CommandName => "Connection.Status"; + public override string Description => "Get current profiler connection status"; + + protected override bool TryWriteFormatted(ConnectionStatusResponse response, bool success, IFormatWriter writer) + { + if (success) + { + var url = !string.IsNullOrEmpty(response.directConnectionUrl) ? $", url={response.directConnectionUrl}" : ""; + writer.WriteLine($"Connected to: {response.name} (id={response.id}{url})"); + } + else + { + writer.WriteLine("Failed to get connection status"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var currentId = ProfilerDriver.connectedProfiler; + return new ValueTask(new ConnectionStatusResponse + { + id = currentId, + name = ProfilerDriver.GetConnectionIdentifier(currentId), + directConnectionUrl = ProfilerDriver.directConnectionUrl + }); + } + } + + [Serializable] + public class ConnectionStatusResponse + { + public int id; + public string name; + public string directConnectionUrl; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Connection/ConnectionStatusHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3ac996423e6f14313b541336227f0a48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Console.meta`: + +```meta +fileFormatVersion: 2 +guid: 604418d3f8ffd47fa82dade9cd2357dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Console/ClearConsoleHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ClearConsoleHandler : CommandHandler + { + private readonly EditorLogManager _logManager; + + public ClearConsoleHandler(EditorLogManager logManager) + { + _logManager = logManager; + } + + public override string CommandName => "Console.Clear"; + public override string Description => "Clear Unity Editor console logs"; + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var count = _logManager.GetLogCount(); + _logManager.ClearLogs(); + + return new ValueTask(new ClearConsoleResponse + { + clearedCount = count + }); + } + } + + [Serializable] + public class ClearConsoleResponse + { + public int clearedCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Console/ClearConsoleHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 2ff64bedadcdc48c48232b0b3a944d14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Console/EditorLogHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class EditorLogGetHandler : CommandHandler + { + private readonly EditorLogManager _logManager; + + public EditorLogGetHandler(EditorLogManager logManager) + { + _logManager = logManager; + } + + public override string CommandName => "Console.GetLog"; + public override string Description => "Retrieve Unity Editor console logs with optional filtering"; + + protected override ValueTask ExecuteAsync(EditorLogRequest request, CancellationToken cancellationToken) + { + var logs = _logManager.GetLogs(request.logType, request.searchText, request.maxCount); + + if (request.stackTraceLines >= 0) + { + for (var i = 0; i < logs.Length; i++) + { + logs[i].stackTrace = StackTraceHelper.Truncate(logs[i].stackTrace, request.stackTraceLines); + } + } + + var response = new EditorLogResponse + { + logs = logs, + totalCount = _logManager.GetLogCount(), + displayedCount = logs.Length + }; + + return new ValueTask(response); + } + } + + [Serializable] + public class EditorLogRequest + { + public string logType = "All"; + public string searchText = ""; + public int maxCount = 100; + public int stackTraceLines = 0; // 0: no stack trace (default), -1: full, N>0: first N lines + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Console/EditorLogHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 0238e879f240449788daef8dab58f1af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/EvalHandler.cs`: + +```cs +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class EvalHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public EvalHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "Eval"; + public override string Description => "Compile and execute C# code dynamically in the Unity Editor context"; + + private static int _evalCounter; + + protected override bool TryWriteFormatted(EvalResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"[{response.ResultType}]"); + writer.WriteLine(response.ResultRaw); + } + else + { + writer.WriteLine(response.ResultRaw); + } + + return true; + } + + protected override async ValueTask ExecuteAsync(EvalRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotCompiling); + + if (string.IsNullOrWhiteSpace(request.code)) + throw new ArgumentException("code must not be empty"); + + var id = ++_evalCounter; + var className = $"UniCliEval_{id}"; + var tempDir = Path.Combine("Temp", "UniCliEval"); + var sourcePath = Path.Combine(tempDir, $"{className}.cs"); + var dllPath = Path.Combine(tempDir, $"{className}.dll"); + + Directory.CreateDirectory(tempDir); + + try + { + var source = WrapUserCode(className, request.code, request.declarations); + await File.WriteAllTextAsync(sourcePath, source); + + await CompileAsync(sourcePath, dllPath, cancellationToken); + + var assembly = System.Reflection.Assembly.Load(await File.ReadAllBytesAsync(dllPath)); + var type = assembly.GetType(className); + var method = type.GetMethod("Execute", BindingFlags.Public | BindingFlags.Static); + object result; + + try + { + var returnValue = method.Invoke(null, new object[] { cancellationToken }); + if (returnValue is Task taskObj) + { + result = await taskObj.WithCancellation(cancellationToken); + } + else if (returnValue is Task task) + { + await task.WithCancellation(cancellationToken); + result = null; + } + else + { + result = returnValue; + } + } + catch (TargetInvocationException ex) + { + var inner = ex.InnerException ?? ex; + throw new CommandFailedException( + $"Runtime error: {inner.Message}", + EvalResponse.FromError( + $"{inner.Message}\n{inner.StackTrace}", + inner.GetType().FullName)); + } + catch (CommandFailedException) + { + throw; + } + catch (Exception ex) + { + throw new CommandFailedException( + $"Runtime error: {ex.Message}", + EvalResponse.FromError( + $"{ex.Message}\n{ex.StackTrace}", + ex.GetType().FullName)); + } + + return EvalResponse.FromResult(result); + } + finally + { + if (File.Exists(sourcePath)) File.Delete(sourcePath); + if (File.Exists(dllPath)) File.Delete(dllPath); + } + } + + private static string WrapUserCode(string className, string userCode, string declarations) + { + return +$@"#pragma warning disable CS0162 // Unreachable code detected +#pragma warning disable CS1998 // Async method lacks await +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +using UnityEditor; + +{declarations ?? ""} + +public static class {className} +{{ + public static async System.Threading.Tasks.Task Execute(System.Threading.CancellationToken cancellationToken) + {{ + {userCode} + return null; + }} +}}"; + } + + private static string[] GetAdditionalReferences() + { + var seen = new System.Collections.Generic.HashSet(StringComparer.OrdinalIgnoreCase); + var refs = new System.Collections.Generic.List(); + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + if (asm.IsDynamic) continue; + try + { + var loc = asm.Location; + if (string.IsNullOrEmpty(loc)) continue; + if (!File.Exists(loc)) continue; + if (!seen.Add(asm.GetName().Name)) continue; + refs.Add(loc); + } + catch + { + // some assemblies may throw on Location access + } + } + return refs.ToArray(); + } + + private static async Task CompileAsync(string sourcePath, string dllPath, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + + var builder = new AssemblyBuilder(dllPath, sourcePath) + { + referencesOptions = ReferencesOptions.UseEngineModules, + additionalReferences = GetAdditionalReferences() + }; + + builder.buildFinished += (path, messages) => { tcs.SetResult(messages); }; + + if (!builder.Build()) + throw new CommandFailedException("AssemblyBuilder.Build() failed to start", + EvalResponse.FromError("Failed to start compilation", "CompileError")); + + var messages = await tcs.Task.WithCancellation(cancellationToken); + + var errors = new System.Collections.Generic.List(); + foreach (var msg in messages) + { + if (msg.type == CompilerMessageType.Error) + errors.Add(msg.message); + } + + if (errors.Count > 0) + { + var errorText = string.Join("\n", errors); + throw new CommandFailedException( + $"Compilation failed with {errors.Count} error(s)", + EvalResponse.FromError(errorText, "CompileError")); + } + } + } + + [Serializable] + public class EvalRequest + { + public string code; + public string declarations; + } + + public class EvalResponse : IRawJsonResponse + { + public string ResultJson { get; private set; } + public string ResultType { get; private set; } + public string ResultRaw { get; private set; } + + public static EvalResponse FromResult(object result) + { + if (result == null) + return new EvalResponse { ResultJson = "null", ResultType = "null", ResultRaw = "null" }; + + var typeName = result.GetType().FullName; + var (json, raw) = SerializeToJson(result); + return new EvalResponse { ResultJson = json, ResultType = typeName, ResultRaw = raw }; + } + + public static EvalResponse FromError(string message, string errorType) + { + return new EvalResponse + { + ResultJson = EscapeJsonString(message), + ResultType = errorType, + ResultRaw = message + }; + } + + public string ToJson() + { + var sb = new StringBuilder(); + sb.Append("{\"result\":"); + sb.Append(ResultJson); + sb.Append(",\"resultType\":"); + sb.Append(EscapeJsonString(ResultType)); + sb.Append('}'); + return sb.ToString(); + } + + private static (string json, string raw) SerializeToJson(object result) + { + if (result is string s) + return (EscapeJsonString(s), s); + + if (result is bool b) + { + var val = b ? "true" : "false"; + return (val, val); + } + + if (result is int or long or short or byte or sbyte or uint or ulong or ushort) + { + var val = result.ToString(); + return (val, val); + } + + if (result is float f) + { + var val = f.ToString("G9"); + return (val, val); + } + + if (result is double d) + { + var val = d.ToString("G17"); + return (val, val); + } + + if (result is UnityEngine.Object unityObj) + { + var json = EditorJsonUtility.ToJson(unityObj, true); + return (json, json); + } + + try + { + var json = ReflectionSerialize(result); + return (json, json); + } + catch + { + // fall through to ToString + } + + var str = result.ToString(); + return (EscapeJsonString(str), str); + } + + private static string ReflectionSerialize(object obj) + { + if (obj == null) return "null"; + + var type = obj.GetType(); + + if (type == typeof(string)) + return EscapeJsonString((string)obj); + + if (type == typeof(bool)) + return (bool)obj ? "true" : "false"; + + if (type.IsPrimitive) + { + if (type == typeof(float)) + return ((float)obj).ToString("G9"); + if (type == typeof(double)) + return ((double)obj).ToString("G17"); + return obj.ToString(); + } + + if (type.IsEnum) + return EscapeJsonString(obj.ToString()); + + if (type.IsArray) + { + var array = (Array)obj; + var sb = new StringBuilder(); + sb.Append('['); + for (int i = 0; i < array.Length; i++) + { + if (i > 0) sb.Append(','); + sb.Append(ReflectionSerialize(array.GetValue(i))); + } + sb.Append(']'); + return sb.ToString(); + } + + if (obj is System.Collections.IList list) + { + var sb = new StringBuilder(); + sb.Append('['); + for (int i = 0; i < list.Count; i++) + { + if (i > 0) sb.Append(','); + sb.Append(ReflectionSerialize(list[i])); + } + sb.Append(']'); + return sb.ToString(); + } + + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + if (fields.Length > 0) + { + var sb = new StringBuilder(); + sb.Append('{'); + var first = true; + foreach (var field in fields) + { + if (!first) sb.Append(','); + first = false; + sb.Append('"'); + sb.Append(field.Name); + sb.Append("\":"); + sb.Append(ReflectionSerialize(field.GetValue(obj))); + } + sb.Append('}'); + return sb.ToString(); + } + + return EscapeJsonString(obj.ToString()); + } + + private static string EscapeJsonString(string s) + { + if (s == null) return "null"; + + var sb = new StringBuilder(s.Length + 2); + sb.Append('"'); + foreach (var c in s) + { + switch (c) + { + case '"': sb.Append("\\\""); break; + case '\\': sb.Append("\\\\"); break; + case '\n': sb.Append("\\n"); break; + case '\r': sb.Append("\\r"); break; + case '\t': sb.Append("\\t"); break; + default: + if (c < 0x20) + sb.AppendFormat("\\u{0:x4}", (int)c); + else + sb.Append(c); + break; + } + } + sb.Append('"'); + return sb.ToString(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/EvalHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 502b87931082a4f29b26ce8acfe8be20 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject.meta`: + +```meta +fileFormatVersion: 2 +guid: 70e84dd466f4745faaa10fe0cdf2c868 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/AddComponentHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class AddComponentHandler : CommandHandler + { + public override string CommandName => "GameObject.AddComponent"; + public override string Description => "Add a component to a GameObject by type name"; + + protected override bool TryWriteFormatted(AddComponentResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Added {response.typeName} to {response.gameObjectName}"); + else + writer.WriteLine("Failed to add component"); + + return true; + } + + protected override ValueTask ExecuteAsync(AddComponentRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.typeName)) + throw new ArgumentException("typeName is required"); + + var componentType = ResolveComponentType(request.typeName); + + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new AddComponentResponse()); + } + + var component = Undo.AddComponent(go, componentType); + if (component == null) + { + throw new CommandFailedException( + $"Failed to add component {componentType.FullName} to {go.name}", + new AddComponentResponse()); + } + + return new ValueTask(new AddComponentResponse + { + gameObjectName = go.name, + typeName = componentType.FullName, + instanceId = component.GetInstanceID(), + enabled = component is not Behaviour behaviour || behaviour.enabled + }); + } + + private static Type ResolveComponentType(string typeName) + { + // 1. Try Type.GetType for assembly-qualified names + var type = Type.GetType(typeName); + if (type != null && typeof(Component).IsAssignableFrom(type)) + return type; + + // 2. Search all assemblies by full name + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + type = assembly.GetType(typeName); + if (type != null && typeof(Component).IsAssignableFrom(type)) + return type; + } + + // 3. If no dot in name, search by short name across all assemblies + if (!typeName.Contains(".")) + { + var candidates = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + Type[] types; + try + { + types = assembly.GetTypes(); + } + catch (System.Reflection.ReflectionTypeLoadException e) + { + types = e.Types; + } + + foreach (var t in types) + { + if (t != null && t.Name == typeName && typeof(Component).IsAssignableFrom(t)) + candidates.Add(t); + } + } + + if (candidates.Count == 1) + return candidates[0]; + + if (candidates.Count > 1) + { + throw new CommandFailedException( + $"Ambiguous type name \"{typeName}\". Candidates: {candidates[0].FullName}, {candidates[1].FullName}", + new AddComponentResponse()); + } + } + + throw new CommandFailedException( + $"Component type \"{typeName}\" not found", + new AddComponentResponse()); + } + } + + [Serializable] + public class AddComponentRequest + { + public int instanceId; + public string path = ""; + public string typeName; + } + + [Serializable] + public class AddComponentResponse + { + public string gameObjectName; + public string typeName; + public int instanceId; + public bool enabled; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/AddComponentHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 64fec2305081a4786b46227d09f066aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/CreateGameObjectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class CreateGameObjectHandler : CommandHandler + { + public override string CommandName => "GameObject.Create"; + public override string Description => "Create a new GameObject in the scene"; + + protected override bool TryWriteFormatted(CreateGameObjectResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Created GameObject \"{response.name}\" (instanceId={response.instanceId})"); + else + writer.WriteLine("Failed to create GameObject"); + + return true; + } + + protected override ValueTask ExecuteAsync(CreateGameObjectRequest request, CancellationToken cancellationToken) + { + var name = string.IsNullOrEmpty(request.name) ? "GameObject" : request.name; + + var go = new GameObject(name); + Undo.RegisterCreatedObjectUndo(go, $"Create {name}"); + + if (!string.IsNullOrEmpty(request.parent)) + { + var parentGo = GameObjectResolver.ResolveByPath(request.parent); + if (parentGo == null) + { + UnityEngine.Object.DestroyImmediate(go); + throw new CommandFailedException( + $"Parent GameObject not found: \"{request.parent}\"", + new CreateGameObjectResponse()); + } + + go.transform.SetParent(parentGo.transform, false); + } + + if (request.components != null) + { + foreach (var typeName in request.components) + { + if (string.IsNullOrEmpty(typeName)) continue; + + var componentType = ResolveComponentType(typeName); + var component = go.AddComponent(componentType); + if (component == null) + { + throw new CommandFailedException( + $"Failed to add component {componentType.FullName} to {go.name}", + new CreateGameObjectResponse()); + } + } + } + + var components = go.GetComponents(); + var componentNames = new List(components.Length); + foreach (var component in components) + { + if (component != null) + componentNames.Add(component.GetType().FullName); + } + + var path = GameObjectResolver.BuildPath(go.transform); + + return new ValueTask(new CreateGameObjectResponse + { + instanceId = go.GetInstanceID(), + name = go.name, + path = path, + isActive = go.activeSelf, + components = componentNames.ToArray() + }); + } + + private static Type ResolveComponentType(string typeName) + { + var type = Type.GetType(typeName); + if (type != null && typeof(Component).IsAssignableFrom(type)) + return type; + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + type = assembly.GetType(typeName); + if (type != null && typeof(Component).IsAssignableFrom(type)) + return type; + } + + if (!typeName.Contains(".")) + { + var candidates = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + Type[] types; + try + { + types = assembly.GetTypes(); + } + catch (System.Reflection.ReflectionTypeLoadException e) + { + types = e.Types; + } + + foreach (var t in types) + { + if (t != null && t.Name == typeName && typeof(Component).IsAssignableFrom(t)) + candidates.Add(t); + } + } + + if (candidates.Count == 1) + return candidates[0]; + + if (candidates.Count > 1) + { + throw new CommandFailedException( + $"Ambiguous type name \"{typeName}\". Candidates: {candidates[0].FullName}, {candidates[1].FullName}", + new CreateGameObjectResponse()); + } + } + + throw new CommandFailedException( + $"Component type \"{typeName}\" not found", + new CreateGameObjectResponse()); + } + } + + [Serializable] + public class CreateGameObjectRequest + { + public string name = ""; + public string parent = ""; + public string[] components; + } + + [Serializable] + public class CreateGameObjectResponse + { + public int instanceId; + public string name; + public string path; + public bool isActive; + public string[] components; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/CreateGameObjectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d065ddefd5fdb4330a2aa1787552b1ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/CreatePrimitiveHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class CreatePrimitiveHandler : CommandHandler + { + public override string CommandName => "GameObject.CreatePrimitive"; + public override string Description => "Create a primitive GameObject (Cube, Sphere, Capsule, Cylinder, Plane, Quad)"; + + protected override bool TryWriteFormatted(CreateGameObjectResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Created primitive \"{response.name}\" (instanceId={response.instanceId})"); + else + writer.WriteLine("Failed to create primitive"); + + return true; + } + + protected override ValueTask ExecuteAsync(CreatePrimitiveRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.primitiveType)) + throw new ArgumentException("primitiveType is required (Cube, Sphere, Capsule, Cylinder, Plane, Quad)"); + + if (!Enum.TryParse(request.primitiveType, true, out var primitiveType)) + { + throw new CommandFailedException( + $"Invalid primitiveType \"{request.primitiveType}\". Valid values: Cube, Sphere, Capsule, Cylinder, Plane, Quad", + new CreateGameObjectResponse()); + } + + var go = GameObject.CreatePrimitive(primitiveType); + Undo.RegisterCreatedObjectUndo(go, $"Create {primitiveType}"); + + if (!string.IsNullOrEmpty(request.name)) + go.name = request.name; + + if (!string.IsNullOrEmpty(request.parent)) + { + var parentGo = GameObjectResolver.ResolveByPath(request.parent); + if (parentGo == null) + { + UnityEngine.Object.DestroyImmediate(go); + throw new CommandFailedException( + $"Parent GameObject not found: \"{request.parent}\"", + new CreateGameObjectResponse()); + } + + go.transform.SetParent(parentGo.transform, false); + } + + var components = go.GetComponents(); + var componentNames = new List(components.Length); + foreach (var component in components) + { + if (component != null) + componentNames.Add(component.GetType().FullName); + } + + return new ValueTask(new CreateGameObjectResponse + { + instanceId = go.GetInstanceID(), + name = go.name, + path = GameObjectResolver.BuildPath(go.transform), + isActive = go.activeSelf, + components = componentNames.ToArray() + }); + } + } + + [Serializable] + public class CreatePrimitiveRequest + { + public string primitiveType = ""; + public string name = ""; + public string parent = ""; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/CreatePrimitiveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c6e0eb79e92942439db31ad66845d290 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/DestroyGameObjectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class DestroyGameObjectHandler : CommandHandler + { + public override string CommandName => "GameObject.Destroy"; + public override string Description => "Destroy a GameObject from the scene"; + + protected override bool TryWriteFormatted(DestroyGameObjectResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Destroyed GameObject \"{response.name}\" (instanceId={response.instanceId})"); + else + writer.WriteLine("Failed to destroy GameObject"); + + return true; + } + + protected override ValueTask ExecuteAsync(DestroyGameObjectRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new DestroyGameObjectResponse()); + } + + var name = go.name; + var instanceId = go.GetInstanceID(); + + Undo.DestroyObjectImmediate(go); + + return new ValueTask(new DestroyGameObjectResponse + { + name = name, + instanceId = instanceId + }); + } + } + + [Serializable] + public class DestroyGameObjectRequest + { + public int instanceId; + public string path = ""; + } + + [Serializable] + public class DestroyGameObjectResponse + { + public string name; + public int instanceId; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/DestroyGameObjectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 5ae704ff0e2649908e9521930415217b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/DuplicateGameObjectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class DuplicateGameObjectHandler : CommandHandler + { + public override string CommandName => "GameObject.Duplicate"; + public override string Description => "Duplicate an existing GameObject in the scene"; + + protected override bool TryWriteFormatted(CreateGameObjectResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Duplicated GameObject \"{response.name}\" (instanceId={response.instanceId})"); + else + writer.WriteLine("Failed to duplicate GameObject"); + + return true; + } + + protected override ValueTask ExecuteAsync(DuplicateGameObjectRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new CreateGameObjectResponse()); + } + + var duplicate = UnityEngine.Object.Instantiate(go, go.transform.parent); + Undo.RegisterCreatedObjectUndo(duplicate, $"Duplicate {go.name}"); + + if (!string.IsNullOrEmpty(request.name)) + duplicate.name = request.name; + + var components = duplicate.GetComponents(); + var componentNames = new List(components.Length); + foreach (var component in components) + { + if (component != null) + componentNames.Add(component.GetType().FullName); + } + + return new ValueTask(new CreateGameObjectResponse + { + instanceId = duplicate.GetInstanceID(), + name = duplicate.name, + path = GameObjectResolver.BuildPath(duplicate.transform), + isActive = duplicate.activeSelf, + components = componentNames.ToArray() + }); + } + } + + [Serializable] + public class DuplicateGameObjectRequest + { + public int instanceId; + public string path = ""; + public string name = ""; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/DuplicateGameObjectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b652709668224f5d88e384764e6ef118 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/FindGameObjectsHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class FindGameObjectsHandler : CommandHandler + { + public override string CommandName => "GameObject.Find"; + public override string Description => "Find GameObjects by name, tag, layer, or component"; + + protected override ValueTask ExecuteAsync(FindGameObjectsRequest request, CancellationToken cancellationToken) + { + var results = new List(); + var totalFound = 0; + + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage != null) + { + SearchGameObject(prefabStage.prefabContentsRoot, prefabStage.scene.name, "", request, results, ref totalFound); + } + else + { + for (var i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (!scene.isLoaded) continue; + + var rootObjects = scene.GetRootGameObjects(); + foreach (var root in rootObjects) + { + SearchGameObject(root, scene.name, "", request, results, ref totalFound); + } + } + } + + return new ValueTask(new FindGameObjectsResponse + { + results = results.ToArray(), + totalFound = totalFound + }); + } + + private static void SearchGameObject( + GameObject go, + string sceneName, + string parentPath, + FindGameObjectsRequest request, + List results, + ref int totalFound) + { + if (!request.includeInactive && !go.activeInHierarchy) return; + + var path = string.IsNullOrEmpty(parentPath) ? go.name : $"{parentPath}/{go.name}"; + + if (MatchesFilter(go, request)) + { + totalFound++; + + if (results.Count < request.maxResults) + { + var components = go.GetComponents(); + var componentNames = new List(components.Length); + foreach (var component in components) + { + if (component != null) + { + componentNames.Add(component.GetType().FullName); + } + } + + results.Add(new GameObjectResult + { + instanceId = go.GetInstanceID(), + name = go.name, + path = path, + isActive = go.activeSelf, + components = componentNames.ToArray(), + sceneName = sceneName, + tag = go.tag, + layer = go.layer + }); + } + } + + var transform = go.transform; + for (var i = 0; i < transform.childCount; i++) + { + SearchGameObject(transform.GetChild(i).gameObject, sceneName, path, request, results, ref totalFound); + } + } + + private static bool MatchesFilter(GameObject go, FindGameObjectsRequest request) + { + if (!string.IsNullOrEmpty(request.namePattern)) + { + if (!go.name.Contains(request.namePattern, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(request.tag)) + { + if (!go.CompareTag(request.tag)) + { + return false; + } + } + + if (request.layer >= 0) + { + if (go.layer != request.layer) + { + return false; + } + } + + if (request.requiredComponents != null && request.requiredComponents.Length > 0) + { + foreach (var componentName in request.requiredComponents) + { + if (!HasComponent(go, componentName)) + { + return false; + } + } + } + + return true; + } + + private static bool HasComponent(GameObject go, string typeName) + { + var components = go.GetComponents(); + foreach (var component in components) + { + if (component == null) continue; + + var type = component.GetType(); + if (type.FullName == typeName || type.Name == typeName) + { + return true; + } + } + return false; + } + } + + [Serializable] + public class FindGameObjectsRequest + { + public string namePattern; + public string tag; + public int layer = -1; + public string[] requiredComponents; + public bool includeInactive; + public int maxResults = 100; + } + + [Serializable] + public class FindGameObjectsResponse + { + public GameObjectResult[] results; + public int totalFound; + } + + [Serializable] + public class GameObjectResult + { + public int instanceId; + public string name; + public string path; + public bool isActive; + public string[] components; + public string sceneName; + public string tag; + public int layer; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/FindGameObjectsHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4c7a75b4318354d5fba83cadcc881f02 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/GetComponentsHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class GetComponentsHandler : CommandHandler + { + public override string CommandName => "GameObject.GetComponents"; + public override string Description => "Get detailed component information for a GameObject"; + + protected override ValueTask ExecuteAsync(GetComponentsRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new GetComponentsResponse { components = Array.Empty() }); + } + + var components = go.GetComponents(); + var details = new List(components.Length); + + foreach (var component in components) + { + if (component == null) continue; + + var detail = new ComponentDetail + { + instanceId = component.GetInstanceID(), + typeName = component.GetType().FullName, + enabled = component is not Behaviour behaviour || behaviour.enabled, + properties = ExtractProperties(component) + }; + + details.Add(detail); + } + + return new ValueTask(new GetComponentsResponse + { + components = details.ToArray() + }); + } + + private static SerializedPropertyInfo[] ExtractProperties(Component component) + { + var result = new List(); + + using var serializedObject = new SerializedObject(component); + var iterator = serializedObject.GetIterator(); + + if (!iterator.NextVisible(true)) return Array.Empty(); + + do + { + result.Add(new SerializedPropertyInfo + { + name = iterator.name, + type = iterator.propertyType.ToString(), + value = GetPropertyValueString(iterator) + }); + } while (iterator.NextVisible(false)); + + return result.ToArray(); + } + + private static string GetPropertyValueString(SerializedProperty property) + { + switch (property.propertyType) + { + case SerializedPropertyType.Integer: + return property.intValue.ToString(); + case SerializedPropertyType.Boolean: + return property.boolValue.ToString().ToLowerInvariant(); + case SerializedPropertyType.Float: + return property.floatValue.ToString("G"); + case SerializedPropertyType.String: + return property.stringValue ?? ""; + case SerializedPropertyType.Enum: + return property.enumNames.Length > property.enumValueIndex && property.enumValueIndex >= 0 + ? property.enumNames[property.enumValueIndex] + : property.enumValueIndex.ToString(); + case SerializedPropertyType.ObjectReference: + return property.objectReferenceValue != null + ? property.objectReferenceValue.name + : "(null)"; + case SerializedPropertyType.Vector2: + return property.vector2Value.ToString(); + case SerializedPropertyType.Vector3: + return property.vector3Value.ToString(); + case SerializedPropertyType.Vector4: + return property.vector4Value.ToString(); + case SerializedPropertyType.Color: + return property.colorValue.ToString(); + case SerializedPropertyType.Rect: + return property.rectValue.ToString(); + case SerializedPropertyType.Bounds: + return property.boundsValue.ToString(); + case SerializedPropertyType.Quaternion: + return property.quaternionValue.ToString(); + case SerializedPropertyType.LayerMask: + return property.intValue.ToString(); + default: + return $"({property.propertyType})"; + } + } + } + + [Serializable] + public class GetComponentsRequest + { + public int instanceId; + public string path = ""; + } + + [Serializable] + public class GetComponentsResponse + { + public ComponentDetail[] components; + } + + [Serializable] + public class ComponentDetail + { + public int instanceId; + public string typeName; + public bool enabled; + public SerializedPropertyInfo[] properties; + } + + [Serializable] + public class SerializedPropertyInfo + { + public string name; + public string type; + public string value; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/GetComponentsHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/GetHierarchyHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class GetHierarchyHandler : CommandHandler + { + public override string CommandName => "GameObject.GetHierarchy"; + public override string Description => "Get the scene hierarchy of GameObjects"; + + protected override bool TryWriteFormatted(GetHierarchyResponse response, bool success, IFormatWriter writer) + { + if (!success || response.scenes == null || response.scenes.Length == 0) + { + writer.WriteLine("No scenes found."); + return true; + } + + for (var s = 0; s < response.scenes.Length; s++) + { + var scene = response.scenes[s]; + if (s > 0) writer.WriteLine(""); + writer.WriteLine($"[{scene.name}]"); + + if (scene.nodes == null) continue; + foreach (var node in scene.nodes) + WriteNode(node, 0, writer); + } + + return true; + } + + private static void WriteNode(HierarchyNode node, int indent, IFormatWriter writer) + { + var prefix = new string(' ', indent * 2) + node.name; + + if (!node.isActive) + prefix += " (inactive)"; + + if (node.components != null && node.components.Length > 0) + { + var names = new string[node.components.Length]; + for (var i = 0; i < node.components.Length; i++) + { + var fullName = node.components[i]; + var dotIndex = fullName.LastIndexOf('.'); + names[i] = dotIndex >= 0 ? fullName.Substring(dotIndex + 1) : fullName; + } + prefix += $" [{string.Join(", ", names)}]"; + } + + writer.WriteLine(prefix); + + if (node.children == null) return; + foreach (var child in node.children) + WriteNode(child, indent + 1, writer); + } + + protected override ValueTask ExecuteAsync(GetHierarchyRequest request, CancellationToken cancellationToken) + { + var scenes = new List(); + + var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage != null) + { + var nodes = new List(); + var node = CollectNode(prefabStage.prefabContentsRoot, 0, request); + if (node != null) nodes.Add(node); + + scenes.Add(new HierarchyScene + { + name = prefabStage.scene.name, + nodes = nodes.ToArray() + }); + } + else + { + for (var i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (!scene.isLoaded) continue; + + var nodes = new List(); + var rootObjects = scene.GetRootGameObjects(); + foreach (var root in rootObjects) + { + var node = CollectNode(root, 0, request); + if (node != null) nodes.Add(node); + } + + scenes.Add(new HierarchyScene + { + name = scene.name, + nodes = nodes.ToArray() + }); + } + } + + return new ValueTask(new GetHierarchyResponse + { + scenes = scenes.ToArray() + }); + } + + private static HierarchyNode CollectNode( + GameObject go, + int depth, + GetHierarchyRequest request) + { + if (!request.includeInactive && !go.activeInHierarchy) return null; + if (request.maxDepth >= 0 && depth > request.maxDepth) return null; + + var node = new HierarchyNode + { + instanceId = go.GetInstanceID(), + name = go.name, + depth = depth, + isActive = go.activeSelf, + tag = go.tag, + layer = go.layer + }; + + if (request.includeComponents) + { + var components = go.GetComponents(); + var componentNames = new List(components.Length); + foreach (var component in components) + { + if (component != null) + componentNames.Add(component.GetType().FullName); + } + node.components = componentNames.ToArray(); + } + else + { + node.components = Array.Empty(); + } + + var children = new List(); + var transform = go.transform; + for (var i = 0; i < transform.childCount; i++) + { + var child = CollectNode(transform.GetChild(i).gameObject, depth + 1, request); + if (child != null) children.Add(child); + } + node.children = children.ToArray(); + + return node; + } + } + + [Serializable] + public class GetHierarchyRequest + { + public bool includeInactive; + public int maxDepth = -1; + public bool includeComponents = true; + } + + [Serializable] + public class GetHierarchyResponse + { + public HierarchyScene[] scenes; + } + + [Serializable] + public class HierarchyScene + { + public string name; + public HierarchyNode[] nodes; + } + + [Serializable] + public class HierarchyNode + { + public int instanceId; + public string name; + public int depth; + public bool isActive; + public string[] components; + public HierarchyNode[] children; + public string tag; + public int layer; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/GetHierarchyHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 49fad92ced0664411b0dece200dec347 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/RemoveComponentHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class RemoveComponentHandler : CommandHandler + { + public override string CommandName => "GameObject.RemoveComponent"; + public override string Description => "Remove a component from a GameObject by instance ID"; + + protected override bool TryWriteFormatted(RemoveComponentResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Removed {response.typeName} from {response.gameObjectName}"); + else + writer.WriteLine("Failed to remove component"); + + return true; + } + + protected override ValueTask ExecuteAsync(RemoveComponentRequest request, CancellationToken cancellationToken) + { + if (request.componentInstanceId == 0) + throw new ArgumentException("componentInstanceId is required"); + + var obj = EditorUtility.InstanceIDToObject(request.componentInstanceId); + if (obj is not Component component) + { + throw new CommandFailedException( + $"Component not found for instanceId={request.componentInstanceId}", + new RemoveComponentResponse()); + } + + if (component is Transform) + { + throw new CommandFailedException( + "Cannot remove Transform component", + new RemoveComponentResponse()); + } + + var goName = component.gameObject.name; + var typeName = component.GetType().FullName; + var instanceId = component.GetInstanceID(); + + Undo.DestroyObjectImmediate(component); + + return new ValueTask(new RemoveComponentResponse + { + gameObjectName = goName, + typeName = typeName, + componentInstanceId = instanceId + }); + } + } + + [Serializable] + public class RemoveComponentRequest + { + public int componentInstanceId; + } + + [Serializable] + public class RemoveComponentResponse + { + public string gameObjectName; + public string typeName; + public int componentInstanceId; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/RemoveComponentHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: a7b3e4f1c8d24a6e9f0b5c7d8e1a2b3c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/RenameGameObjectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class RenameGameObjectHandler : CommandHandler + { + public override string CommandName => "GameObject.Rename"; + public override string Description => "Rename a GameObject"; + + protected override bool TryWriteFormatted(RenameGameObjectResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Renamed \"{response.previousName}\" to \"{response.name}\""); + else + writer.WriteLine("Failed to rename GameObject"); + + return true; + } + + protected override ValueTask ExecuteAsync(RenameGameObjectRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new RenameGameObjectResponse()); + } + + var previousName = go.name; + Undo.RecordObject(go, $"Rename {previousName}"); + go.name = request.name; + + return new ValueTask(new RenameGameObjectResponse + { + previousName = previousName, + name = go.name, + instanceId = go.GetInstanceID() + }); + } + } + + [Serializable] + public class RenameGameObjectRequest + { + public int instanceId; + public string path = ""; + public string name = ""; + } + + [Serializable] + public class RenameGameObjectResponse + { + public string previousName; + public string name; + public int instanceId; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/RenameGameObjectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 635d547b671b45bba67875e8f5523435 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/SetActiveHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class SetActiveHandler : CommandHandler + { + public override string CommandName => "GameObject.SetActive"; + public override string Description => "Set active state of a GameObject"; + + protected override ValueTask ExecuteAsync(SetActiveRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new SetActiveResponse { name = "", previousState = false, currentState = false }); + } + + var previousState = go.activeSelf; + + Undo.RecordObject(go, $"SetActive {go.name}"); + go.SetActive(request.active); + + return new ValueTask(new SetActiveResponse + { + name = go.name, + previousState = previousState, + currentState = go.activeSelf + }); + } + } + + [Serializable] + public class SetActiveRequest + { + public int instanceId; + public string path = ""; + public bool active; + } + + [Serializable] + public class SetActiveResponse + { + public string name; + public bool previousState; + public bool currentState; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/SetActiveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/SetParentHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class SetParentHandler : CommandHandler + { + public override string CommandName => "GameObject.SetParent"; + public override string Description => "Change the parent of a GameObject (or move to root)"; + + protected override bool TryWriteFormatted(SetParentResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Moved \"{response.name}\" to \"{response.path}\""); + else + writer.WriteLine("Failed to set parent"); + + return true; + } + + protected override ValueTask ExecuteAsync(SetParentRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new SetParentResponse()); + } + + Transform newParent = null; + if (request.parentInstanceId != 0 || !string.IsNullOrEmpty(request.parentPath)) + { + var parentGo = GameObjectResolver.ResolveByIdOrPath(request.parentInstanceId, request.parentPath); + if (parentGo == null) + { + throw new CommandFailedException( + $"Parent GameObject not found (instanceId={request.parentInstanceId}, path=\"{request.parentPath}\")", + new SetParentResponse()); + } + + newParent = parentGo.transform; + } + + Undo.SetTransformParent(go.transform, newParent, $"Set Parent {go.name}"); + + if (!request.worldPositionStays) + go.transform.SetParent(newParent, false); + + return new ValueTask(new SetParentResponse + { + instanceId = go.GetInstanceID(), + name = go.name, + path = GameObjectResolver.BuildPath(go.transform) + }); + } + } + + [Serializable] + public class SetParentRequest + { + public int instanceId; + public string path = ""; + public int parentInstanceId; + public string parentPath = ""; + public bool worldPositionStays = true; + } + + [Serializable] + public class SetParentResponse + { + public int instanceId; + public string name; + public string path; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/SetParentHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1d68dc2dec6545f1997c438c13d4bc2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/SetTransformHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("GameObject")] + public sealed class SetTransformHandler : CommandHandler + { + public override string CommandName => "GameObject.SetTransform"; + public override string Description => "Set the local transform (position, rotation, scale) of a GameObject"; + + protected override bool TryWriteFormatted(SetTransformResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Updated transform of \"{response.name}\""); + else + writer.WriteLine("Failed to set transform"); + + return true; + } + + protected override ValueTask ExecuteAsync(SetTransformRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new SetTransformResponse()); + } + + var transform = go.transform; + Undo.RecordObject(transform, $"Set Transform {go.name}"); + + if (request.position != null && request.position.Length == 3) + transform.localPosition = new Vector3(request.position[0], request.position[1], request.position[2]); + + if (request.rotation != null && request.rotation.Length == 3) + transform.localEulerAngles = new Vector3(request.rotation[0], request.rotation[1], request.rotation[2]); + + if (request.localScale != null && request.localScale.Length == 3) + transform.localScale = new Vector3(request.localScale[0], request.localScale[1], request.localScale[2]); + + return new ValueTask(new SetTransformResponse + { + instanceId = go.GetInstanceID(), + name = go.name, + position = new[] { transform.localPosition.x, transform.localPosition.y, transform.localPosition.z }, + rotation = new[] { transform.localEulerAngles.x, transform.localEulerAngles.y, transform.localEulerAngles.z }, + localScale = new[] { transform.localScale.x, transform.localScale.y, transform.localScale.z } + }); + } + } + + [Serializable] + public class SetTransformRequest + { + public int instanceId; + public string path = ""; + public float[] position; + public float[] rotation; + public float[] localScale; + } + + [Serializable] + public class SetTransformResponse + { + public int instanceId; + public string name; + public float[] position; + public float[] rotation; + public float[] localScale; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/GameObject/SetTransformHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: cb38be8c8c7c4fd0a9edb8e67d89ae9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/ICommandHandler.cs`: + +```cs +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UniCli.Server.Editor.Internal; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public interface ICommandHandler + { + string CommandName { get; } + string Description { get; } + CommandInfo GetCommandInfo(); + ValueTask ExecuteAsync(object request, CancellationToken cancellationToken); + } + + public interface IResponseFormatter + { + bool TryWriteFormatted(object response, bool success, IFormatWriter writer); + } + + public interface IRawJsonResponse + { + string ToJson(); + } + + public abstract class CommandHandler : ICommandHandler, IResponseFormatter + { + public abstract string CommandName { get; } + public abstract string Description { get; } + + protected string ClientWorkingDirectory { get; private set; } + + public CommandInfo GetCommandInfo() + { + var handlerType = GetType(); + var assemblyName = handlerType.Assembly.GetName().Name; + var requestMetadata = CommandFieldInfoExtractor.Extract(typeof(TRequest)); + var responseMetadata = CommandFieldInfoExtractor.Extract(typeof(TResponse)); + return new CommandInfo + { + name = CommandName, + description = Description, + builtIn = assemblyName.StartsWith("UniCli.Server.Editor"), + module = ModuleRegistry.ResolveModuleName(handlerType), + requestFields = requestMetadata.Fields, + responseFields = responseMetadata.Fields, + requestTypeDetails = requestMetadata.TypeDetails, + responseTypeDetails = responseMetadata.TypeDetails + }; + } + + public async ValueTask ExecuteAsync(object request, CancellationToken cancellationToken) + { + if (request is not CommandRequest commandRequest) + { + throw new System.ArgumentException($"Invalid request type. Expected CommandRequest, got {request?.GetType().Name ?? "null"}"); + } + + ClientWorkingDirectory = commandRequest.cwd ?? ""; + + TRequest typedRequest; + if (typeof(TRequest) == typeof(Unit)) + { + typedRequest = (TRequest)(object)Unit.Value; + } + else if (string.IsNullOrEmpty(commandRequest.data)) + { + typedRequest = JsonUtility.FromJson("{}"); + } + else + { + typedRequest = JsonUtility.FromJson(commandRequest.data); + if (typedRequest == null) + { + throw new System.ArgumentException($"Failed to deserialize request data to {typeof(TRequest).Name}"); + } + } + + return await ExecuteAsync(typedRequest, cancellationToken); + } + + protected string ResolvePath(string path) + { + if (string.IsNullOrEmpty(path) || Path.IsPathRooted(path)) + return path; + if (string.IsNullOrEmpty(ClientWorkingDirectory)) + return path; + return Path.Combine(ClientWorkingDirectory, path); + } + + public bool TryWriteFormatted(object response, bool success, IFormatWriter writer) + { + if (response is TResponse typed) + return TryWriteFormatted(typed, success, writer); + return false; + } + + protected virtual bool TryWriteFormatted(TResponse response, bool success, IFormatWriter writer) + { + return false; + } + + protected abstract ValueTask ExecuteAsync(TRequest request, CancellationToken cancellationToken); + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/ICommandHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c9b811d94a0164a52b81b22d48703643 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material.meta`: + +```meta +fileFormatVersion: 2 +guid: a96732205a69e4c73b9887445533dace +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/CreateMaterialHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class CreateMaterialHandler : CommandHandler + { + public override string CommandName => "Material.Create"; + public override string Description => "Create a new material asset"; + + protected override bool TryWriteFormatted(CreateMaterialResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Created material at {response.assetPath} (shader: {response.shaderName}, guid: {response.guid})"); + else + writer.WriteLine("Failed to create material"); + + return true; + } + + protected override ValueTask ExecuteAsync(CreateMaterialRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.assetPath)) + throw new ArgumentException("assetPath is required"); + + if (!request.assetPath.EndsWith(".mat")) + { + throw new CommandFailedException( + $"assetPath must end with .mat (got \"{request.assetPath}\")", + new CreateMaterialResponse()); + } + + var shader = ResolveShader(request.shader); + if (shader == null) + { + throw new CommandFailedException( + string.IsNullOrEmpty(request.shader) + ? "No default shader found" + : $"Shader not found: \"{request.shader}\"", + new CreateMaterialResponse()); + } + + var material = new Material(shader); + AssetDatabase.CreateAsset(material, request.assetPath); + AssetDatabase.SaveAssets(); + + var guid = AssetDatabase.AssetPathToGUID(request.assetPath); + + return new ValueTask(new CreateMaterialResponse + { + assetPath = request.assetPath, + guid = guid, + shaderName = shader.name + }); + } + + static Shader ResolveShader(string shaderName) + { + if (!string.IsNullOrEmpty(shaderName)) + return Shader.Find(shaderName); + + var pipeline = GraphicsSettings.currentRenderPipeline; + if (pipeline != null && pipeline.defaultShader != null) + return pipeline.defaultShader; + + return Shader.Find("Standard"); + } + } + + [Serializable] + public class CreateMaterialRequest + { + public string assetPath; + public string shader = ""; + } + + [Serializable] + public class CreateMaterialResponse + { + public string assetPath; + public string guid; + public string shaderName; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/CreateMaterialHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ea9b4a3d671844f6799326f763926c2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialGetColorHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class MaterialGetColorHandler : CommandHandler + { + public override string CommandName => "Material.GetColor"; + public override string Description => "Get a color property from a material (Material.GetColor)"; + + protected override ValueTask ExecuteAsync(MaterialGetColorRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.guid)) + throw new ArgumentException("guid is required"); + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name (shader property name) is required"); + + var assetPath = AssetDatabase.GUIDToAssetPath(request.guid); + if (string.IsNullOrEmpty(assetPath)) + throw new CommandFailedException($"Asset not found for GUID: {request.guid}", new MaterialGetColorResponse()); + + var material = AssetDatabase.LoadAssetAtPath(assetPath); + if (material == null) + throw new CommandFailedException($"Failed to load material at: {assetPath}", new MaterialGetColorResponse()); + + if (!material.HasProperty(request.name)) + throw new CommandFailedException($"Material does not have property: {request.name}", new MaterialGetColorResponse()); + + var color = material.GetColor(request.name); + + return new ValueTask(new MaterialGetColorResponse + { + guid = request.guid, + name = request.name, + value = new ColorValue { r = color.r, g = color.g, b = color.b, a = color.a } + }); + } + } + + [Serializable] + public class MaterialGetColorRequest + { + public string guid; + public string name; + } + + [Serializable] + public class MaterialGetColorResponse + { + public string guid; + public string name; + public ColorValue value; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialGetColorHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 12b1f3b321ae44a9eb58cdf61bcb515b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialGetFloatHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class MaterialGetFloatHandler : CommandHandler + { + public override string CommandName => "Material.GetFloat"; + public override string Description => "Get a float property from a material (Material.GetFloat)"; + + protected override ValueTask ExecuteAsync(MaterialGetFloatRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.guid)) + throw new ArgumentException("guid is required"); + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name (shader property name) is required"); + + var assetPath = AssetDatabase.GUIDToAssetPath(request.guid); + if (string.IsNullOrEmpty(assetPath)) + throw new CommandFailedException($"Asset not found for GUID: {request.guid}", new MaterialGetFloatResponse()); + + var material = AssetDatabase.LoadAssetAtPath(assetPath); + if (material == null) + throw new CommandFailedException($"Failed to load material at: {assetPath}", new MaterialGetFloatResponse()); + + if (!material.HasProperty(request.name)) + throw new CommandFailedException($"Material does not have property: {request.name}", new MaterialGetFloatResponse()); + + var value = material.GetFloat(request.name); + + return new ValueTask(new MaterialGetFloatResponse + { + guid = request.guid, + name = request.name, + value = value + }); + } + } + + [Serializable] + public class MaterialGetFloatRequest + { + public string guid; + public string name; + } + + [Serializable] + public class MaterialGetFloatResponse + { + public string guid; + public string name; + public float value; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialGetFloatHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b2e4dc891e14f4967bf47c72771a5743 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialSetColorHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class MaterialSetColorHandler : CommandHandler + { + public override string CommandName => "Material.SetColor"; + public override string Description => "Set a color property on a material (Material.SetColor)"; + + protected override ValueTask ExecuteAsync(MaterialSetColorRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.guid)) + throw new ArgumentException("guid is required"); + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name (shader property name) is required"); + + var assetPath = AssetDatabase.GUIDToAssetPath(request.guid); + if (string.IsNullOrEmpty(assetPath)) + throw new CommandFailedException($"Asset not found for GUID: {request.guid}", new MaterialSetColorResponse()); + + var material = AssetDatabase.LoadAssetAtPath(assetPath); + if (material == null) + throw new CommandFailedException($"Failed to load material at: {assetPath}", new MaterialSetColorResponse()); + + if (!material.HasProperty(request.name)) + throw new CommandFailedException($"Material does not have property: {request.name}", new MaterialSetColorResponse()); + + var color = new Color(request.value.r, request.value.g, request.value.b, request.value.a); + material.SetColor(request.name, color); + EditorUtility.SetDirty(material); + + return new ValueTask(new MaterialSetColorResponse + { + guid = request.guid, + name = request.name, + value = request.value + }); + } + } + + [Serializable] + public class MaterialSetColorRequest + { + public string guid; + public string name; + public ColorValue value; + } + + [Serializable] + public class MaterialSetColorResponse + { + public string guid; + public string name; + public ColorValue value; + } + + [Serializable] + public class ColorValue + { + public float r; + public float g; + public float b; + public float a = 1f; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialSetColorHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1f828c09937bd42d380f07a4e71f3828 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialSetFloatHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class MaterialSetFloatHandler : CommandHandler + { + public override string CommandName => "Material.SetFloat"; + public override string Description => "Set a float property on a material (Material.SetFloat)"; + + protected override ValueTask ExecuteAsync(MaterialSetFloatRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.guid)) + throw new ArgumentException("guid is required"); + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name (shader property name) is required"); + + var assetPath = AssetDatabase.GUIDToAssetPath(request.guid); + if (string.IsNullOrEmpty(assetPath)) + throw new CommandFailedException($"Asset not found for GUID: {request.guid}", new MaterialSetFloatResponse()); + + var material = AssetDatabase.LoadAssetAtPath(assetPath); + if (material == null) + throw new CommandFailedException($"Failed to load material at: {assetPath}", new MaterialSetFloatResponse()); + + if (!material.HasProperty(request.name)) + throw new CommandFailedException($"Material does not have property: {request.name}", new MaterialSetFloatResponse()); + + material.SetFloat(request.name, request.value); + EditorUtility.SetDirty(material); + + return new ValueTask(new MaterialSetFloatResponse + { + guid = request.guid, + name = request.name, + value = request.value + }); + } + } + + [Serializable] + public class MaterialSetFloatRequest + { + public string guid; + public string name; + public float value; + } + + [Serializable] + public class MaterialSetFloatResponse + { + public string guid; + public string name; + public float value; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Material/MaterialSetFloatHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1e53972ddf0c0429fb794a40686d0197 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Menu.meta`: + +```meta +fileFormatVersion: 2 +guid: 027ae35af705840a29e54431736fc849 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Menu/ExecuteMenuItemHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ExecuteMenuItemHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public ExecuteMenuItemHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "Menu.Execute"; + public override string Description => "Execute a Unity Editor menu item by path"; + + protected override ValueTask ExecuteAsync(ExecuteMenuItemRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotCompiling); + + if (string.IsNullOrEmpty(request.menuItemPath)) + { + throw new ArgumentException("menuItemPath is required"); + } + + var executed = EditorApplication.ExecuteMenuItem(request.menuItemPath); + + return new ValueTask(new ExecuteMenuItemResponse + { + executed = executed, + menuItemPath = request.menuItemPath + }); + } + } + + [Serializable] + public class ExecuteMenuItemRequest + { + public string menuItemPath; + } + + [Serializable] + public class ExecuteMenuItemResponse + { + public bool executed; + public string menuItemPath; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Menu/ExecuteMenuItemHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c774f19f7855947489072715d2782793 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Menu/GetMenuItemsHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class GetMenuItemsHandler : CommandHandler + { + public override string CommandName => "Menu.List"; + public override string Description => "List available Unity Editor menu items with filtering"; + + protected override ValueTask ExecuteAsync(GetMenuItemsRequest request, CancellationToken cancellationToken) + { + var methods = TypeCache.GetMethodsWithAttribute(); + var items = new List(); + + foreach (var method in methods) + { + var attr = method.GetCustomAttribute(); + if (attr == null) continue; + if (attr.validate) continue; + + var path = attr.menuItem; + + if (!string.IsNullOrEmpty(request.filterText)) + { + var match = request.filterType switch + { + "startswith" => path.StartsWith(request.filterText, StringComparison.OrdinalIgnoreCase), + "exact" => path.Equals(request.filterText, StringComparison.OrdinalIgnoreCase), + _ => path.Contains(request.filterText, StringComparison.OrdinalIgnoreCase) + }; + + if (!match) continue; + } + + items.Add(new MenuItemInfo + { + path = path, + priority = attr.priority, + methodName = method.Name, + typeName = method.DeclaringType?.FullName ?? "" + }); + + if (request.maxCount > 0 && items.Count >= request.maxCount) + break; + } + + items.Sort((a, b) => string.Compare(a.path, b.path, StringComparison.Ordinal)); + + return new ValueTask(new GetMenuItemsResponse + { + items = items.ToArray(), + totalCount = methods.Count, + filteredCount = items.Count + }); + } + } + + [Serializable] + public class GetMenuItemsRequest + { + public string filterText = ""; + public string filterType = "contains"; + public int maxCount = 200; + } + + [Serializable] + public class GetMenuItemsResponse + { + public MenuItemInfo[] items; + public int totalCount; + public int filteredCount; + } + + [Serializable] + public class MenuItemInfo + { + public string path; + public int priority; + public string methodName; + public string typeName; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Menu/GetMenuItemsHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ff79654d8787e4367a0d06f5e9947000 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Module.meta`: + +```meta +fileFormatVersion: 2 +guid: 0498290b2f64844feb2b62513ce5e67c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Module/ModuleDisableHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ModuleDisableHandler : CommandHandler + { + private readonly UniCliSettings _settings; + private readonly IDispatcherReloader _reloader; + + public ModuleDisableHandler(UniCliSettings settings, IDispatcherReloader reloader) + { + _settings = settings; + _reloader = reloader; + } + + public override string CommandName => "Module.Disable"; + public override string Description => "Disable a module and reload the command dispatcher"; + + protected override ValueTask ExecuteAsync(ModuleNameRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + _settings.DisableModule(request.name); + _reloader.Reload(); + + return new ValueTask(Unit.Value); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Module/ModuleDisableHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 0bcbf71b325744612947c2800e00ffe3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Module/ModuleEnableHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ModuleEnableHandler : CommandHandler + { + private readonly UniCliSettings _settings; + private readonly IDispatcherReloader _reloader; + + public ModuleEnableHandler(UniCliSettings settings, IDispatcherReloader reloader) + { + _settings = settings; + _reloader = reloader; + } + + public override string CommandName => "Module.Enable"; + public override string Description => "Enable a module and reload the command dispatcher"; + + protected override ValueTask ExecuteAsync(ModuleNameRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + _settings.EnableModule(request.name); + _reloader.Reload(); + + return new ValueTask(Unit.Value); + } + } + + [Serializable] + public class ModuleNameRequest + { + public string name; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Module/ModuleEnableHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 50c3b7b800d024001801768a41bbc731 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Module/ModuleListHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ModuleListHandler : CommandHandler, IResponseFormatter + { + private readonly UniCliSettings _settings; + + public ModuleListHandler(UniCliSettings settings) + { + _settings = settings; + } + + public override string CommandName => "Module.List"; + public override string Description => "List all available modules and their enabled status"; + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var allNames = _settings.DiscoverAllModuleNames(); + + var registeredDescriptions = new Dictionary(); + foreach (var m in ModuleRegistry.All) + registeredDescriptions[m.Name] = m.Description; + + var items = new ModuleInfo[allNames.Length]; + for (var i = 0; i < allNames.Length; i++) + { + var name = allNames[i]; + registeredDescriptions.TryGetValue(name, out var description); + + items[i] = new ModuleInfo + { + name = name, + description = description ?? "", + enabled = _settings.IsModuleEnabled(name) + }; + } + + return new ValueTask(new ModuleListResponse { modules = items }); + } + + protected override bool TryWriteFormatted(ModuleListResponse response, bool success, IFormatWriter writer) + { + if (!success || response.modules == null) + return false; + + writer.WriteLine("Modules:"); + foreach (var m in response.modules) + { + var status = m.enabled ? "enabled" : "disabled"; + var desc = string.IsNullOrEmpty(m.description) ? "" : $" - {m.description}"; + writer.WriteLine($" [{status}] {m.name}{desc}"); + } + + return true; + } + } + + [Serializable] + public class ModuleListResponse + { + public ModuleInfo[] modules; + } + + [Serializable] + public class ModuleInfo + { + public string name; + public string description; + public bool enabled; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Module/ModuleListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d2381cc8dd8914341a6172950d0d5581 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity.meta`: + +```meta +fileFormatVersion: 2 +guid: 30834cf610f8c4985a7988bc9e6e1dc4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetAddSourceHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + [Module("NuGet")] + public sealed class NuGetAddSourceHandler : CommandHandler + { + public override string CommandName => "NuGet.AddSource"; + public override string Description => "Add a NuGet package source"; + + protected override bool TryWriteFormatted(NuGetAddSourceResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Added source '{response.name}' ({response.path})" + : "Failed to add source"); + return true; + } + + protected override ValueTask ExecuteAsync(NuGetAddSourceRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + if (string.IsNullOrEmpty(request.path)) + throw new ArgumentException("path is required"); + + NuGetConfigHelper.AddSource(request.name, request.path); + + return new ValueTask(new NuGetAddSourceResponse + { + name = request.name, + path = request.path, + }); + } + } + + [Serializable] + public class NuGetAddSourceRequest + { + public string name; + public string path; + } + + [Serializable] + public class NuGetAddSourceResponse + { + public string name; + public string path; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetAddSourceHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b9820e8484147415885c6cbf6614072a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetConfigHelper.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using NugetForUnity.Configuration; +using NugetForUnity.PackageSource; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + internal static class NuGetConfigHelper + { + private static readonly MethodInfo CreatePackageSourceMethod; + + static NuGetConfigHelper() + { + var creatorType = typeof(INugetPackageSource).Assembly + .GetType("NugetForUnity.PackageSource.NugetPackageSourceCreator"); + + CreatePackageSourceMethod = creatorType?.GetMethod( + "CreatePackageSource", + BindingFlags.Public | BindingFlags.Static); + + if (CreatePackageSourceMethod == null) + throw new InvalidOperationException( + "NugetPackageSourceCreator.CreatePackageSource not found. NuGetForUnity version may be incompatible."); + } + + public static void AddSource(string name, string path) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("name is required"); + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("path is required"); + + var config = ConfigurationManager.NugetConfigFile; + + var existing = config.PackageSources + .FirstOrDefault(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (existing != null) + throw new InvalidOperationException($"Package source '{name}' already exists"); + + var source = (INugetPackageSource)CreatePackageSourceMethod.Invoke( + null, + new object[] { name, path, null, config.PackageSources }); + + config.PackageSources.Add(source); + config.Save(ConfigurationManager.NugetConfigFilePath); + ConfigurationManager.LoadNugetConfigFile(); + } + + public static void RemoveSource(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("name is required"); + + var config = ConfigurationManager.NugetConfigFile; + + var source = config.PackageSources + .FirstOrDefault(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (source == null) + throw new InvalidOperationException($"Package source '{name}' not found"); + + config.PackageSources.Remove(source); + config.Save(ConfigurationManager.NugetConfigFilePath); + ConfigurationManager.LoadNugetConfigFile(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetConfigHelper.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: efdd51aabcbd849d489da3d105ca22ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetInstallHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using NugetForUnity; +using NugetForUnity.Models; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + [Module("NuGet")] + public sealed class NuGetInstallHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public NuGetInstallHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "NuGet.Install"; + public override string Description => "Install a NuGet package by id and optional version"; + + protected override bool TryWriteFormatted(NuGetInstallResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Installed {response.id}@{response.version}" + : "Failed to install package"); + return true; + } + + protected override ValueTask ExecuteAsync(NuGetInstallRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + if (string.IsNullOrEmpty(request.id)) + throw new ArgumentException("id is required"); + + var existing = InstalledPackagesManager.InstalledPackages + .FirstOrDefault(p => string.Equals(p.Id, request.id, StringComparison.OrdinalIgnoreCase)); + + if (existing != null && string.IsNullOrEmpty(request.version)) + throw new CommandFailedException( + $"Package {request.id}@{existing.Version} is already installed", + new NuGetInstallResponse { id = existing.Id, version = existing.Version }); + + var tempSourceName = !string.IsNullOrEmpty(request.source) + ? $"__unicli_tmp_{Guid.NewGuid():N}" + : null; + + try + { + if (tempSourceName != null) + NuGetConfigHelper.AddSource(tempSourceName, request.source); + + var identifier = string.IsNullOrEmpty(request.version) + ? new NugetPackageIdentifier(request.id, string.Empty) + : new NugetPackageIdentifier(request.id, request.version); + + var success = NugetPackageInstaller.InstallIdentifier(identifier); + + if (!success) + throw new CommandFailedException( + $"Failed to install package {request.id}", + new NuGetInstallResponse { id = request.id, version = request.version ?? "" }); + + var installed = InstalledPackagesManager.InstalledPackages + .FirstOrDefault(p => string.Equals(p.Id, request.id, StringComparison.OrdinalIgnoreCase)); + + return new ValueTask(new NuGetInstallResponse + { + id = installed?.Id ?? request.id, + version = installed?.Version ?? request.version ?? "", + }); + } + finally + { + if (tempSourceName != null) + { + try { NuGetConfigHelper.RemoveSource(tempSourceName); } + catch { /* best effort cleanup */ } + } + } + } + } + + [Serializable] + public class NuGetInstallRequest + { + public string id; + public string version; + public string source; + } + + [Serializable] + public class NuGetInstallResponse + { + public string id; + public string version; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetInstallHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 51e88dfefbe51483f9e608e87d49301f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetListHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using NugetForUnity; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + [Module("NuGet")] + public sealed class NuGetListHandler : CommandHandler + { + public override string CommandName => "NuGet.List"; + public override string Description => "List all installed NuGet packages"; + + protected override bool TryWriteFormatted(NuGetListResponse response, bool success, IFormatWriter writer) + { + var idWidth = "Id".Length; + var versionWidth = "Version".Length; + + foreach (var pkg in response.packages) + { + idWidth = Math.Max(idWidth, pkg.id.Length); + versionWidth = Math.Max(versionWidth, pkg.version.Length); + } + + writer.WriteLine($"{"Id".PadRight(idWidth)} {"Version".PadRight(versionWidth)}"); + + foreach (var pkg in response.packages) + { + writer.WriteLine($"{pkg.id.PadRight(idWidth)} {pkg.version.PadRight(versionWidth)}"); + } + + writer.WriteLine($"{response.totalCount} package(s)"); + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var packages = InstalledPackagesManager.InstalledPackages + .Select(p => new NuGetPackageEntry + { + id = p.Id, + version = p.Version, + }) + .OrderBy(p => p.id) + .ToArray(); + + return new ValueTask(new NuGetListResponse + { + packages = packages, + totalCount = packages.Length, + }); + } + } + + [Serializable] + public class NuGetListResponse + { + public NuGetPackageEntry[] packages; + public int totalCount; + } + + [Serializable] + public class NuGetPackageEntry + { + public string id; + public string version; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8188a30b66b094c698aafacc28329776 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetListSourcesHandler.cs`: + +```cs +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NugetForUnity.Configuration; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + [Module("NuGet")] + public sealed class NuGetListSourcesHandler : CommandHandler + { + public override string CommandName => "NuGet.ListSources"; + public override string Description => "List all configured NuGet package sources"; + + protected override bool TryWriteFormatted(NuGetListSourcesResponse response, bool success, IFormatWriter writer) + { + var nameWidth = "Name".Length; + var pathWidth = "Path".Length; + + foreach (var src in response.sources) + { + nameWidth = Math.Max(nameWidth, src.name.Length); + pathWidth = Math.Max(pathWidth, src.path.Length); + } + + writer.WriteLine($"{"Name".PadRight(nameWidth)} {"Path".PadRight(pathWidth)} Enabled"); + + foreach (var src in response.sources) + { + writer.WriteLine($"{src.name.PadRight(nameWidth)} {src.path.PadRight(pathWidth)} {src.isEnabled}"); + } + + writer.WriteLine($"{response.totalCount} source(s)"); + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var sources = ConfigurationManager.NugetConfigFile.PackageSources + .Select(s => new NuGetSourceEntry + { + name = s.Name, + path = s.SavedPath, + isEnabled = s.IsEnabled, + }) + .ToArray(); + + return new ValueTask(new NuGetListSourcesResponse + { + sources = sources, + totalCount = sources.Length, + }); + } + } + + [Serializable] + public class NuGetListSourcesResponse + { + public NuGetSourceEntry[] sources; + public int totalCount; + } + + [Serializable] + public class NuGetSourceEntry + { + public string name; + public string path; + public bool isEnabled; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetListSourcesHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4ccce0531caf34b9e8360411fc0648ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetRemoveSourceHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + [Module("NuGet")] + public sealed class NuGetRemoveSourceHandler : CommandHandler + { + public override string CommandName => "NuGet.RemoveSource"; + public override string Description => "Remove a NuGet package source"; + + protected override bool TryWriteFormatted(NuGetRemoveSourceResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Removed source '{response.name}'" + : "Failed to remove source"); + return true; + } + + protected override ValueTask ExecuteAsync(NuGetRemoveSourceRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + NuGetConfigHelper.RemoveSource(request.name); + + return new ValueTask(new NuGetRemoveSourceResponse + { + name = request.name, + }); + } + } + + [Serializable] + public class NuGetRemoveSourceRequest + { + public string name; + } + + [Serializable] + public class NuGetRemoveSourceResponse + { + public string name; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetRemoveSourceHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: cd606d4f0cabd485db795be998f223b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetRestoreHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using NugetForUnity; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + [Module("NuGet")] + public sealed class NuGetRestoreHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public NuGetRestoreHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "NuGet.Restore"; + public override string Description => "Restore all NuGet packages from packages.config"; + + protected override bool TryWriteFormatted(NuGetRestoreResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Restored {response.packageCount} package(s)" + : "Failed to restore packages"); + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + PackageRestorer.Restore(false); + + var packageCount = InstalledPackagesManager.InstalledPackages.Count(); + + return new ValueTask(new NuGetRestoreResponse + { + packageCount = packageCount, + }); + } + } + + [Serializable] + public class NuGetRestoreResponse + { + public int packageCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetRestoreHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8b8f76e8bb0824bfb85c4e2d4b3cf06b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetUninstallHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using NugetForUnity; +using NugetForUnity.Models; +using NugetForUnity.PluginAPI; + +namespace UniCli.Server.Editor.Handlers.NuGetForUnity +{ + [Module("NuGet")] + public sealed class NuGetUninstallHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public NuGetUninstallHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "NuGet.Uninstall"; + public override string Description => "Uninstall a NuGet package by id"; + + protected override bool TryWriteFormatted(NuGetUninstallResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Uninstalled {response.id}" + : "Failed to uninstall package"); + return true; + } + + protected override ValueTask ExecuteAsync(NuGetUninstallRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + if (string.IsNullOrEmpty(request.id)) + throw new ArgumentException("id is required"); + + var installed = InstalledPackagesManager.InstalledPackages + .FirstOrDefault(p => string.Equals(p.Id, request.id, StringComparison.OrdinalIgnoreCase)); + + if (installed == null) + throw new CommandFailedException( + $"Package {request.id} is not installed", + new NuGetUninstallResponse { id = request.id }); + + NugetPackageUninstaller.Uninstall( + new NugetPackageIdentifier(installed.Id, installed.Version), + PackageUninstallReason.IndividualUninstall); + + return new ValueTask(new NuGetUninstallResponse + { + id = installed.Id, + }); + } + } + + [Serializable] + public class NuGetUninstallRequest + { + public string id; + } + + [Serializable] + public class NuGetUninstallResponse + { + public string id; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/NuGetUninstallHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: e38a748df33334d02ad27999573a599d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/UniCli.Server.Editor.NuGetForUnity.asmdef`: + +```asmdef +{ + "name": "UniCli.Server.Editor.NuGetForUnity", + "rootNamespace": "UniCli.Server.Editor.Handlers.NuGetForUnity", + "references": [ + "UniCli.Server.Editor", + "NuGetForUnity" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "NuGetForUnity.PluginAPI.dll" + ], + "autoReferenced": true, + "defineConstraints": [ + "NUGET_FOR_UNITY" + ], + "versionDefines": [ + { + "name": "com.github-glitchenzo.nugetforunity", + "expression": "", + "define": "NUGET_FOR_UNITY" + } + ], + "noEngineReferences": false +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/NuGetForUnity/UniCli.Server.Editor.NuGetForUnity.asmdef.meta`: + +```meta +fileFormatVersion: 2 +guid: 81633941a6f724dc3b5a80a5b35462c9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager.meta`: + +```meta +fileFormatVersion: 2 +guid: b18d710b40846430599895fd999e0d20 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerAddHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor.PackageManager; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class PackageManagerAddHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public PackageManagerAddHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "PackageManager.Add"; + public override string Description => "Add a package by identifier (e.g., com.unity.foo@1.2.3 or git URL)"; + + protected override bool TryWriteFormatted(PackageManagerAddResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Added {response.name}@{response.version} ({response.source})" + : "Failed to add package"); + return true; + } + + protected override async ValueTask ExecuteAsync(PackageManagerAddRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + if (string.IsNullOrEmpty(request.identifier)) + throw new ArgumentException("identifier is required"); + + var addRequest = Client.Add(request.identifier); + await PackageManagerRequestHelper.WaitForCompletion(addRequest, cancellationToken); + + if (addRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package add failed: {addRequest.Error?.message ?? "Unknown error"}", + new PackageManagerAddResponse()); + + var info = addRequest.Result; + return new PackageManagerAddResponse + { + name = info.name, + displayName = info.displayName ?? "", + version = info.version, + source = info.source.ToString() + }; + } + } + + [Serializable] + public class PackageManagerAddRequest + { + public string identifier; + } + + [Serializable] + public class PackageManagerAddResponse + { + public string name; + public string displayName; + public string version; + public string source; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerAddHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 43bbd660471694d00954bd7fd6cfa614 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerGetInfoHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using UnityEditor.PackageManager; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class PackageManagerGetInfoHandler : CommandHandler + { + public override string CommandName => "PackageManager.GetInfo"; + public override string Description => "Get detailed information about a specific installed package"; + + protected override bool TryWriteFormatted(PackageManagerGetInfoResponse response, bool success, IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to get package info"); + return true; + } + + writer.WriteLine($"Name: {response.name}"); + writer.WriteLine($"Display Name: {response.displayName}"); + writer.WriteLine($"Version: {response.version}"); + writer.WriteLine($"Latest Version: {response.latestVersion}"); + writer.WriteLine($"Source: {response.source}"); + writer.WriteLine($"Direct Dependency: {(response.isDirectDependency ? "yes" : "no")}"); + writer.WriteLine($"Description: {response.description}"); + + if (response.dependencies is { Length: > 0 }) + { + writer.WriteLine("Dependencies:"); + foreach (var dep in response.dependencies) + writer.WriteLine($" - {dep}"); + } + + return true; + } + + protected override async ValueTask ExecuteAsync(PackageManagerGetInfoRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var listRequest = Client.List(true); + await PackageManagerRequestHelper.WaitForCompletion(listRequest, cancellationToken); + + if (listRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package list failed: {listRequest.Error?.message ?? "Unknown error"}", + new PackageManagerGetInfoResponse()); + + var packageInfo = listRequest.Result.FirstOrDefault(p => p.name == request.name); + if (packageInfo == null) + throw new CommandFailedException( + $"Package '{request.name}' is not installed", + new PackageManagerGetInfoResponse()); + + var dependencies = packageInfo.dependencies != null + ? packageInfo.dependencies.Select(d => $"{d.name}@{d.version}").ToArray() + : Array.Empty(); + + return new PackageManagerGetInfoResponse + { + name = packageInfo.name, + displayName = packageInfo.displayName ?? "", + version = packageInfo.version, + source = packageInfo.source.ToString(), + description = packageInfo.description ?? "", + isDirectDependency = packageInfo.isDirectDependency, + latestVersion = packageInfo.versions.latest ?? packageInfo.version, + dependencies = dependencies + }; + } + } + + [Serializable] + public class PackageManagerGetInfoRequest + { + public string name; + } + + [Serializable] + public class PackageManagerGetInfoResponse + { + public string name; + public string displayName; + public string version; + public string source; + public string description; + public bool isDirectDependency; + public string latestVersion; + public string[] dependencies; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerGetInfoHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3cf1f04455596462eb8fc8a080139d5e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerListHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor.PackageManager; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class PackageManagerListHandler : CommandHandler + { + public override string CommandName => "PackageManager.List"; + public override string Description => "List all installed packages in the project"; + + protected override bool TryWriteFormatted(PackageManagerListResponse response, bool success, IFormatWriter writer) + { + var nameWidth = "Name".Length; + var versionWidth = "Version".Length; + var sourceWidth = "Source".Length; + + foreach (var pkg in response.packages) + { + nameWidth = Math.Max(nameWidth, pkg.name.Length); + versionWidth = Math.Max(versionWidth, pkg.version.Length); + sourceWidth = Math.Max(sourceWidth, pkg.source.Length); + } + + writer.WriteLine( + $"{"Name".PadRight(nameWidth)} {"Version".PadRight(versionWidth)} {"Source".PadRight(sourceWidth)} Direct"); + + foreach (var pkg in response.packages) + { + var direct = pkg.isDirectDependency ? "yes" : ""; + writer.WriteLine( + $"{pkg.name.PadRight(nameWidth)} {pkg.version.PadRight(versionWidth)} {pkg.source.PadRight(sourceWidth)} {direct}"); + } + + writer.WriteLine($"{response.totalCount} package(s)"); + + return true; + } + + protected override async ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var listRequest = Client.List(true); + await PackageManagerRequestHelper.WaitForCompletion(listRequest, cancellationToken); + + if (listRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package list failed: {listRequest.Error?.message ?? "Unknown error"}", + new PackageManagerListResponse { packages = Array.Empty(), totalCount = 0 }); + + var packages = listRequest.Result + .Select(p => new PackageEntry + { + name = p.name, + displayName = p.displayName ?? "", + version = p.version, + source = p.source.ToString(), + isDirectDependency = p.isDirectDependency + }) + .OrderBy(p => p.name) + .ToArray(); + + return new PackageManagerListResponse + { + packages = packages, + totalCount = packages.Length + }; + } + } + + [Serializable] + public class PackageManagerListResponse + { + public PackageEntry[] packages; + public int totalCount; + } + + [Serializable] + public class PackageEntry + { + public string name; + public string displayName; + public string version; + public string source; + public bool isDirectDependency; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: da5afa3fc034e4e079c9910790bfd1be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerRemoveHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UniCli.Server.Editor; +using UnityEditor.PackageManager; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class PackageManagerRemoveHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public PackageManagerRemoveHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "PackageManager.Remove"; + public override string Description => "Remove a package by name (e.g., com.unity.cinemachine)"; + + protected override bool TryWriteFormatted(PackageManagerRemoveResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Removed {response.name}" + : "Failed to remove package"); + return true; + } + + protected override async ValueTask ExecuteAsync(PackageManagerRemoveRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var removeRequest = Client.Remove(request.name); + await PackageManagerRequestHelper.WaitForCompletion(removeRequest, cancellationToken); + + if (removeRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package remove failed: {removeRequest.Error?.message ?? "Unknown error"}", + new PackageManagerRemoveResponse()); + + return new PackageManagerRemoveResponse + { + name = request.name + }; + } + } + + [Serializable] + public class PackageManagerRemoveRequest + { + public string name; + } + + [Serializable] + public class PackageManagerRemoveResponse + { + public string name; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerRemoveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4b11701ff02b342cca16afd3068de287 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerRequestHelper.cs`: + +```cs +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.PackageManager.Requests; + +namespace UniCli.Server.Editor.Handlers +{ + internal static class PackageManagerRequestHelper + { + public static Task WaitForCompletion(Request request, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + + void Poll() + { + if (cancellationToken.IsCancellationRequested) + { + EditorApplication.update -= Poll; + tcs.TrySetCanceled(cancellationToken); + return; + } + + if (!request.IsCompleted) return; + + EditorApplication.update -= Poll; + tcs.TrySetResult(true); + } + + EditorApplication.update += Poll; + return tcs.Task; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerRequestHelper.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ed4883beef5d843e3a2c4a2bdc23e908 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerSearchHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using UniCli.Server.Editor; +using UnityEditor.PackageManager; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class PackageManagerSearchHandler : CommandHandler + { + public override string CommandName => "PackageManager.Search"; + public override string Description => "Search for packages in the Unity registry"; + + protected override bool TryWriteFormatted(PackageManagerSearchResponse response, bool success, IFormatWriter writer) + { + var nameWidth = "Name".Length; + var versionWidth = "Version".Length; + + foreach (var pkg in response.packages) + { + nameWidth = Math.Max(nameWidth, pkg.name.Length); + versionWidth = Math.Max(versionWidth, pkg.version.Length); + } + + writer.WriteLine( + $"{"Name".PadRight(nameWidth)} {"Version".PadRight(versionWidth)} Description"); + + foreach (var pkg in response.packages) + { + writer.WriteLine( + $"{pkg.name.PadRight(nameWidth)} {pkg.version.PadRight(versionWidth)} {pkg.description}"); + } + + writer.WriteLine($"{response.totalCount} result(s)"); + + return true; + } + + protected override async ValueTask ExecuteAsync(PackageManagerSearchRequest request, CancellationToken cancellationToken) + { + PackageSearchEntry[] entries; + + if (string.IsNullOrEmpty(request.query)) + { + var searchRequest = Client.SearchAll(); + await PackageManagerRequestHelper.WaitForCompletion(searchRequest, cancellationToken); + + if (searchRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package search failed: {searchRequest.Error?.message ?? "Unknown error"}", + new PackageManagerSearchResponse { packages = Array.Empty(), totalCount = 0 }); + + entries = searchRequest.Result + .Select(ToEntry) + .OrderBy(e => e.name) + .ToArray(); + } + else + { + var searchRequest = Client.Search(request.query); + await PackageManagerRequestHelper.WaitForCompletion(searchRequest, cancellationToken); + + if (searchRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package search failed: {searchRequest.Error?.message ?? "Unknown error"}", + new PackageManagerSearchResponse { packages = Array.Empty(), totalCount = 0 }); + + entries = searchRequest.Result + .Select(ToEntry) + .OrderBy(e => e.name) + .ToArray(); + } + + return new PackageManagerSearchResponse + { + packages = entries, + totalCount = entries.Length + }; + } + + static PackageSearchEntry ToEntry(UnityEditor.PackageManager.PackageInfo p) + { + return new PackageSearchEntry + { + name = p.name, + displayName = p.displayName ?? "", + version = p.version, + description = p.description ?? "" + }; + } + } + + [Serializable] + public class PackageManagerSearchRequest + { + public string query; + } + + [Serializable] + public class PackageManagerSearchResponse + { + public PackageSearchEntry[] packages; + public int totalCount; + } + + [Serializable] + public class PackageSearchEntry + { + public string name; + public string displayName; + public string version; + public string description; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerSearchHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 69633a7662e0a41cc9986e21b74e9ade +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerUpdateHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using UniCli.Server.Editor; +using UnityEditor.PackageManager; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class PackageManagerUpdateHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public PackageManagerUpdateHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "PackageManager.Update"; + public override string Description => "Update a package to a specific version or the latest version"; + + protected override bool TryWriteFormatted(PackageManagerUpdateResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine(success + ? $"Updated {response.name} {response.previousVersion} → {response.version}" + : "Failed to update package"); + return true; + } + + protected override async ValueTask ExecuteAsync(PackageManagerUpdateRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("name is required"); + + var listRequest = Client.List(true); + await PackageManagerRequestHelper.WaitForCompletion(listRequest, cancellationToken); + + if (listRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package list failed: {listRequest.Error?.message ?? "Unknown error"}", + new PackageManagerUpdateResponse()); + + var currentPackage = listRequest.Result.FirstOrDefault(p => p.name == request.name); + if (currentPackage == null) + throw new CommandFailedException( + $"Package '{request.name}' is not installed", + new PackageManagerUpdateResponse()); + + var previousVersion = currentPackage.version; + var targetVersion = string.IsNullOrEmpty(request.version) + ? currentPackage.versions.latest ?? currentPackage.version + : request.version; + + var identifier = $"{request.name}@{targetVersion}"; + var addRequest = Client.Add(identifier); + await PackageManagerRequestHelper.WaitForCompletion(addRequest, cancellationToken); + + if (addRequest.Status == StatusCode.Failure) + throw new CommandFailedException( + $"Package update failed: {addRequest.Error?.message ?? "Unknown error"}", + new PackageManagerUpdateResponse + { + name = request.name, + previousVersion = previousVersion + }); + + var info = addRequest.Result; + return new PackageManagerUpdateResponse + { + name = info.name, + displayName = info.displayName ?? "", + previousVersion = previousVersion, + version = info.version, + source = info.source.ToString() + }; + } + } + + [Serializable] + public class PackageManagerUpdateRequest + { + public string name; + public string version; + } + + [Serializable] + public class PackageManagerUpdateResponse + { + public string name; + public string displayName; + public string previousVersion; + public string version; + public string source; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PackageManager/PackageManagerUpdateHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 966b7eecbfda2433e85c01997520350b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PlayMode.meta`: + +```meta +fileFormatVersion: 2 +guid: 648fa1026a6c14d9cb98e26c60938153 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PlayMode/PlayModeHandler.cs`: + +```cs +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class PlayModeEnterHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public PlayModeEnterHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "PlayMode.Enter"; + public override string Description => "Enter play mode in Unity Editor"; + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotCompiling); + EditorApplication.EnterPlaymode(); + return new ValueTask(Unit.Value); + } + } + + public sealed class PlayModeExitHandler : CommandHandler + { + public override string CommandName => "PlayMode.Exit"; + public override string Description => "Exit play mode in Unity Editor"; + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + EditorApplication.ExitPlaymode(); + return new ValueTask(Unit.Value); + } + } + + public sealed class PlayModePauseHandler : CommandHandler + { + public override string CommandName => "PlayMode.Pause"; + public override string Description => "Toggle pause state in play mode"; + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + EditorApplication.isPaused = !EditorApplication.isPaused; + return new ValueTask(Unit.Value); + } + } + + public sealed class PlayModeStatusHandler : CommandHandler + { + public override string CommandName => "PlayMode.Status"; + public override string Description => "Get the current play mode state"; + + protected override bool TryWriteFormatted(PlayModeStatusResponse response, bool success, IFormatWriter writer) + { + if (!success) return false; + + writer.WriteLine($"isPlaying: {response.isPlaying}"); + writer.WriteLine($"isPaused: {response.isPaused}"); + writer.WriteLine($"isCompiling: {response.isCompiling}"); + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + return new ValueTask(new PlayModeStatusResponse + { + isPlaying = EditorApplication.isPlaying, + isPaused = EditorApplication.isPaused, + isCompiling = EditorApplication.isCompiling, + }); + } + } + + [System.Serializable] + public class PlayModeStatusResponse + { + public bool isPlaying; + public bool isPaused; + public bool isCompiling; + } +} +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/PlayMode/PlayModeHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: df0fb65e2e58f470d90f74eef69d1e95 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab.meta`: + +```meta +fileFormatVersion: 2 +guid: a4e7c2b1d3f84a5e9b6c0d1e2f3a4b5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabApplyHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class PrefabApplyHandler : CommandHandler + { + public override string CommandName => "Prefab.Apply"; + public override string Description => "Apply overrides of a prefab instance to the source prefab asset via PrefabUtility"; + + protected override bool TryWriteFormatted(PrefabApplyResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Applied overrides of {response.gameObjectName} to {response.assetPath}"); + else + writer.WriteLine("Failed to apply prefab overrides"); + + return true; + } + + protected override ValueTask ExecuteAsync(PrefabApplyRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new PrefabApplyResponse()); + } + + var status = PrefabUtility.GetPrefabInstanceStatus(go); + if (status == PrefabInstanceStatus.NotAPrefab) + { + throw new CommandFailedException( + $"'{go.name}' is not a prefab instance", + new PrefabApplyResponse()); + } + + var assetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go); + + PrefabUtility.ApplyPrefabInstance(go, InteractionMode.UserAction); + + return new ValueTask(new PrefabApplyResponse + { + gameObjectName = go.name, + assetPath = assetPath + }); + } + } + + [Serializable] + public class PrefabApplyRequest + { + public int instanceId; + public string path = ""; + } + + [Serializable] + public class PrefabApplyResponse + { + public string gameObjectName; + public string assetPath; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabApplyHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabGetStatusHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class PrefabGetStatusHandler : CommandHandler + { + public override string CommandName => "Prefab.GetStatus"; + public override string Description => "Get prefab instance status for a GameObject via PrefabUtility"; + + protected override bool TryWriteFormatted(PrefabGetStatusResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"{response.gameObjectName}: status={response.status}, asset={response.assetPath}"); + if (response.isPrefabInstance) + writer.WriteLine($" hasOverrides={response.hasOverrides}"); + } + else + { + writer.WriteLine("Failed to get prefab status"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(PrefabGetStatusRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new PrefabGetStatusResponse()); + } + + var status = PrefabUtility.GetPrefabInstanceStatus(go); + var isPrefabInstance = status != PrefabInstanceStatus.NotAPrefab; + + var statusString = status switch + { + PrefabInstanceStatus.Connected => "Connected", + PrefabInstanceStatus.Disconnected => "Disconnected", + PrefabInstanceStatus.MissingAsset => "MissingAsset", + _ => "NotAPrefab" + }; + + var assetPath = isPrefabInstance + ? PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go) + : ""; + + var hasOverrides = isPrefabInstance && PrefabUtility.HasPrefabInstanceAnyOverrides(go, false); + + return new ValueTask(new PrefabGetStatusResponse + { + gameObjectName = go.name, + status = statusString, + assetPath = assetPath, + hasOverrides = hasOverrides, + isPrefabInstance = isPrefabInstance + }); + } + } + + [Serializable] + public class PrefabGetStatusRequest + { + public int instanceId; + public string path = ""; + } + + [Serializable] + public class PrefabGetStatusResponse + { + public string gameObjectName; + public string status; + public string assetPath; + public bool hasOverrides; + public bool isPrefabInstance; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabGetStatusHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabInstantiateHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class PrefabInstantiateHandler : CommandHandler + { + public override string CommandName => "Prefab.Instantiate"; + public override string Description => "Instantiate a prefab asset into the scene via PrefabUtility"; + + protected override bool TryWriteFormatted(PrefabInstantiateResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Instantiated {response.name} (instanceId={response.instanceId}) from {response.assetPath}"); + else + writer.WriteLine("Failed to instantiate prefab"); + + return true; + } + + protected override ValueTask ExecuteAsync(PrefabInstantiateRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.assetPath)) + throw new ArgumentException("assetPath is required"); + + var prefab = AssetDatabase.LoadAssetAtPath(request.assetPath); + if (prefab == null) + { + throw new CommandFailedException( + $"Prefab not found at \"{request.assetPath}\"", + new PrefabInstantiateResponse()); + } + + var instance = PrefabUtility.InstantiatePrefab(prefab) as GameObject; + if (instance == null) + { + throw new CommandFailedException( + $"Failed to instantiate prefab from \"{request.assetPath}\"", + new PrefabInstantiateResponse()); + } + + Undo.RegisterCreatedObjectUndo(instance, "Instantiate Prefab"); + + var parent = GameObjectResolver.ResolveByIdOrPath(request.parentInstanceId, request.parentPath); + if (parent != null) + instance.transform.SetParent(parent.transform, true); + + return new ValueTask(new PrefabInstantiateResponse + { + instanceId = instance.GetInstanceID(), + name = instance.name, + assetPath = request.assetPath + }); + } + } + + [Serializable] + public class PrefabInstantiateRequest + { + public string assetPath; + public int parentInstanceId; + public string parentPath = ""; + } + + [Serializable] + public class PrefabInstantiateResponse + { + public int instanceId; + public string name; + public string assetPath; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabInstantiateHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabSaveHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class PrefabSaveHandler : CommandHandler + { + public override string CommandName => "Prefab.Save"; + public override string Description => "Save a GameObject as a prefab asset via PrefabUtility"; + + protected override bool TryWriteFormatted(PrefabSaveResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Saved {response.gameObjectName} as prefab at {response.assetPath}"); + else + writer.WriteLine("Failed to save prefab"); + + return true; + } + + protected override ValueTask ExecuteAsync(PrefabSaveRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.assetPath)) + throw new ArgumentException("assetPath is required"); + + if (!request.assetPath.EndsWith(".prefab")) + { + throw new CommandFailedException( + $"assetPath must end with .prefab (got \"{request.assetPath}\")", + new PrefabSaveResponse()); + } + + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new PrefabSaveResponse()); + } + + var prefab = PrefabUtility.SaveAsPrefabAsset(go, request.assetPath, out var success); + if (!success || prefab == null) + { + throw new CommandFailedException( + $"Failed to save '{go.name}' as prefab at \"{request.assetPath}\"", + new PrefabSaveResponse()); + } + + return new ValueTask(new PrefabSaveResponse + { + gameObjectName = go.name, + assetPath = request.assetPath + }); + } + } + + [Serializable] + public class PrefabSaveRequest + { + public int instanceId; + public string path = ""; + public string assetPath; + } + + [Serializable] + public class PrefabSaveResponse + { + public string gameObjectName; + public string assetPath; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabSaveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabUnpackHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Assets")] + public sealed class PrefabUnpackHandler : CommandHandler + { + public override string CommandName => "Prefab.Unpack"; + public override string Description => "Unpack a prefab instance via PrefabUtility, disconnecting it from the source prefab"; + + protected override bool TryWriteFormatted(PrefabUnpackResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Unpacked {response.gameObjectName} (mode={response.unpackMode})"); + else + writer.WriteLine("Failed to unpack prefab"); + + return true; + } + + protected override ValueTask ExecuteAsync(PrefabUnpackRequest request, CancellationToken cancellationToken) + { + var go = GameObjectResolver.ResolveByIdOrPath(request.instanceId, request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found (instanceId={request.instanceId}, path=\"{request.path}\")", + new PrefabUnpackResponse()); + } + + var status = PrefabUtility.GetPrefabInstanceStatus(go); + if (status == PrefabInstanceStatus.NotAPrefab) + { + throw new CommandFailedException( + $"'{go.name}' is not a prefab instance", + new PrefabUnpackResponse()); + } + + var mode = request.completely + ? PrefabUnpackMode.Completely + : PrefabUnpackMode.OutermostRoot; + + PrefabUtility.UnpackPrefabInstance(go, mode, InteractionMode.UserAction); + + return new ValueTask(new PrefabUnpackResponse + { + gameObjectName = go.name, + unpackMode = request.completely ? "Completely" : "OutermostRoot" + }); + } + } + + [Serializable] + public class PrefabUnpackRequest + { + public int instanceId; + public string path = ""; + public bool completely; + } + + [Serializable] + public class PrefabUnpackResponse + { + public string gameObjectName; + public string unpackMode; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Prefab/PrefabUnpackHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler.meta`: + +```meta +fileFormatVersion: 2 +guid: 5b3e9cff24e214a7684bf6f1f9f70878 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerAnalyzeFramesHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor.Profiling; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerAnalyzeFramesHandler : CommandHandler + { + public override string CommandName => "Profiler.AnalyzeFrames"; + public override string Description => "Analyze recorded frames and return aggregate statistics"; + + protected override bool TryWriteFormatted(ProfilerAnalyzeFramesResponse response, bool success, IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to analyze frames"); + return true; + } + + writer.WriteLine($"Analyzed {response.analyzedFrameCount} frames [{response.startFrame}..{response.endFrame}]"); + writer.WriteLine(""); + + var ft = response.frameTime; + writer.WriteLine("Frame Time:"); + writer.WriteLine($" Avg: {ft.avgMs:F2}ms Min: {ft.minMs:F2}ms Max: {ft.maxMs:F2}ms Median: {ft.medianMs:F2}ms P95: {ft.p95Ms:F2}ms P99: {ft.p99Ms:F2}ms"); + writer.WriteLine($" Worst frame: #{ft.maxFrameIndex}"); + writer.WriteLine(""); + + var gc = response.gcAlloc; + var maxGcFrameStr = gc.maxBytesInFrame > 0 ? $"frame #{gc.maxFrameIndex}" : "frame #-"; + writer.WriteLine("GC Allocation:"); + writer.WriteLine($" Total: {ProfilerFrameHelper.FormatBytes(gc.totalBytes)} Avg/frame: {ProfilerFrameHelper.FormatBytes((long)gc.avgBytesPerFrame)} Max/frame: {ProfilerFrameHelper.FormatBytes(gc.maxBytesInFrame)} ({maxGcFrameStr})"); + writer.WriteLine($" Frames with GC: {gc.framesWithGc} / {response.analyzedFrameCount}"); + writer.WriteLine(""); + + if (response.topSamples != null && response.topSamples.Length > 0) + { + writer.WriteLine("Top Samples (by avg self time):"); + writer.WriteLine($"{"Name",-50} {"Avg Self",10} {"Max Self",10} {"Avg Total",10} {"Avg Calls",10} {"Avg GC",10} {"Frames",8}"); + writer.WriteLine(new string('-', 112)); + foreach (var s in response.topSamples) + { + writer.WriteLine( + $"{ProfilerFrameHelper.Truncate(s.name, 50),-50} " + + $"{s.avgSelfMs,8:F3}ms " + + $"{s.maxSelfMs,8:F3}ms " + + $"{s.avgTotalMs,8:F3}ms " + + $"{s.avgCalls,10:F1} " + + $"{ProfilerFrameHelper.FormatBytes((long)s.avgGcAllocBytes),10} " + + $"{s.presentInFrames,8}"); + } + } + + return true; + } + + protected override ValueTask ExecuteAsync(ProfilerAnalyzeFramesRequest request, CancellationToken cancellationToken) + { + var (start, end) = ProfilerFrameHelper.ResolveFrameRange(request.startFrame, request.endFrame); + var frameCount = end - start + 1; + var topSampleCount = request.topSampleCount > 0 ? request.topSampleCount : 10; + + var frameTimes = new float[frameCount]; + var frameGcAllocs = new long[frameCount]; + var frameIndices = new int[frameCount]; + + var sampleAccumulators = new Dictionary(); + + for (var i = 0; i < frameCount; i++) + { + var frameIndex = start + i; + frameIndices[i] = frameIndex; + + using (var frameData = ProfilerDriver.GetHierarchyFrameDataView( + frameIndex, 0, HierarchyFrameDataView.ViewModes.MergeSamplesWithTheSameName, + HierarchyFrameDataView.columnDontSort, false)) + { + frameTimes[i] = frameData.frameTimeMs; + + var samples = ProfilerFrameHelper.CollectAllSamples(frameData); + + long gcTotal = 0; + foreach (var s in samples) + { + gcTotal += s.gcAllocBytes; + + if (!sampleAccumulators.TryGetValue(s.name, out var acc)) + { + acc = new SampleAccumulator { name = s.name }; + sampleAccumulators[s.name] = acc; + } + acc.totalSelfMs += s.selfTimeMs; + acc.totalTotalMs += s.totalTimeMs; + acc.totalCalls += s.calls; + acc.totalGcAllocBytes += s.gcAllocBytes; + if (s.selfTimeMs > acc.maxSelfMs) acc.maxSelfMs = s.selfTimeMs; + acc.presentInFrames++; + } + + frameGcAllocs[i] = gcTotal; + } + } + + var frameTime = BuildFrameTimeStats(frameTimes, frameIndices); + var gcAlloc = BuildGcAllocStats(frameGcAllocs, frameIndices, frameCount); + var topSamples = BuildTopSamples(sampleAccumulators, frameCount, topSampleCount, request.sampleNameFilter); + + return new ValueTask(new ProfilerAnalyzeFramesResponse + { + analyzedFrameCount = frameCount, + startFrame = start, + endFrame = end, + frameTime = frameTime, + gcAlloc = gcAlloc, + topSamples = topSamples + }); + } + + private static FrameTimeStats BuildFrameTimeStats(float[] frameTimes, int[] frameIndices) + { + var sorted = new float[frameTimes.Length]; + Array.Copy(frameTimes, sorted, frameTimes.Length); + Array.Sort(sorted); + + float min = sorted[0]; + float max = sorted[sorted.Length - 1]; + float sum = 0; + int maxFrameIndex = frameIndices[0]; + float maxVal = frameTimes[0]; + + for (var i = 0; i < frameTimes.Length; i++) + { + sum += frameTimes[i]; + if (frameTimes[i] > maxVal) + { + maxVal = frameTimes[i]; + maxFrameIndex = frameIndices[i]; + } + } + + return new FrameTimeStats + { + avgMs = sum / frameTimes.Length, + minMs = min, + maxMs = max, + medianMs = GetPercentile(sorted, 0.5f), + p95Ms = GetPercentile(sorted, 0.95f), + p99Ms = GetPercentile(sorted, 0.99f), + maxFrameIndex = maxFrameIndex + }; + } + + private static float GetPercentile(float[] sorted, float percentile) + { + if (sorted.Length == 1) return sorted[0]; + var index = percentile * (sorted.Length - 1); + var lower = (int)Math.Floor(index); + var upper = (int)Math.Ceiling(index); + if (lower == upper) return sorted[lower]; + var fraction = index - lower; + return sorted[lower] + (sorted[upper] - sorted[lower]) * fraction; + } + + private static GcAllocStats BuildGcAllocStats(long[] gcAllocs, int[] frameIndices, int frameCount) + { + long total = 0; + long maxBytes = 0; + int maxFrameIndex = -1; + int framesWithGc = 0; + + for (var i = 0; i < gcAllocs.Length; i++) + { + total += gcAllocs[i]; + if (gcAllocs[i] > 0) framesWithGc++; + if (gcAllocs[i] > maxBytes) + { + maxBytes = gcAllocs[i]; + maxFrameIndex = frameIndices[i]; + } + } + + return new GcAllocStats + { + totalBytes = total, + avgBytesPerFrame = frameCount > 0 ? (float)total / frameCount : 0, + maxBytesInFrame = maxBytes, + maxFrameIndex = maxFrameIndex, + framesWithGc = framesWithGc + }; + } + + private static SampleStats[] BuildTopSamples(Dictionary accumulators, int frameCount, int topCount, string filter) + { + var list = new List(); + foreach (var kvp in accumulators) + { + var acc = kvp.Value; + + if (!string.IsNullOrEmpty(filter) && + acc.name.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0) + continue; + + list.Add(new SampleStats + { + name = acc.name, + avgSelfMs = acc.totalSelfMs / frameCount, + maxSelfMs = acc.maxSelfMs, + avgTotalMs = acc.totalTotalMs / frameCount, + avgCalls = (float)acc.totalCalls / frameCount, + avgGcAllocBytes = (float)acc.totalGcAllocBytes / frameCount, + presentInFrames = acc.presentInFrames + }); + } + + list.Sort((a, b) => b.avgSelfMs.CompareTo(a.avgSelfMs)); + if (list.Count > topCount) + list.RemoveRange(topCount, list.Count - topCount); + + return list.ToArray(); + } + + private class SampleAccumulator + { + public string name; + public float totalSelfMs; + public float maxSelfMs; + public float totalTotalMs; + public long totalCalls; + public long totalGcAllocBytes; + public int presentInFrames; + } + } + + [Serializable] + public class ProfilerAnalyzeFramesRequest + { + public int startFrame = -1; + public int endFrame = -1; + public int topSampleCount = 10; + public string sampleNameFilter = ""; + } + + [Serializable] + public class ProfilerAnalyzeFramesResponse + { + public int analyzedFrameCount; + public int startFrame; + public int endFrame; + public FrameTimeStats frameTime; + public GcAllocStats gcAlloc; + public SampleStats[] topSamples; + } + + [Serializable] + public class FrameTimeStats + { + public float avgMs; + public float minMs; + public float maxMs; + public float medianMs; + public float p95Ms; + public float p99Ms; + public int maxFrameIndex; + } + + [Serializable] + public class GcAllocStats + { + public long totalBytes; + public float avgBytesPerFrame; + public long maxBytesInFrame; + public int maxFrameIndex; + public int framesWithGc; + } + + [Serializable] + public class SampleStats + { + public string name; + public float avgSelfMs; + public float maxSelfMs; + public float avgTotalMs; + public float avgCalls; + public float avgGcAllocBytes; + public int presentInFrames; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerAnalyzeFramesHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d999cd90d15c84d009270450a2fa8fa1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerFindSpikesHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor.Profiling; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerFindSpikesHandler : CommandHandler + { + public override string CommandName => "Profiler.FindSpikes"; + public override string Description => "Find frames exceeding frame time or GC allocation thresholds"; + + protected override bool TryWriteFormatted(ProfilerFindSpikesResponse response, bool success, IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to find spikes"); + return true; + } + + writer.WriteLine($"Searched {response.searchedFrameCount} frames [{response.startFrame}..{response.endFrame}]"); + + var showing = response.spikes != null ? response.spikes.Length : 0; + writer.WriteLine($"Found {response.totalSpikeCount} spikes (showing top {showing})"); + + if (response.spikes == null || response.spikes.Length == 0) + return true; + + writer.WriteLine(""); + + for (var i = 0; i < response.spikes.Length; i++) + { + var spike = response.spikes[i]; + writer.WriteLine( + $"#{i + 1} Frame {spike.frameIndex} {spike.frameTimeMs:F2}ms " + + $"GC: {ProfilerFrameHelper.FormatBytes(spike.gcAllocBytes)} [{spike.reason}]"); + + if (spike.topSamples != null) + { + foreach (var s in spike.topSamples) + { + var gcPart = s.gcAllocBytes > 0 ? $" GC: {ProfilerFrameHelper.FormatBytes(s.gcAllocBytes)}" : ""; + writer.WriteLine($" {ProfilerFrameHelper.Truncate(s.name, 40),-40} {s.selfTimeMs,7:F2}ms self{gcPart}"); + } + } + + if (i < response.spikes.Length - 1) + writer.WriteLine(""); + } + + return true; + } + + protected override ValueTask ExecuteAsync(ProfilerFindSpikesRequest request, CancellationToken cancellationToken) + { + if (request.frameTimeThresholdMs <= 0 && request.gcThresholdBytes <= 0) + throw new ArgumentException("At least one threshold must be specified (frameTimeThresholdMs or gcThresholdBytes)"); + + var (start, end) = ProfilerFrameHelper.ResolveFrameRange(request.startFrame, request.endFrame); + var frameCount = end - start + 1; + var limit = request.limit > 0 ? request.limit : 20; + var samplesPerFrame = request.samplesPerFrame; + + var spikes = new List(); + + for (var i = 0; i < frameCount; i++) + { + var frameIndex = start + i; + + using (var frameData = ProfilerDriver.GetHierarchyFrameDataView( + frameIndex, 0, HierarchyFrameDataView.ViewModes.MergeSamplesWithTheSameName, + HierarchyFrameDataView.columnDontSort, false)) + { + var frameTimeMs = frameData.frameTimeMs; + var gcAlloc = ProfilerFrameHelper.GetFrameGcAlloc(frameData); + + var isFrameTimeSpike = request.frameTimeThresholdMs > 0 && frameTimeMs > request.frameTimeThresholdMs; + var isGcSpike = request.gcThresholdBytes > 0 && gcAlloc > request.gcThresholdBytes; + + if (!isFrameTimeSpike && !isGcSpike) continue; + + string reason; + if (isFrameTimeSpike && isGcSpike) reason = "both"; + else if (isFrameTimeSpike) reason = "frameTime"; + else reason = "gcAlloc"; + + ProfilerSampleInfo[] topSamples = null; + if (samplesPerFrame > 0) + { + var allSamples = ProfilerFrameHelper.CollectAllSamples(frameData); + allSamples.Sort((a, b) => b.totalTimeMs.CompareTo(a.totalTimeMs)); + if (allSamples.Count > samplesPerFrame) + allSamples.RemoveRange(samplesPerFrame, allSamples.Count - samplesPerFrame); + topSamples = allSamples.ToArray(); + } + + spikes.Add(new SpikeFrame + { + frameIndex = frameIndex, + frameTimeMs = frameTimeMs, + gcAllocBytes = gcAlloc, + totalSampleCount = frameData.sampleCount, + reason = reason, + topSamples = topSamples + }); + } + } + + var totalSpikeCount = spikes.Count; + + spikes.Sort((a, b) => b.frameTimeMs.CompareTo(a.frameTimeMs)); + if (spikes.Count > limit) + spikes.RemoveRange(limit, spikes.Count - limit); + + return new ValueTask(new ProfilerFindSpikesResponse + { + searchedFrameCount = frameCount, + startFrame = start, + endFrame = end, + totalSpikeCount = totalSpikeCount, + spikes = spikes.ToArray() + }); + } + } + + [Serializable] + public class ProfilerFindSpikesRequest + { + public int startFrame = -1; + public int endFrame = -1; + public float frameTimeThresholdMs; + public long gcThresholdBytes; + public int limit = 20; + public int samplesPerFrame = 5; + } + + [Serializable] + public class ProfilerFindSpikesResponse + { + public int searchedFrameCount; + public int startFrame; + public int endFrame; + public int totalSpikeCount; + public SpikeFrame[] spikes; + } + + [Serializable] + public class SpikeFrame + { + public int frameIndex; + public float frameTimeMs; + public long gcAllocBytes; + public int totalSampleCount; + public string reason; + public ProfilerSampleInfo[] topSamples; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerFindSpikesHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 9c7dd8ff0dd6d4090bf281be71fafd51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerFrameHelper.cs`: + +```cs +using System; +using System.Collections.Generic; +using UnityEditor.Profiling; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + internal static class ProfilerFrameHelper + { + public static (int start, int end) ResolveFrameRange(int startFrame, int endFrame) + { + var first = ProfilerDriver.firstFrameIndex; + var last = ProfilerDriver.lastFrameIndex; + + if (last < first) + throw new InvalidOperationException("No profiler frames available. Start recording first."); + + var start = startFrame < 0 ? first : Math.Max(startFrame, first); + var end = endFrame < 0 ? last : Math.Min(endFrame, last); + + if (start > end) + throw new ArgumentException($"Invalid frame range: start ({start}) > end ({end})"); + + return (start, end); + } + + public static List CollectAllSamples(HierarchyFrameDataView frameData) + { + var rootId = frameData.GetRootItemID(); + var children = new List(); + frameData.GetItemChildren(rootId, children); + + var samples = new List(); + foreach (var childId in children) + { + CollectSamplesRecursive(frameData, childId, samples); + } + + return samples; + } + + public static void CollectSamplesRecursive(HierarchyFrameDataView frameData, int itemId, List results) + { + var name = frameData.GetItemName(itemId); + var totalTime = frameData.GetItemColumnDataAsFloat(itemId, HierarchyFrameDataView.columnTotalTime); + var selfTime = frameData.GetItemColumnDataAsFloat(itemId, HierarchyFrameDataView.columnSelfTime); + var calls = (int)frameData.GetItemColumnDataAsFloat(itemId, HierarchyFrameDataView.columnCalls); + var gcAlloc = (long)frameData.GetItemColumnDataAsFloat(itemId, HierarchyFrameDataView.columnGcMemory); + + results.Add(new ProfilerSampleInfo + { + name = name, + totalTimeMs = totalTime, + selfTimeMs = selfTime, + calls = calls, + gcAllocBytes = gcAlloc + }); + + var children = new List(); + frameData.GetItemChildren(itemId, children); + foreach (var childId in children) + { + CollectSamplesRecursive(frameData, childId, results); + } + } + + public static long GetFrameGcAlloc(HierarchyFrameDataView frameData) + { + var samples = CollectAllSamples(frameData); + long total = 0; + foreach (var s in samples) + { + total += s.gcAllocBytes; + } + return total; + } + + public static string FormatBytes(long bytes) + { + if (bytes == 0) return "0 B"; + if (bytes < 1024L) return $"{bytes} B"; + if (bytes < 1024L * 1024) return $"{bytes / 1024.0:F1} KB"; + return $"{bytes / (1024.0 * 1024):F1} MB"; + } + + public static string Truncate(string s, int maxLen) + { + if (s == null) return ""; + return s.Length <= maxLen ? s : s.Substring(0, maxLen - 3) + "..."; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerFrameHelper.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 44fb3d3d8d5734f8794a77ad599f05df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerGetFrameDataHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor.Profiling; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerGetFrameDataHandler : CommandHandler + { + public override string CommandName => "Profiler.GetFrameData"; + public override string Description => "Get CPU profiler sample data for a specific frame"; + + protected override bool TryWriteFormatted(ProfilerGetFrameDataResponse response, bool success, IFormatWriter writer) + { + if (!success) + { + writer.WriteLine("Failed to get frame data"); + return true; + } + + writer.WriteLine($"Frame {response.frameIndex} ({response.frameTimeMs:F2}ms, {response.totalSampleCount} total samples)"); + writer.WriteLine(""); + + if (response.samples != null && response.samples.Length > 0) + { + writer.WriteLine($"{"Name",-50} {"Total",8} {"Self",8} {"Calls",6} {"GC Alloc",10}"); + writer.WriteLine(new string('-', 86)); + foreach (var s in response.samples) + { + writer.WriteLine($"{ProfilerFrameHelper.Truncate(s.name, 50),-50} {s.totalTimeMs,7:F2}ms {s.selfTimeMs,7:F2}ms {s.calls,6} {ProfilerFrameHelper.FormatBytes(s.gcAllocBytes),10}"); + } + } + else + { + writer.WriteLine(" (no samples)"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(ProfilerGetFrameDataRequest request, CancellationToken cancellationToken) + { + var firstFrame = ProfilerDriver.firstFrameIndex; + var lastFrame = ProfilerDriver.lastFrameIndex; + + if (lastFrame < firstFrame) + throw new InvalidOperationException("No profiler frames available. Start recording first."); + + var frameIndex = request.frame < 0 ? lastFrame : request.frame; + + if (frameIndex < firstFrame || frameIndex > lastFrame) + throw new ArgumentException($"Frame {frameIndex} is out of range [{firstFrame}..{lastFrame}]"); + + var limit = request.limit > 0 ? request.limit : 20; + + using (var frameData = ProfilerDriver.GetHierarchyFrameDataView( + frameIndex, 0, HierarchyFrameDataView.ViewModes.MergeSamplesWithTheSameName, + HierarchyFrameDataView.columnDontSort, false)) + { + var frameTimeMs = frameData.frameTimeMs; + var totalSamples = frameData.sampleCount; + + var allSamples = ProfilerFrameHelper.CollectAllSamples(frameData); + allSamples.Sort((a, b) => b.totalTimeMs.CompareTo(a.totalTimeMs)); + if (allSamples.Count > limit) + allSamples.RemoveRange(limit, allSamples.Count - limit); + + return new ValueTask(new ProfilerGetFrameDataResponse + { + frameIndex = frameIndex, + frameTimeMs = frameTimeMs, + totalSampleCount = totalSamples, + samples = allSamples.ToArray() + }); + } + } + } + + [Serializable] + public class ProfilerGetFrameDataRequest + { + public int frame = -1; + public int limit; + } + + [Serializable] + public class ProfilerGetFrameDataResponse + { + public int frameIndex; + public float frameTimeMs; + public int totalSampleCount; + public ProfilerSampleInfo[] samples; + } + + [Serializable] + public class ProfilerSampleInfo + { + public string name; + public float totalTimeMs; + public float selfTimeMs; + public int calls; + public long gcAllocBytes; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerGetFrameDataHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f753b235de5f34b728d20c71b99be089 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerInspectHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditorInternal; +using UnityEngine.Profiling; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerInspectHandler : CommandHandler + { + public override string CommandName => "Profiler.Inspect"; + public override string Description => "Get profiler status and memory statistics"; + + protected override bool TryWriteFormatted(ProfilerInspectResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine($"Profiler: {(response.enabled ? "Recording" : "Idle")}"); + writer.WriteLine($" Deep Profiling: {response.deepProfiling}"); + writer.WriteLine($" Profile Editor: {response.profileEditor}"); + if (response.frameCount > 0) + writer.WriteLine($" Frames: {response.firstFrameIndex}..{response.lastFrameIndex} ({response.frameCount} frames)"); + else + writer.WriteLine(" Frames: (none)"); + writer.WriteLine(""); + writer.WriteLine("Memory:"); + writer.WriteLine($" Total Allocated: {FormatBytes(response.totalAllocatedMemory)}"); + writer.WriteLine($" Total Reserved: {FormatBytes(response.totalReservedMemory)}"); + writer.WriteLine($" Mono Heap Size: {FormatBytes(response.monoHeapSize)}"); + writer.WriteLine($" Mono Used Size: {FormatBytes(response.monoUsedSize)}"); + writer.WriteLine($" Graphics Memory: {FormatBytes(response.graphicsMemory)}"); + return true; + } + + private static string FormatBytes(long bytes) + { + if (bytes < 1024L) return $"{bytes} B"; + if (bytes < 1024L * 1024) return $"{bytes / 1024.0:F1} KB"; + if (bytes < 1024L * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB"; + return $"{bytes / (1024.0 * 1024 * 1024):F2} GB"; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var firstFrame = ProfilerDriver.firstFrameIndex; + var lastFrame = ProfilerDriver.lastFrameIndex; + + return new ValueTask(new ProfilerInspectResponse + { + enabled = ProfilerDriver.enabled, + deepProfiling = ProfilerDriver.deepProfiling, + profileEditor = ProfilerDriver.profileEditor, + firstFrameIndex = firstFrame, + lastFrameIndex = lastFrame, + frameCount = lastFrame >= firstFrame && firstFrame >= 0 ? lastFrame - firstFrame + 1 : 0, + totalAllocatedMemory = Profiler.GetTotalAllocatedMemoryLong(), + totalReservedMemory = Profiler.GetTotalReservedMemoryLong(), + monoHeapSize = Profiler.GetMonoHeapSizeLong(), + monoUsedSize = Profiler.GetMonoUsedSizeLong(), + graphicsMemory = Profiler.GetAllocatedMemoryForGraphicsDriver() + }); + } + } + + [Serializable] + public class ProfilerInspectResponse + { + public bool enabled; + public bool deepProfiling; + public bool profileEditor; + public int firstFrameIndex; + public int lastFrameIndex; + public int frameCount; + public long totalAllocatedMemory; + public long totalReservedMemory; + public long monoHeapSize; + public long monoUsedSize; + public long graphicsMemory; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerInspectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f36009f08db8e4ce992fda80f5c56563 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerLoadProfileHandler.cs`: + +```cs +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerLoadProfileHandler : CommandHandler + { + public override string CommandName => "Profiler.LoadProfile"; + public override string Description => "Load profiler data from a .raw file"; + + protected override bool TryWriteFormatted(ProfilerLoadProfileResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"Profile loaded from: {response.path}"); + writer.WriteLine($"Frames: [{response.firstFrameIndex}..{response.lastFrameIndex}] ({response.frameCount} frames)"); + } + else + { + writer.WriteLine("Failed to load profile"); + } + return true; + } + + protected override ValueTask ExecuteAsync(ProfilerLoadProfileRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.path)) + throw new ArgumentException("path is required"); + + if (!File.Exists(request.path)) + throw new FileNotFoundException($"Profile file not found: {request.path}"); + + var result = ProfilerDriver.LoadProfile(request.path, request.keepExistingData); + if (!result) + throw new InvalidOperationException($"Failed to load profile from: {request.path}"); + + var first = ProfilerDriver.firstFrameIndex; + var last = ProfilerDriver.lastFrameIndex; + + return new ValueTask(new ProfilerLoadProfileResponse + { + path = request.path, + firstFrameIndex = first, + lastFrameIndex = last, + frameCount = last - first + 1 + }); + } + } + + [Serializable] + public class ProfilerLoadProfileRequest + { + public string path; + public bool keepExistingData; + } + + [Serializable] + public class ProfilerLoadProfileResponse + { + public string path; + public int firstFrameIndex; + public int lastFrameIndex; + public int frameCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerLoadProfileHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8f9989fa200fc439390b9ac1a79902ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerSaveProfileHandler.cs`: + +```cs +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerSaveProfileHandler : CommandHandler + { + public override string CommandName => "Profiler.SaveProfile"; + public override string Description => "Save profiler data to a .raw file"; + + protected override bool TryWriteFormatted(ProfilerSaveProfileResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Profile saved to: {response.path}"); + else + writer.WriteLine("Failed to save profile"); + return true; + } + + protected override ValueTask ExecuteAsync(ProfilerSaveProfileRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.path)) + throw new ArgumentException("path is required"); + + var directory = Path.GetDirectoryName(request.path); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + ProfilerDriver.SaveProfile(request.path); + + if (!File.Exists(request.path)) + throw new InvalidOperationException($"Failed to save profile to: {request.path}"); + + return new ValueTask(new ProfilerSaveProfileResponse + { + path = request.path, + size = new FileInfo(request.path).Length + }); + } + } + + [Serializable] + public class ProfilerSaveProfileRequest + { + public string path; + } + + [Serializable] + public class ProfilerSaveProfileResponse + { + public string path; + public long size; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerSaveProfileHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 49c185b6ef59248b7985520a61f4d5a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerStartRecordingHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerStartRecordingHandler : CommandHandler + { + public override string CommandName => "Profiler.StartRecording"; + public override string Description => "Start profiler recording"; + + protected override bool TryWriteFormatted(ProfilerStartRecordingResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine("Profiler recording started"); + if (response.deepProfiling) + writer.WriteLine(" Deep Profiling: enabled"); + if (response.profileEditor) + writer.WriteLine(" Profile Editor: enabled"); + } + else + { + writer.WriteLine("Failed to start profiler recording"); + } + return true; + } + + protected override ValueTask ExecuteAsync(ProfilerStartRecordingRequest request, CancellationToken cancellationToken) + { + if (!request.keepFrames) + ProfilerDriver.ClearAllFrames(); + + ProfilerDriver.deepProfiling = request.deep; + ProfilerDriver.profileEditor = request.editor; + ProfilerDriver.enabled = true; + + return new ValueTask(new ProfilerStartRecordingResponse + { + enabled = ProfilerDriver.enabled, + deepProfiling = ProfilerDriver.deepProfiling, + profileEditor = ProfilerDriver.profileEditor + }); + } + } + + [Serializable] + public class ProfilerStartRecordingRequest + { + public bool deep; + public bool editor; + public bool keepFrames; + } + + [Serializable] + public class ProfilerStartRecordingResponse + { + public bool enabled; + public bool deepProfiling; + public bool profileEditor; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerStartRecordingHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1dc5e198296394995a38921387c12fe0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerStopRecordingHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditorInternal; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerStopRecordingHandler : CommandHandler + { + public override string CommandName => "Profiler.StopRecording"; + public override string Description => "Stop profiler recording"; + + protected override bool TryWriteFormatted(ProfilerStopRecordingResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine("Profiler recording stopped"); + if (response.frameCount > 0) + writer.WriteLine($" Captured frames: {response.firstFrameIndex}..{response.lastFrameIndex} ({response.frameCount} frames)"); + } + else + { + writer.WriteLine("Failed to stop profiler recording"); + } + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var firstFrame = ProfilerDriver.firstFrameIndex; + var lastFrame = ProfilerDriver.lastFrameIndex; + + ProfilerDriver.enabled = false; + + return new ValueTask(new ProfilerStopRecordingResponse + { + firstFrameIndex = firstFrame, + lastFrameIndex = lastFrame, + frameCount = lastFrame >= firstFrame && firstFrame >= 0 ? lastFrame - firstFrame + 1 : 0 + }); + } + } + + [Serializable] + public class ProfilerStopRecordingResponse + { + public int firstFrameIndex; + public int lastFrameIndex; + public int frameCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerStopRecordingHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8e7924ebb525642f1af24d294586a612 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerTakeSnapshotHandler.cs`: + +```cs +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Unity.Profiling.Memory; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Profiler")] + public sealed class ProfilerTakeSnapshotHandler : CommandHandler + { + public override string CommandName => "Profiler.TakeSnapshot"; + public override string Description => "Take a memory snapshot (.snap file)"; + + protected override bool TryWriteFormatted(ProfilerTakeSnapshotResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"Memory snapshot saved to: {response.path}"); + if (response.size > 0) + writer.WriteLine($" Size: {FormatBytes(response.size)}"); + } + else + { + writer.WriteLine("Failed to take memory snapshot"); + } + return true; + } + + private static string FormatBytes(long bytes) + { + if (bytes < 1024L) return $"{bytes} B"; + if (bytes < 1024L * 1024) return $"{bytes / 1024.0:F1} KB"; + if (bytes < 1024L * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB"; + return $"{bytes / (1024.0 * 1024 * 1024):F2} GB"; + } + + protected override async ValueTask ExecuteAsync(ProfilerTakeSnapshotRequest request, CancellationToken cancellationToken) + { + var path = request.path; + if (string.IsNullOrEmpty(path)) + { + var dir = "MemoryCaptures"; + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + path = Path.Combine(dir, $"snapshot_{DateTime.Now:yyyyMMdd_HHmmss}.snap"); + } + else + { + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + Directory.CreateDirectory(directory); + } + + var tcs = new TaskCompletionSource(); + var captureFlags = CaptureFlags.ManagedObjects | CaptureFlags.NativeObjects | CaptureFlags.NativeAllocations; + + MemoryProfiler.TakeSnapshot(path, (filePath, success) => + { + if (success) + tcs.TrySetResult(filePath); + else + tcs.TrySetException(new InvalidOperationException($"Failed to take memory snapshot at: {path}")); + }, captureFlags); + + var resultPath = await tcs.Task.WithCancellation(cancellationToken); + + long size = 0; + if (File.Exists(resultPath)) + size = new FileInfo(resultPath).Length; + + return new ProfilerTakeSnapshotResponse + { + path = resultPath, + size = size + }; + } + } + + [Serializable] + public class ProfilerTakeSnapshotRequest + { + public string path; + } + + [Serializable] + public class ProfilerTakeSnapshotResponse + { + public string path; + public long size; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Profiler/ProfilerTakeSnapshotHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 97a169856b84948968700d1e055e49af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/ProjectInfoHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ProjectInfoHandler : CommandHandler + { + private readonly ServerContext _context; + + public ProjectInfoHandler(ServerContext context) + { + _context = context; + } + + public override string CommandName => "Project.Inspect"; + public override string Description => "Get Unity project information"; + + protected override bool TryWriteFormatted(ProjectInfoResponse response, bool success, IFormatWriter writer) + { + writer.WriteLine($"Unity: {response.unityVersion}"); + writer.WriteLine($"Project: {response.productName}"); + writer.WriteLine($"Company: {response.companyName}"); + writer.WriteLine($"Path: {response.projectPath}"); + writer.WriteLine($"Target: {response.buildTarget}"); + writer.WriteLine($"Playing: {(response.isPlaying ? "Yes" : "No")}"); + writer.WriteLine($"PID: {response.processId}"); + writer.WriteLine($"Server ID: {response.serverId}"); + writer.WriteLine($"Version: {response.serverVersion}"); + writer.WriteLine($"Started: {response.startedAt}"); + writer.WriteLine($"Uptime: {response.uptimeSeconds:F1}s"); + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(ProjectInfoHandler).Assembly); + + var response = new ProjectInfoResponse + { + unityVersion = Application.unityVersion, + projectPath = Application.dataPath, + productName = PlayerSettings.productName, + companyName = PlayerSettings.companyName, + buildTarget = EditorUserBuildSettings.activeBuildTarget.ToString(), + isPlaying = EditorApplication.isPlaying, + processId = Process.GetCurrentProcess().Id, + serverId = _context.ServerId, + serverVersion = packageInfo?.version ?? "unknown", + startedAt = _context.StartedAt.ToString("yyyy-MM-dd HH:mm:ss"), + uptimeSeconds = (DateTime.Now - _context.StartedAt).TotalSeconds + }; + + return new ValueTask(response); + } + } + + [Serializable] + public class ProjectInfoResponse + { + public string unityVersion; + public string projectPath; + public string productName; + public string companyName; + public string buildTarget; + public bool isPlaying; + public int processId; + public string serverId; + public string serverVersion; + public string startedAt; + public double uptimeSeconds; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/ProjectInfoHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 34baa7174363e4ba7aff730dfb9380a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder.meta`: + +```meta +fileFormatVersion: 2 +guid: 4700b577962d84a09b38811d2a9f6801 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderStartRecordingHandler.cs`: + +```cs +#if UNICLI_RECORDER +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEditor.Recorder; +using UnityEditor.Recorder.Encoder; +using UnityEditor.Recorder.Input; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Recorder")] + public sealed class RecorderStartRecordingHandler : CommandHandler + { + public override string CommandName => "Recorder.StartRecording"; + public override string Description => "Start recording the Game View as a video (requires Play Mode)"; + + protected override bool TryWriteFormatted(RecorderStartRecordingResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"Recording started: {response.path}"); + writer.WriteLine($" Format: {response.format}"); + writer.WriteLine($" Resolution: {response.width}x{response.height}"); + writer.WriteLine($" Frame Rate: {response.frameRate} fps"); + } + else + { + writer.WriteLine("Failed to start recording"); + } + return true; + } + + protected override ValueTask ExecuteAsync(RecorderStartRecordingRequest request, CancellationToken cancellationToken) + { + if (!EditorApplication.isPlaying) + throw new InvalidOperationException("Recorder.StartRecording requires Play Mode. Use PlayMode.Enter first."); + + if (RecorderState.Controller != null && RecorderState.Controller.IsRecording()) + throw new InvalidOperationException("Recording is already in progress. Use Recorder.StopRecording first."); + + var format = string.IsNullOrEmpty(request.format) ? "MP4" : request.format.ToUpperInvariant(); + var frameRate = request.frameRate > 0 ? request.frameRate : 30f; + var quality = string.IsNullOrEmpty(request.quality) ? "High" : request.quality; + + var path = string.IsNullOrEmpty(request.path) + ? Path.Combine("Recordings", $"recording_{DateTime.Now:yyyyMMdd_HHmmss}") + : request.path; + // Strip extension since Recorder appends it automatically + if (path.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".webm", StringComparison.OrdinalIgnoreCase)) + path = path.Substring(0, path.LastIndexOf('.')); + path = ResolvePath(path); + + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + var controllerSettings = ScriptableObject.CreateInstance(); + controllerSettings.SetRecordModeToManual(); + controllerSettings.FrameRate = frameRate; + + var movieSettings = ScriptableObject.CreateInstance(); + movieSettings.name = "UniCli Movie Recorder"; + movieSettings.Enabled = true; + movieSettings.OutputFile = path; + + if (format == "WEBM") + { + movieSettings.EncoderSettings = new CoreEncoderSettings + { + Codec = CoreEncoderSettings.OutputCodec.WEBM, + EncodingQuality = ParseQuality(quality), + }; + } + else + { + movieSettings.EncoderSettings = new CoreEncoderSettings + { + Codec = CoreEncoderSettings.OutputCodec.MP4, + EncodingQuality = ParseQuality(quality), + }; + } + + movieSettings.CaptureAudio = request.captureAudio; + + var imageInputSettings = movieSettings.ImageInputSettings as GameViewInputSettings; + if (imageInputSettings != null && request.width > 0 && request.height > 0) + { + imageInputSettings.OutputWidth = request.width; + imageInputSettings.OutputHeight = request.height; + } + + controllerSettings.AddRecorderSettings(movieSettings); + + var controller = new RecorderController(controllerSettings); + controller.PrepareRecording(); + controller.StartRecording(); + + if (!controller.IsRecording()) + { + UnityEngine.Object.DestroyImmediate(movieSettings); + UnityEngine.Object.DestroyImmediate(controllerSettings); + throw new InvalidOperationException("Failed to start recording. Ensure the Game View is visible."); + } + + var actualWidth = request.width > 0 ? request.width : Screen.width; + var actualHeight = request.height > 0 ? request.height : Screen.height; + + RecorderState.Controller = controller; + RecorderState.OutputPath = path + (format == "WEBM" ? ".webm" : ".mp4"); + + return new ValueTask(new RecorderStartRecordingResponse + { + path = RecorderState.OutputPath, + format = format == "WEBM" ? "WebM" : "MP4", + width = actualWidth, + height = actualHeight, + frameRate = frameRate, + }); + } + + private static CoreEncoderSettings.VideoEncodingQuality ParseQuality(string quality) + { + switch (quality.ToLowerInvariant()) + { + case "low": return CoreEncoderSettings.VideoEncodingQuality.Low; + case "medium": return CoreEncoderSettings.VideoEncodingQuality.Medium; + case "high": return CoreEncoderSettings.VideoEncodingQuality.High; + default: return CoreEncoderSettings.VideoEncodingQuality.High; + } + } + } + + [Serializable] + public class RecorderStartRecordingRequest + { + public string path; + public string format; + public int width; + public int height; + public float frameRate; + public string quality; + public bool captureAudio; + } + + [Serializable] + public class RecorderStartRecordingResponse + { + public string path; + public string format; + public int width; + public int height; + public float frameRate; + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderStartRecordingHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: fb4aa55fa55bb4ed4bb8ae07e73ea21f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderState.cs`: + +```cs +#if UNICLI_RECORDER +using UnityEditor.Recorder; + +namespace UniCli.Server.Editor.Handlers +{ + internal static class RecorderState + { + public static RecorderController Controller { get; set; } + public static string OutputPath { get; set; } + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderState.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 595be2838920d4a219c0322b88b00bf1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderStatusHandler.cs`: + +```cs +#if UNICLI_RECORDER +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Recorder")] + public sealed class RecorderStatusHandler : CommandHandler + { + public override string CommandName => "Recorder.Status"; + public override string Description => "Get the current recording status"; + + protected override bool TryWriteFormatted(RecorderStatusResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine(response.isRecording ? "Recording: active" : "Recording: inactive"); + } + else + { + writer.WriteLine("Failed to get recording status"); + } + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var isRecording = RecorderState.Controller != null && RecorderState.Controller.IsRecording(); + + return new ValueTask(new RecorderStatusResponse + { + isRecording = isRecording, + }); + } + } + + [Serializable] + public class RecorderStatusResponse + { + public bool isRecording; + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderStatusHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f39f9ae72f09b49f7928ce6145c062d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderStopRecordingHandler.cs`: + +```cs +#if UNICLI_RECORDER +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Recorder")] + public sealed class RecorderStopRecordingHandler : CommandHandler + { + public override string CommandName => "Recorder.StopRecording"; + public override string Description => "Stop the current video recording"; + + protected override bool TryWriteFormatted(RecorderStopRecordingResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"Recording saved to: {response.path}"); + writer.WriteLine($" Size: {FormatBytes(response.size)}"); + } + else + { + writer.WriteLine("Failed to stop recording"); + } + return true; + } + + private static string FormatBytes(long bytes) + { + if (bytes < 1024L) return $"{bytes} B"; + if (bytes < 1024L * 1024) return $"{bytes / 1024.0:F1} KB"; + if (bytes < 1024L * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB"; + return $"{bytes / (1024.0 * 1024 * 1024):F2} GB"; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + if (RecorderState.Controller == null || !RecorderState.Controller.IsRecording()) + throw new InvalidOperationException("No recording is in progress. Use Recorder.StartRecording first."); + + var controller = RecorderState.Controller; + var outputPath = RecorderState.OutputPath; + + controller.StopRecording(); + + var controllerSettings = controller.Settings; + if (controllerSettings != null) + { + foreach (var recorder in controllerSettings.RecorderSettings) + { + if (recorder != null) + UnityEngine.Object.DestroyImmediate(recorder); + } + UnityEngine.Object.DestroyImmediate(controllerSettings); + } + + RecorderState.Controller = null; + RecorderState.OutputPath = null; + + long fileSize = 0; + if (File.Exists(outputPath)) + fileSize = new FileInfo(outputPath).Length; + + return new ValueTask(new RecorderStopRecordingResponse + { + path = outputPath, + size = fileSize, + }); + } + } + + [Serializable] + public class RecorderStopRecordingResponse + { + public string path; + public long size; + } +} +#endif + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Recorder/RecorderStopRecordingHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 5f561679d51e84d6695ce29ea1854ff6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote.meta`: + +```meta +fileFormatVersion: 2 +guid: 7061a0f3ac66d4d5c833c0776bfd6138 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteBridge.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Remote; +using UnityEditor.Networking.PlayerConnection; +using UnityEngine; +using UnityEngine.Networking.PlayerConnection; + +namespace UniCli.Server.Editor.Handlers.Remote +{ + public sealed class RemoteBridge : ScriptableObject + { + private readonly Dictionary> _pendingRequests = + new Dictionary>(); + + private readonly Dictionary _chunkBuffers = + new Dictionary(); + + private bool _registered; + + public void EnsureRegistered() + { + if (_registered) + return; + + EditorConnection.instance.Register(RuntimeMessageGuids.ChunkedResponse, OnChunkedResponse); + _registered = true; + } + + private void OnDestroy() + { + if (!_registered) + return; + + EditorConnection.instance.Unregister(RuntimeMessageGuids.ChunkedResponse, OnChunkedResponse); + _registered = false; + + foreach (var kvp in _pendingRequests) + kvp.Value.TrySetCanceled(); + _pendingRequests.Clear(); + _chunkBuffers.Clear(); + } + + public async Task SendCommandAsync(string command, string data, int playerId, CancellationToken cancellationToken) + { + EnsureRegistered(); + + var requestId = Guid.NewGuid().ToString(); + var request = new RuntimeCommandRequest + { + requestId = requestId, + command = command, + data = data ?? "" + }; + + var json = JsonUtility.ToJson(request); + var bytes = Encoding.UTF8.GetBytes(json); + + var tcs = new TaskCompletionSource(); + _pendingRequests[requestId] = tcs; + + await using var registration = cancellationToken.Register(() => + { + _pendingRequests.Remove(requestId); + _chunkBuffers.Remove(requestId); + tcs.TrySetCanceled(); + }); + + EditorConnection.instance.Send(RuntimeMessageGuids.CommandRequest, bytes, playerId); + + return await tcs.Task; + } + + public async Task SendListAsync(int playerId, CancellationToken cancellationToken) + { + EnsureRegistered(); + + var requestId = Guid.NewGuid().ToString(); + var request = new RuntimeListRequest + { + requestId = requestId + }; + + var json = JsonUtility.ToJson(request); + var bytes = Encoding.UTF8.GetBytes(json); + + var tcs = new TaskCompletionSource(); + _pendingRequests[requestId] = tcs; + + await using var registration = cancellationToken.Register(() => + { + _pendingRequests.Remove(requestId); + _chunkBuffers.Remove(requestId); + tcs.TrySetCanceled(); + }); + + EditorConnection.instance.Send(RuntimeMessageGuids.ListRequest, bytes, playerId); + + return await tcs.Task; + } + + private void OnChunkedResponse(MessageEventArgs args) + { + var json = Encoding.UTF8.GetString(args.data); + var chunk = JsonUtility.FromJson(json); + + if (!_pendingRequests.ContainsKey(chunk.requestId)) + return; + + if (!_chunkBuffers.TryGetValue(chunk.requestId, out var accumulator)) + { + accumulator = new ChunkAccumulator(chunk.totalChunks); + _chunkBuffers[chunk.requestId] = accumulator; + } + + accumulator.Add(chunk.chunkIndex, chunk.data); + + if (!accumulator.IsComplete) + return; + + var reassembled = accumulator.Reassemble(); + _chunkBuffers.Remove(chunk.requestId); + + if (_pendingRequests.TryGetValue(chunk.requestId, out var tcs)) + { + _pendingRequests.Remove(chunk.requestId); + tcs.TrySetResult(reassembled); + } + } + + private sealed class ChunkAccumulator + { + private readonly string[] _chunks; + private int _receivedCount; + + public bool IsComplete => _receivedCount >= _chunks.Length; + + public ChunkAccumulator(int totalChunks) + { + _chunks = new string[totalChunks]; + } + + public void Add(int index, string data) + { + if (index < 0 || index >= _chunks.Length) + return; + + if (_chunks[index] != null) + return; + + _chunks[index] = data; + _receivedCount++; + } + + public string Reassemble() + { + var sb = new StringBuilder(); + for (var i = 0; i < _chunks.Length; i++) + sb.Append(_chunks[i]); + return sb.ToString(); + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteBridge.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 882fe45343dee42f0b61d5e4d7759b3d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteHelper.cs`: + +```cs +using UnityEditor; +using UnityEditor.Networking.PlayerConnection; + +namespace UniCli.Server.Editor.Handlers.Remote +{ + internal static class RemoteHelper + { + public static bool ShouldExecuteLocally(int requestedPlayerId) + { + if (requestedPlayerId > 0) + return false; + + return EditorApplication.isPlaying; + } + + public static int ResolvePlayerId(int requestedId) + { + if (requestedId > 0) + return requestedId; + + var players = EditorConnection.instance.ConnectedPlayers; + if (players.Count == 0) + throw new System.InvalidOperationException("No runtime player connected. Connect a Development Build first."); + + return players[0].playerId; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteHelper.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d4d1621789b984a0fa53438864d8b62b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteInvokeHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UniCli.Remote; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers.Remote +{ + [Module("Remote")] + public sealed class RemoteInvokeHandler : CommandHandler + { + private readonly RemoteBridge _bridge; + private readonly DebugCommandRegistry _registry; + + public RemoteInvokeHandler(RemoteBridge bridge, DebugCommandRegistry registry) + { + _bridge = bridge; + _registry = registry; + } + + public override string CommandName => "Remote.Invoke"; + public override string Description => "Invoke a debug command on connected runtime player"; + + protected override bool TryWriteFormatted(RemoteInvokeResponse response, bool success, IFormatWriter writer) + { + if (success) + { + if (string.IsNullOrEmpty(response.data)) + writer.WriteLine($"Command '{response.command}' succeeded"); + else + writer.WriteLine(response.data); + } + else + { + writer.WriteLine($"Command failed: {response.message}"); + } + + return true; + } + + protected override async ValueTask ExecuteAsync(RemoteInvokeRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.command)) + throw new ArgumentException("'command' is required"); + + if (RemoteHelper.ShouldExecuteLocally(request.playerId)) + return ExecuteLocally(request); + + var playerId = RemoteHelper.ResolvePlayerId(request.playerId); + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(TimeSpan.FromSeconds(30)); + + var responseJson = await _bridge.SendCommandAsync(request.command, request.data, playerId, cts.Token); + var cmdResponse = JsonUtility.FromJson(responseJson); + + if (!cmdResponse.success) + { + throw new CommandFailedException( + cmdResponse.message, + new RemoteInvokeResponse + { + command = request.command, + success = false, + message = cmdResponse.message, + data = cmdResponse.data + }); + } + + return new RemoteInvokeResponse + { + command = request.command, + success = true, + message = cmdResponse.message, + data = cmdResponse.data + }; + } + + private RemoteInvokeResponse ExecuteLocally(RemoteInvokeRequest request) + { + if (!_registry.TryGetCommand(request.command, out var command)) + { + throw new CommandFailedException( + $"Unknown debug command: {request.command}", + new RemoteInvokeResponse + { + command = request.command, + success = false, + message = $"Unknown debug command: {request.command}", + data = "" + }); + } + + var resultJson = command.Execute(request.data); + + return new RemoteInvokeResponse + { + command = request.command, + success = true, + message = $"Command '{request.command}' succeeded (local PlayMode)", + data = resultJson + }; + } + + } + + [Serializable] + public class RemoteInvokeRequest + { + public string command; + public string data; + public int playerId; + } + + [Serializable] + public class RemoteInvokeResponse : IRawJsonResponse + { + public string command; + public bool success; + public string message; + public string data; + + public string ToJson() + { + var dataJson = string.IsNullOrEmpty(data) ? "null" : data; + var escapedCommand = command?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? ""; + var escapedMessage = message?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? ""; + return $"{{\"command\":\"{escapedCommand}\",\"success\":{(success ? "true" : "false")},\"message\":\"{escapedMessage}\",\"data\":{dataJson}}}"; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteInvokeHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 458b7215e0c0d4f29a725e9922b578b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteListHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UniCli.Remote; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers.Remote +{ + [Module("Remote")] + public sealed class RemoteListHandler : CommandHandler + { + private readonly RemoteBridge _bridge; + private readonly DebugCommandRegistry _registry; + + public RemoteListHandler(RemoteBridge bridge, DebugCommandRegistry registry) + { + _bridge = bridge; + _registry = registry; + } + + public override string CommandName => "Remote.List"; + public override string Description => "List debug commands registered on connected runtime player"; + + protected override bool TryWriteFormatted(RemoteListResponse response, bool success, IFormatWriter writer) + { + if (!success || response.commands == null || response.commands.Length == 0) + { + writer.WriteLine("No debug commands available on connected player."); + return true; + } + + writer.WriteLine($"Debug commands ({response.commands.Length}):"); + foreach (var cmd in response.commands) + { + var desc = string.IsNullOrEmpty(cmd.description) ? "" : $" - {cmd.description}"; + writer.WriteLine($" {cmd.name}{desc}"); + } + + return true; + } + + protected override async ValueTask ExecuteAsync(RemoteListRequest request, CancellationToken cancellationToken) + { + if (RemoteHelper.ShouldExecuteLocally(request.playerId)) + return ExecuteListLocally(); + + var playerId = RemoteHelper.ResolvePlayerId(request.playerId); + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(TimeSpan.FromSeconds(10)); + + var responseJson = await _bridge.SendListAsync(playerId, cts.Token); + var listResponse = JsonUtility.FromJson(responseJson); + + return new RemoteListResponse + { + commands = listResponse.commands + }; + } + + private RemoteListResponse ExecuteListLocally() + { + return new RemoteListResponse + { + commands = _registry.GetCommandInfos() + }; + } + + } + + [Serializable] + public class RemoteListRequest + { + public int playerId; + } + + [Serializable] + public class RemoteListResponse + { + public RuntimeCommandInfo[] commands; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 6a0caade396834c9da18954d769abae4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteServiceInstaller.cs`: + +```cs +using UniCli.Remote; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers.Remote +{ + public sealed class RemoteServiceInstaller : IServiceInstaller + { + public void Install(ServiceRegistry services) + { + var bridge = ScriptableObject.CreateInstance(); + bridge.hideFlags = HideFlags.HideAndDontSave; + services.AddSingleton(bridge); + + var registry = new DebugCommandRegistry(); + registry.DiscoverCommands(); + services.AddSingleton(registry); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Remote/RemoteServiceInstaller.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: f0a24e0be003d4825ac70c09750f74ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene.meta`: + +```meta +fileFormatVersion: 2 +guid: 41a1b208b04d54c3aabbb677d7592cab +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneCloseHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Scene")] + public sealed class SceneCloseHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public SceneCloseHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "Scene.Close"; + public override string Description => "Close a loaded scene via EditorSceneManager"; + + protected override bool TryWriteFormatted(SceneCloseResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Closed scene: {response.name} ({response.path}) removed={response.removed}"); + else + writer.WriteLine("Failed to close scene"); + + return true; + } + + protected override ValueTask ExecuteAsync(SceneCloseRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlaying); + + var scene = SceneResolver.Resolve(request.name, request.path, request.sceneIndex); + if (!scene.IsValid()) + { + throw new CommandFailedException( + $"Scene not found (name=\"{request.name}\", path=\"{request.path}\", sceneIndex={request.sceneIndex})", + new SceneCloseResponse()); + } + + var sceneName = scene.name; + var scenePath = scene.path; + + var closed = EditorSceneManager.CloseScene(scene, request.removeScene); + if (!closed) + { + throw new CommandFailedException( + $"Failed to close scene '{sceneName}' (it may be the only loaded scene)", + new SceneCloseResponse { name = sceneName, path = scenePath }); + } + + return new ValueTask(new SceneCloseResponse + { + name = sceneName, + path = scenePath, + removed = request.removeScene + }); + } + } + + [Serializable] + public class SceneCloseRequest + { + public string name = ""; + public string path = ""; + public int sceneIndex = -1; + public bool removeScene; + } + + [Serializable] + public class SceneCloseResponse + { + public string name; + public string path; + public bool removed; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneCloseHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: fc7e6f2065e654fbaa5acfe2966eb9a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneGetActiveHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Scene")] + public sealed class SceneGetActiveHandler : CommandHandler + { + public override string CommandName => "Scene.GetActive"; + public override string Description => "Get the active scene via SceneManager"; + + protected override bool TryWriteFormatted(SceneInfoResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"{response.name} ({response.path}) buildIndex={response.buildIndex} dirty={response.isDirty}"); + else + writer.WriteLine("Failed to get active scene"); + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var scene = SceneManager.GetActiveScene(); + + return new ValueTask(SceneInfoResponse.From(scene)); + } + } + + [Serializable] + public class SceneInfoResponse + { + public string name; + public string path; + public int buildIndex; + public bool isDirty; + public bool isLoaded; + public int rootCount; + + public static SceneInfoResponse From(Scene scene) + { + return new SceneInfoResponse + { + name = scene.name, + path = scene.path, + buildIndex = scene.buildIndex, + isDirty = scene.isDirty, + isLoaded = scene.isLoaded, + rootCount = scene.rootCount + }; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneGetActiveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 648c84ebe5b8e4550aedd56907fe2415 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneListHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Scene")] + public sealed class SceneListHandler : CommandHandler + { + public override string CommandName => "Scene.List"; + public override string Description => "List all loaded scenes via SceneManager"; + + protected override bool TryWriteFormatted(SceneListResponse response, bool success, IFormatWriter writer) + { + if (!success || response.scenes == null || response.scenes.Length == 0) + { + writer.WriteLine("No scenes loaded."); + return true; + } + + writer.WriteLine($"Active scene: {response.activeSceneName}"); + writer.WriteLine($"Loaded scenes ({response.scenes.Length}):"); + + foreach (var scene in response.scenes) + { + var dirty = scene.isDirty ? " [dirty]" : ""; + writer.WriteLine($" {scene.name} ({scene.path}) buildIndex={scene.buildIndex}{dirty}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var count = SceneManager.sceneCount; + var scenes = new SceneInfo[count]; + var activeScene = SceneManager.GetActiveScene(); + + for (var i = 0; i < count; i++) + { + var scene = SceneManager.GetSceneAt(i); + scenes[i] = new SceneInfo + { + name = scene.name, + path = scene.path, + buildIndex = scene.buildIndex, + isDirty = scene.isDirty, + isLoaded = scene.isLoaded, + rootCount = scene.rootCount + }; + } + + return new ValueTask(new SceneListResponse + { + scenes = scenes, + activeSceneName = activeScene.name + }); + } + } + + [Serializable] + public class SceneListResponse + { + public SceneInfo[] scenes; + public string activeSceneName; + } + + [Serializable] + public class SceneInfo + { + public string name; + public string path; + public int buildIndex; + public bool isDirty; + public bool isLoaded; + public int rootCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: da1a3e7fecdf94b5b8d0fac637a39df1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneNewHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Scene")] + public sealed class SceneNewHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public SceneNewHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "Scene.New"; + public override string Description => "Create a new scene via EditorSceneManager"; + + protected override bool TryWriteFormatted(SceneInfoResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Created new scene: {response.name}"); + else + writer.WriteLine("Failed to create new scene"); + + return true; + } + + protected override ValueTask ExecuteAsync(SceneNewRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlaying); + + var setup = request.empty + ? NewSceneSetup.EmptyScene + : NewSceneSetup.DefaultGameObjects; + + var mode = request.additive + ? NewSceneMode.Additive + : NewSceneMode.Single; + + var scene = EditorSceneManager.NewScene(setup, mode); + if (!scene.IsValid()) + { + throw new CommandFailedException( + "Failed to create new scene", + new SceneInfoResponse()); + } + + return new ValueTask(SceneInfoResponse.From(scene)); + } + } + + [Serializable] + public class SceneNewRequest + { + public bool empty; + public bool additive; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneNewHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: a8153eaf0fb834a7fad226b3caac4992 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneOpenHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEditor.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Scene")] + public sealed class SceneOpenHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public SceneOpenHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "Scene.Open"; + public override string Description => "Open a scene by asset path via EditorSceneManager"; + + protected override bool TryWriteFormatted(SceneInfoResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Opened scene: {response.name} ({response.path})"); + else + writer.WriteLine("Failed to open scene"); + + return true; + } + + protected override ValueTask ExecuteAsync(SceneOpenRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlaying); + + if (string.IsNullOrEmpty(request.path)) + throw new ArgumentException("path is required"); + + var mode = request.additive + ? OpenSceneMode.Additive + : OpenSceneMode.Single; + + var scene = EditorSceneManager.OpenScene(request.path, mode); + if (!scene.IsValid()) + { + throw new CommandFailedException( + $"Failed to open scene at \"{request.path}\"", + new SceneInfoResponse()); + } + + return new ValueTask(SceneInfoResponse.From(scene)); + } + } + + [Serializable] + public class SceneOpenRequest + { + public string path; + public bool additive; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneOpenHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3cef2e53236d145fc818f5ebec11a369 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneResolver.cs`: + +```cs +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + internal static class SceneResolver + { + public static Scene Resolve(string name, string path, int sceneIndex = -1) + { + if (!string.IsNullOrEmpty(path)) + { + var scene = SceneManager.GetSceneByPath(path); + if (scene.IsValid()) + return scene; + } + + if (!string.IsNullOrEmpty(name)) + { + var scene = SceneManager.GetSceneByName(name); + if (scene.IsValid()) + return scene; + } + + if (sceneIndex >= 0 && sceneIndex < SceneManager.sceneCount) + return SceneManager.GetSceneAt(sceneIndex); + + return default; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneResolver.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 9c2e5592883fb48b9a83a3c12380a1e2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneSaveHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor.SceneManagement; +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Scene")] + public sealed class SceneSaveHandler : CommandHandler + { + public override string CommandName => "Scene.Save"; + public override string Description => "Save a scene or all open scenes via EditorSceneManager"; + + protected override bool TryWriteFormatted(SceneSaveResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"Saved {response.savedCount} scene(s):"); + if (response.savedScenePaths != null) + { + foreach (var p in response.savedScenePaths) + writer.WriteLine($" {p}"); + } + } + else + { + writer.WriteLine("Failed to save scene(s)"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(SceneSaveRequest request, CancellationToken cancellationToken) + { + if (request.all) + return SaveAllScenes(); + + return SaveSingleScene(request); + } + + private static ValueTask SaveAllScenes() + { + var saved = EditorSceneManager.SaveOpenScenes(); + if (!saved) + { + throw new CommandFailedException( + "Failed to save open scenes", + new SceneSaveResponse { savedScenePaths = Array.Empty(), savedCount = 0 }); + } + + var paths = new List(); + for (var i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene.isLoaded && !string.IsNullOrEmpty(scene.path)) + paths.Add(scene.path); + } + + return new ValueTask(new SceneSaveResponse + { + savedScenePaths = paths.ToArray(), + savedCount = paths.Count + }); + } + + private static ValueTask SaveSingleScene(SceneSaveRequest request) + { + Scene scene; + + if (!string.IsNullOrEmpty(request.name) || !string.IsNullOrEmpty(request.path) || request.sceneIndex >= 0) + { + scene = SceneResolver.Resolve(request.name, request.path, request.sceneIndex); + if (!scene.IsValid()) + { + throw new CommandFailedException( + $"Scene not found (name=\"{request.name}\", path=\"{request.path}\")", + new SceneSaveResponse { savedScenePaths = Array.Empty(), savedCount = 0 }); + } + } + else + { + scene = SceneManager.GetActiveScene(); + } + + var savePath = !string.IsNullOrEmpty(request.saveAsPath) + ? request.saveAsPath + : scene.path; + + var saved = EditorSceneManager.SaveScene(scene, savePath); + if (!saved) + { + throw new CommandFailedException( + $"Failed to save scene '{scene.name}' to \"{savePath}\"", + new SceneSaveResponse { savedScenePaths = Array.Empty(), savedCount = 0 }); + } + + return new ValueTask(new SceneSaveResponse + { + savedScenePaths = new[] { savePath }, + savedCount = 1 + }); + } + } + + [Serializable] + public class SceneSaveRequest + { + public string name = ""; + public string path = ""; + public int sceneIndex = -1; + public string saveAsPath = ""; + public bool all; + } + + [Serializable] + public class SceneSaveResponse + { + public string[] savedScenePaths; + public int savedCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneSaveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 40cc0745f9b4c4453bdfa3733489912e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneSetActiveHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Threading.Tasks; +using UnityEngine.SceneManagement; + +namespace UniCli.Server.Editor.Handlers +{ + [Module("Scene")] + public sealed class SceneSetActiveHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public SceneSetActiveHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "Scene.SetActive"; + public override string Description => "Set the active scene via SceneManager"; + + protected override bool TryWriteFormatted(SceneInfoResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Active scene set to: {response.name} ({response.path})"); + else + writer.WriteLine("Failed to set active scene"); + + return true; + } + + protected override ValueTask ExecuteAsync(SceneSetActiveRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlaying); + + var scene = SceneResolver.Resolve(request.name, request.path, request.sceneIndex); + if (!scene.IsValid()) + { + throw new CommandFailedException( + $"Scene not found (name=\"{request.name}\", path=\"{request.path}\")", + new SceneInfoResponse()); + } + + if (!scene.isLoaded) + { + throw new CommandFailedException( + $"Scene '{scene.name}' is not loaded", + new SceneInfoResponse()); + } + + if (!SceneManager.SetActiveScene(scene)) + { + throw new CommandFailedException( + $"Failed to set '{scene.name}' as active scene", + new SceneInfoResponse()); + } + + return new ValueTask(SceneInfoResponse.From(scene)); + } + } + + [Serializable] + public class SceneSetActiveRequest + { + public string name = ""; + public string path = ""; + public int sceneIndex = -1; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Scene/SceneSetActiveHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8a37ddfaddd1741288e1194c37e5c3cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Screenshot.meta`: + +```meta +fileFormatVersion: 2 +guid: 31d406ad25a774543919797fa2e6e258 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Screenshot/ScreenshotCaptureHandler.cs`: + +```cs +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ScreenshotCaptureHandler : CommandHandler + { + public override string CommandName => "Screenshot.Capture"; + public override string Description => "Capture a screenshot of the Game View and save as PNG (requires Play Mode)"; + + protected override bool TryWriteFormatted(ScreenshotCaptureResponse response, bool success, IFormatWriter writer) + { + if (success) + { + writer.WriteLine($"Screenshot saved to: {response.path}"); + writer.WriteLine($" Resolution: {response.width}x{response.height}"); + writer.WriteLine($" Size: {FormatBytes(response.size)}"); + } + else + { + writer.WriteLine("Failed to capture screenshot"); + } + return true; + } + + private static string FormatBytes(long bytes) + { + if (bytes < 1024L) return $"{bytes} B"; + if (bytes < 1024L * 1024) return $"{bytes / 1024.0:F1} KB"; + if (bytes < 1024L * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB"; + return $"{bytes / (1024.0 * 1024 * 1024):F2} GB"; + } + + protected override ValueTask ExecuteAsync(ScreenshotCaptureRequest request, CancellationToken cancellationToken) + { + if (!EditorApplication.isPlaying) + throw new InvalidOperationException("Screenshot.Capture requires Play Mode. Use PlayMode.Enter first."); + + var superSize = request.superSize > 0 ? request.superSize : 1; + + var path = string.IsNullOrEmpty(request.path) + ? Path.Combine("Screenshots", $"screenshot_{DateTime.Now:yyyyMMdd_HHmmss}.png") + : request.path; + path = ResolvePath(path); + + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + int capturedWidth; + int capturedHeight; + + Texture2D tex = null; + try + { + tex = ScreenCapture.CaptureScreenshotAsTexture(superSize); + if (tex == null) + throw new InvalidOperationException("Failed to capture screenshot. Ensure the Game View is visible and rendering."); + + capturedWidth = tex.width; + capturedHeight = tex.height; + + var pngBytes = tex.EncodeToPNG(); + File.WriteAllBytes(path, pngBytes); + } + finally + { + if (tex != null) + UnityEngine.Object.DestroyImmediate(tex); + } + + var fullPath = Path.GetFullPath(path); + + if (!File.Exists(fullPath)) + throw new InvalidOperationException($"Failed to save screenshot to: {fullPath}"); + + var fileInfo = new FileInfo(fullPath); + return new ValueTask(new ScreenshotCaptureResponse + { + path = fullPath, + width = capturedWidth, + height = capturedHeight, + size = fileInfo.Length + }); + } + } + + [Serializable] + public class ScreenshotCaptureRequest + { + public string path; + public int superSize; + } + + [Serializable] + public class ScreenshotCaptureResponse + { + public string path; + public int width; + public int height; + public long size; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Screenshot/ScreenshotCaptureHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c78852afab937412ab233e7d0fb10f8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Search.meta`: + +```meta +fileFormatVersion: 2 +guid: c6e9a130500ce487bb3ac8d170b4eeaa +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Search/UniCli.Server.Editor.Search.asmdef`: + +```asmdef +{ + "name": "UniCli.Server.Editor.Search", + "rootNamespace": "UniCli.Server.Editor.Handlers.Search", + "references": [ + "UniCli.Server.Editor" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Search/UniCli.Server.Editor.Search.asmdef.meta`: + +```meta +fileFormatVersion: 2 +guid: bb8cb4bc48f7e4a289bd61bd1d4c8499 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Search/UnitySearchHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEditor.Search; + +namespace UniCli.Server.Editor.Handlers.Search +{ + [Module("Search")] + public sealed class UnitySearchHandler : CommandHandler + { + public override string CommandName => "Search"; + public override string Description => "Search Unity project using Unity Search API"; + + protected override ValueTask ExecuteAsync(UnitySearchRequest request, CancellationToken cancellationToken) + { + var flags = SearchFlags.Synchronous; + if (request.includePackages) + flags |= SearchFlags.Packages; + + var providers = string.IsNullOrEmpty(request.provider) ? null : new[] { request.provider }; + + using var context = SearchService.CreateContext(providers, request.query, flags); + var searchItems = SearchService.GetItems(context); + + var results = new List(); + var maxResults = request.maxResults > 0 ? request.maxResults : 50; + + foreach (var item in searchItems) + { + if (results.Count >= maxResults) + break; + + results.Add(new SearchResultItem + { + id = item.id ?? "", + label = item.GetLabel(context, true) ?? "", + description = item.GetDescription(context, true) ?? "", + provider = item.provider?.id ?? "" + }); + } + + return new ValueTask(new UnitySearchResponse + { + results = results.ToArray(), + totalCount = searchItems.Count, + displayedCount = results.Count, + query = request.query + }); + } + } + + [Serializable] + public class UnitySearchRequest + { + public string query = ""; + public string provider = ""; + public int maxResults = 50; + public bool includePackages; + } + + [Serializable] + public class UnitySearchResponse + { + public SearchResultItem[] results; + public int totalCount; + public int displayedCount; + public string query; + } + + [Serializable] + public class SearchResultItem + { + public string id; + public string label; + public string description; + public string provider; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Search/UnitySearchHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: a0eedf8ed3cc34ad5bc887e5e2e190dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection.meta`: + +```meta +fileFormatVersion: 2 +guid: 66e70198ad3e8496080dcb20eb1d5d87 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/GetSelectionHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class GetSelectionHandler : CommandHandler + { + public override string CommandName => "Selection.Get"; + public override string Description => "Get the current selection in the editor"; + + protected override bool TryWriteFormatted(GetSelectionResponse response, bool success, IFormatWriter writer) + { + if (!success) return true; + + if (response.gameObjects != null && response.gameObjects.Length > 0) + { + writer.WriteLine($"Selected GameObjects ({response.gameObjects.Length}):"); + foreach (var go in response.gameObjects) + writer.WriteLine($" {go.path} (instanceId={go.instanceId})"); + } + + if (response.assets != null && response.assets.Length > 0) + { + writer.WriteLine($"Selected Assets ({response.assets.Length}):"); + foreach (var asset in response.assets) + writer.WriteLine($" {asset.assetPath} ({asset.typeName})"); + } + + if ((response.gameObjects == null || response.gameObjects.Length == 0) && + (response.assets == null || response.assets.Length == 0)) + { + writer.WriteLine("Nothing selected."); + } + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var gameObjects = new List(); + var assets = new List(); + + foreach (var obj in UnityEditor.Selection.objects) + { + if (obj is GameObject go) + { + gameObjects.Add(new SelectedGameObjectInfo + { + instanceId = go.GetInstanceID(), + name = go.name, + path = GameObjectResolver.BuildPath(go.transform) + }); + } + else + { + var assetPath = AssetDatabase.GetAssetPath(obj); + if (!string.IsNullOrEmpty(assetPath)) + { + assets.Add(new SelectedAssetInfo + { + assetPath = assetPath, + typeName = obj.GetType().FullName, + name = obj.name + }); + } + } + } + + return new ValueTask(new GetSelectionResponse + { + gameObjects = gameObjects.ToArray(), + assets = assets.ToArray() + }); + } + } + + [Serializable] + public class GetSelectionResponse + { + public SelectedGameObjectInfo[] gameObjects; + public SelectedAssetInfo[] assets; + } + + [Serializable] + public class SelectedGameObjectInfo + { + public int instanceId; + public string name; + public string path; + } + + [Serializable] + public class SelectedAssetInfo + { + public string assetPath; + public string typeName; + public string name; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/GetSelectionHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 262ea707686cc4942a08a1daacc1aa7e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetAssetHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class SetAssetSelectionHandler : CommandHandler + { + public override string CommandName => "Selection.SetAsset"; + public override string Description => "Select an asset by path"; + + protected override bool TryWriteFormatted(SetAssetSelectionResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Selected: {response.assetPath} ({response.typeName})"); + return true; + } + + protected override ValueTask ExecuteAsync(SetAssetSelectionRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.assetPath)) + { + throw new CommandFailedException( + "assetPath is required", + new SetAssetSelectionResponse()); + } + + var asset = AssetDatabase.LoadMainAssetAtPath(request.assetPath); + if (asset == null) + { + throw new CommandFailedException( + $"Asset not found: \"{request.assetPath}\"", + new SetAssetSelectionResponse()); + } + + Selection.activeObject = asset; + + return new ValueTask(new SetAssetSelectionResponse + { + assetPath = request.assetPath, + typeName = asset.GetType().FullName, + name = asset.name + }); + } + } + + [Serializable] + public class SetAssetSelectionRequest + { + public string assetPath; + } + + [Serializable] + public class SetAssetSelectionResponse + { + public string assetPath; + public string typeName; + public string name; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetAssetHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 5ead808fe8bbb4b468699bbf76b8044c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetAssetsHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class SetAssetsSelectionHandler : CommandHandler + { + public override string CommandName => "Selection.SetAssets"; + public override string Description => "Select multiple assets by paths"; + + protected override bool TryWriteFormatted(SetAssetsSelectionResponse response, bool success, IFormatWriter writer) + { + if (!success) return true; + + writer.WriteLine($"Selected {response.selected.Length} assets:"); + foreach (var item in response.selected) + writer.WriteLine($" {item.assetPath} ({item.typeName})"); + + if (response.notFound != null && response.notFound.Length > 0) + { + writer.WriteLine($"Not found ({response.notFound.Length}):"); + foreach (var path in response.notFound) + writer.WriteLine($" {path}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(SetAssetsSelectionRequest request, CancellationToken cancellationToken) + { + if (request.assetPaths == null || request.assetPaths.Length == 0) + { + throw new CommandFailedException( + "assetPaths is required", + new SetAssetsSelectionResponse + { + selected = Array.Empty(), + notFound = Array.Empty() + }); + } + + var objects = new List(); + var selected = new List(); + var notFound = new List(); + + foreach (var assetPath in request.assetPaths) + { + var asset = AssetDatabase.LoadMainAssetAtPath(assetPath); + if (asset != null) + { + objects.Add(asset); + selected.Add(new SelectedAssetInfo + { + assetPath = assetPath, + typeName = asset.GetType().FullName, + name = asset.name + }); + } + else + { + notFound.Add(assetPath); + } + } + + Selection.objects = objects.ToArray(); + + return new ValueTask(new SetAssetsSelectionResponse + { + selected = selected.ToArray(), + notFound = notFound.ToArray() + }); + } + } + + [Serializable] + public class SetAssetsSelectionRequest + { + public string[] assetPaths; + } + + [Serializable] + public class SetAssetsSelectionResponse + { + public SelectedAssetInfo[] selected; + public string[] notFound; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetAssetsHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 308d0260d29a44732922ad09a643a1fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetGameObjectHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class SetGameObjectSelectionHandler : CommandHandler + { + public override string CommandName => "Selection.SetGameObject"; + public override string Description => "Select a GameObject by path"; + + protected override bool TryWriteFormatted(SetGameObjectSelectionResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Selected: {response.path} (instanceId={response.instanceId})"); + return true; + } + + protected override ValueTask ExecuteAsync(SetGameObjectSelectionRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.path)) + { + throw new CommandFailedException( + "path is required", + new SetGameObjectSelectionResponse()); + } + + var go = GameObjectResolver.ResolveByPath(request.path); + if (go == null) + { + throw new CommandFailedException( + $"GameObject not found: \"{request.path}\"", + new SetGameObjectSelectionResponse()); + } + + Selection.activeGameObject = go; + + return new ValueTask(new SetGameObjectSelectionResponse + { + path = GameObjectResolver.BuildPath(go.transform), + instanceId = go.GetInstanceID() + }); + } + } + + [Serializable] + public class SetGameObjectSelectionRequest + { + public string path; + } + + [Serializable] + public class SetGameObjectSelectionResponse + { + public string path; + public int instanceId; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetGameObjectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 2cbb65f04887443ec95731ad567e3e05 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetGameObjectsHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class SetGameObjectsSelectionHandler : CommandHandler + { + public override string CommandName => "Selection.SetGameObjects"; + public override string Description => "Select multiple GameObjects by paths"; + + protected override bool TryWriteFormatted(SetGameObjectsSelectionResponse response, bool success, IFormatWriter writer) + { + if (!success) return true; + + writer.WriteLine($"Selected {response.selected.Length} GameObjects:"); + foreach (var item in response.selected) + writer.WriteLine($" {item.path} (instanceId={item.instanceId})"); + + if (response.notFound != null && response.notFound.Length > 0) + { + writer.WriteLine($"Not found ({response.notFound.Length}):"); + foreach (var path in response.notFound) + writer.WriteLine($" {path}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(SetGameObjectsSelectionRequest request, CancellationToken cancellationToken) + { + if (request.paths == null || request.paths.Length == 0) + { + throw new CommandFailedException( + "paths is required", + new SetGameObjectsSelectionResponse + { + selected = Array.Empty(), + notFound = Array.Empty() + }); + } + + var objects = new List(); + var selected = new List(); + var notFound = new List(); + + foreach (var path in request.paths) + { + var go = GameObjectResolver.ResolveByPath(path); + if (go != null) + { + objects.Add(go); + selected.Add(new SelectedGameObjectInfo + { + instanceId = go.GetInstanceID(), + name = go.name, + path = GameObjectResolver.BuildPath(go.transform) + }); + } + else + { + notFound.Add(path); + } + } + + Selection.objects = objects.ToArray(); + + return new ValueTask(new SetGameObjectsSelectionResponse + { + selected = selected.ToArray(), + notFound = notFound.ToArray() + }); + } + } + + [Serializable] + public class SetGameObjectsSelectionRequest + { + public string[] paths; + } + + [Serializable] + public class SetGameObjectsSelectionResponse + { + public SelectedGameObjectInfo[] selected; + public string[] notFound; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Selection/SetGameObjectsHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 473d6b90f11714b589a3ba20a69a2bd1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/TestRunner.meta`: + +```meta +fileFormatVersion: 2 +guid: e9b6059e233a644d2b505352dc3fa503 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/TestRunner/TestRunnerHandler.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEditor.TestTools.TestRunner.Api; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class TestListHandler : CommandHandler + { + public override string CommandName => "TestRunner.List"; + public override string Description => "List available tests for EditMode or PlayMode"; + + protected override async ValueTask ExecuteAsync(TestListRequest request, CancellationToken cancellationToken) + { + var testMode = string.Equals(request.mode, "PlayMode", StringComparison.OrdinalIgnoreCase) + ? TestMode.PlayMode + : TestMode.EditMode; + + var api = ScriptableObject.CreateInstance(); + var tcs = new TaskCompletionSource(); + api.RetrieveTestList(testMode, adaptor => tcs.TrySetResult(adaptor)); + var root = await tcs.Task.WithCancellation(cancellationToken); + + var tests = new List(); + CollectTests(root, tests); + + return new TestListResponse + { + mode = testMode == TestMode.EditMode ? "EditMode" : "PlayMode", + total = tests.Count, + tests = tests.ToArray() + }; + } + + private static void CollectTests(ITestAdaptor node, List tests) + { + if (!node.IsSuite) + { + var categories = node.Categories != null ? new List(node.Categories).ToArray() : Array.Empty(); + tests.Add(new TestListEntry + { + fullName = node.FullName, + name = node.Name, + categories = categories + }); + return; + } + + if (!node.HasChildren) return; + foreach (var child in node.Children) + CollectTests(child, tests); + } + } + + [Serializable] + public class TestListRequest + { + public string mode = "EditMode"; + } + + [Serializable] + public class TestListResponse + { + public string mode; + public int total; + public TestListEntry[] tests; + } + + [Serializable] + public class TestListEntry + { + public string fullName; + public string name; + public string[] categories; + } + + public sealed class TestRunEditModeHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public TestRunEditModeHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "TestRunner.RunEditMode"; + public override string Description => "Run EditMode tests with optional name/assembly filter"; + + protected override bool TryWriteFormatted(TestRunnerResponse response, bool success, IFormatWriter writer) + => TestRunnerResponseFormatter.TryWriteFormatted(response, success, writer); + + protected override async ValueTask ExecuteAsync(TestRunRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotPlayingOrCompiling); + return await TestRunnerHelper.RunTestsAsync(TestMode.EditMode, request, cancellationToken); + } + } + + public sealed class TestRunPlayModeHandler : CommandHandler + { + private readonly EditorStateGuard _guard; + + public TestRunPlayModeHandler(EditorStateGuard guard) + { + _guard = guard; + } + + public override string CommandName => "TestRunner.RunPlayMode"; + public override string Description => "Run PlayMode tests with optional name/assembly filter"; + + protected override bool TryWriteFormatted(TestRunnerResponse response, bool success, IFormatWriter writer) + => TestRunnerResponseFormatter.TryWriteFormatted(response, success, writer); + + protected override async ValueTask ExecuteAsync(TestRunRequest request, CancellationToken cancellationToken) + { + using var scope = _guard.BeginScope(CommandName, GuardCondition.NotCompiling); + return await TestRunnerHelper.RunTestsAsync(TestMode.PlayMode, request, cancellationToken); + } + } + + internal static class TestRunnerResponseFormatter + { + public static bool TryWriteFormatted(TestRunnerResponse response, bool success, IFormatWriter writer) + { + var status = success ? "passed" : "failed"; + writer.WriteLine($"Tests {status}: {response.passed} passed, {response.failed} failed, {response.skipped} skipped ({response.total} total)"); + + if (response.results != null) + { + foreach (var result in response.results) + { + if (result.status == "Passed") + continue; + + var label = result.status == "Failed" ? "FAIL" : result.status.ToUpperInvariant(); + var line = !string.IsNullOrEmpty(result.message) + ? $" {label} {result.name} - {result.message}" + : $" {label} {result.name}"; + writer.WriteLine(line); + } + } + + return true; + } + } + + internal static class TestRunnerHelper + { + public static async ValueTask RunTestsAsync(TestMode testMode, TestRunRequest request, CancellationToken cancellationToken) + { + var api = ScriptableObject.CreateInstance(); + var tcs = new TaskCompletionSource(); + var filter = new Filter + { + testMode = testMode + }; + + if (request.testNames.Length > 0) + { + filter.testNames = request.testNames; + } + + if (request.groupNames.Length > 0) + { + filter.groupNames = request.groupNames; + } + + if (request.categories.Length > 0) + { + filter.categoryNames = request.categories; + } + + if (request.assemblies.Length > 0) + { + filter.assemblyNames = request.assemblies; + } + + var callbacks = new TestRunnerCallbacks(tcs, request.resultFilter, request.stackTraceLines); + api.RegisterCallbacks(callbacks); + try + { + api.Execute(new ExecutionSettings(filter)); + var response = await tcs.Task.WithCancellation(cancellationToken); + if (response.failed > 0) + throw new CommandFailedException($"{response.failed} test(s) failed", response); + return response; + } + finally + { + api.UnregisterCallbacks(callbacks); + } + } + } + + [Serializable] + public class TestRunRequest + { + public string[] testNames = Array.Empty(); + public string[] groupNames = Array.Empty(); + public string[] categories = Array.Empty(); + public string[] assemblies = Array.Empty(); + public string resultFilter = "failures"; // "failures" (default): failed+skipped only, "all": everything, "none": summary only + public int stackTraceLines = 0; // 0: no stack trace (default), -1: full, N>0: first N lines + } + + internal class TestRunnerCallbacks : ICallbacks + { + private readonly TaskCompletionSource _tcs; + private readonly System.Collections.Generic.List _testResults = new(); + private readonly string _resultFilter; + private readonly int _stackTraceLines; + private int _passedCount; + private int _failedCount; + private int _skippedCount; + + public TestRunnerCallbacks(TaskCompletionSource tcs, string resultFilter = "failures", int stackTraceLines = 0) + { + _tcs = tcs; + _resultFilter = string.IsNullOrEmpty(resultFilter) ? "failures" : resultFilter; + _stackTraceLines = stackTraceLines; + } + + public void RunStarted(ITestAdaptor testsToRun) + { + _passedCount = 0; + _failedCount = 0; + _skippedCount = 0; + _testResults.Clear(); + } + + public void RunFinished(ITestResultAdaptor result) + { + _tcs.SetResult(new TestRunnerResponse + { + passed = _passedCount, + failed = _failedCount, + skipped = _skippedCount, + total = _passedCount + _failedCount + _skippedCount, + results = _testResults.ToArray() + }); + } + + public void TestStarted(ITestAdaptor test) + { + } + + public void TestFinished(ITestResultAdaptor result) + { + if (result.Test.IsSuite) return; + + var status = result.TestStatus switch + { + TestStatus.Passed => "Passed", + TestStatus.Failed => "Failed", + TestStatus.Skipped => "Skipped", + _ => "Unknown" + }; + + if (result.TestStatus == TestStatus.Passed) + { + _passedCount++; + } + else if (result.TestStatus == TestStatus.Failed) + { + _failedCount++; + } + else if (result.TestStatus == TestStatus.Skipped) + { + _skippedCount++; + } + + var shouldInclude = _resultFilter switch + { + "all" => true, + "none" => false, + _ => result.TestStatus != TestStatus.Passed // "failures": failed + skipped + }; + + if (shouldInclude) + { + _testResults.Add(new TestResult + { + name = result.Test.FullName, + status = status, + duration = result.Duration, + message = result.Message ?? "", + stackTrace = StackTraceHelper.Truncate(result.StackTrace, _stackTraceLines) + }); + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/TestRunner/TestRunnerHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 56b9918e2c8464c79982a9b514f72d89 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Types.meta`: + +```meta +fileFormatVersion: 2 +guid: 445d245108640451ab8cb612207920f8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Types/TypeCacheListHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class TypeCacheListHandler : CommandHandler + { + public override string CommandName => "Type.List"; + public override string Description => "List types derived from a base type or matching a pattern"; + + protected override ValueTask ExecuteAsync(TypeCacheListRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.baseType)) + { + throw new CommandFailedException( + "baseType is required", + new TypeCacheListResponse { types = Array.Empty() }); + } + + var baseType = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => + { + try { return a.GetTypes(); } + catch { return Array.Empty(); } + }) + .FirstOrDefault(t => t.FullName == request.baseType); + + if (baseType == null) + { + throw new CommandFailedException( + $"Type '{request.baseType}' not found", + new TypeCacheListResponse { types = Array.Empty() }); + } + + var types = TypeCache.GetTypesDerivedFrom(baseType); + var typeNames = types + .Where(t => !t.IsAbstract && !t.IsInterface) + .Select(t => t.FullName) + .OrderBy(n => n) + .ToArray(); + + if (!string.IsNullOrEmpty(request.filter)) + { + typeNames = typeNames + .Where(n => n.IndexOf(request.filter, StringComparison.OrdinalIgnoreCase) >= 0) + .ToArray(); + } + + return new ValueTask(new TypeCacheListResponse + { + types = typeNames, + count = typeNames.Length + }); + } + } + + [Serializable] + public class TypeCacheListRequest + { + public string baseType; + public string filter; + } + + [Serializable] + public class TypeCacheListResponse + { + public string[] types; + public int count; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Types/TypeCacheListHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 7d431f994ecd046b0b110a8c66f7cfcd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Types/TypeInspectHandler.cs`: + +```cs +using System.Threading; +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class TypeInspectHandler : CommandHandler + { + public override string CommandName => "Type.Inspect"; + public override string Description => "Inspect nested types of a given type"; + + protected override ValueTask ExecuteAsync(TypeInspectRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.typeName)) + { + throw new CommandFailedException( + "typeName is required", + new TypeInspectResponse + { + nestedTypes = Array.Empty() + }); + } + + var type = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => + { + try { return a.GetTypes(); } + catch { return Array.Empty(); } + }) + .FirstOrDefault(t => t.FullName == request.typeName); + + if (type == null) + { + throw new CommandFailedException( + $"Type '{request.typeName}' not found", + new TypeInspectResponse + { + nestedTypes = Array.Empty() + }); + } + + var nested = type.GetNestedTypes(BindingFlags.Public | BindingFlags.Static) + .Select(nt => new TypeInspectNestedInfo + { + name = nt.Name, + fullName = nt.FullName, + isStatic = nt.IsAbstract && nt.IsSealed, + isPublic = nt.IsPublic || nt.IsNestedPublic, + memberCount = nt.GetMembers(BindingFlags.Public | BindingFlags.Static).Length + }) + .OrderBy(nt => nt.name) + .ToArray(); + + return new ValueTask(new TypeInspectResponse + { + typeName = type.FullName, + nestedTypes = nested, + count = nested.Length + }); + } + } + + [Serializable] + public class TypeInspectRequest + { + public string typeName; + } + + [Serializable] + public class TypeInspectResponse + { + public string typeName; + public TypeInspectNestedInfo[] nestedTypes; + public int count; + } + + [Serializable] + public class TypeInspectNestedInfo + { + public string name; + public string fullName; + public bool isStatic; + public bool isPublic; + public int memberCount; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Types/TypeInspectHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8ba6d359717174d8580b9f9e1aa49540 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window.meta`: + +```meta +fileFormatVersion: 2 +guid: 674d9a61be77d4abfa7de7ac641d18f2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/CreateWindowHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class CreateWindowHandler : CommandHandler + { + public override string CommandName => "Window.Create"; + public override string Description => "Create a new EditorWindow instance by type name"; + + protected override bool TryWriteFormatted(CreateWindowResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Created: {response.typeName} (instanceId={response.instanceId})"); + return true; + } + + protected override ValueTask ExecuteAsync(CreateWindowRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.typeName)) + { + throw new CommandFailedException( + "typeName is required", + new CreateWindowResponse()); + } + + var type = WindowResolver.FindWindowType(request.typeName); + if (type == null) + { + throw new CommandFailedException( + $"EditorWindow type '{request.typeName}' not found. Use Window.List to see available types.", + new CreateWindowResponse()); + } + + var window = ScriptableObject.CreateInstance(type) as EditorWindow; + if (window == null) + { + throw new CommandFailedException( + $"Failed to create instance of '{type.FullName}'", + new CreateWindowResponse { typeName = type.FullName }); + } + + window.Show(); + + return new ValueTask(new CreateWindowResponse + { + typeName = type.FullName, + instanceId = window.GetInstanceID() + }); + } + } + + [Serializable] + public class CreateWindowRequest + { + public string typeName; + } + + [Serializable] + public class CreateWindowResponse + { + public string typeName; + public int instanceId; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/CreateWindowHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4e0e3352962334634adb0b4dcc1efa01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/FocusWindowHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class FocusWindowHandler : CommandHandler + { + public override string CommandName => "Window.Focus"; + public override string Description => "Focus an already-open EditorWindow by type name"; + + protected override bool TryWriteFormatted(FocusWindowResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Focused: {response.typeName}"); + return true; + } + + protected override ValueTask ExecuteAsync(FocusWindowRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.typeName)) + { + throw new CommandFailedException( + "typeName is required", + new FocusWindowResponse()); + } + + var type = WindowResolver.FindWindowType(request.typeName); + if (type == null) + { + throw new CommandFailedException( + $"EditorWindow type '{request.typeName}' not found. Use Window.List to see available types.", + new FocusWindowResponse()); + } + + var windows = Resources.FindObjectsOfTypeAll(type); + if (windows == null || windows.Length == 0) + { + throw new CommandFailedException( + $"No open window of type '{type.FullName}'. Use Window.Open to open it first.", + new FocusWindowResponse { typeName = type.FullName }); + } + + var window = (EditorWindow)windows[0]; + window.Focus(); + + return new ValueTask(new FocusWindowResponse { typeName = type.FullName }); + } + } + + [Serializable] + public class FocusWindowRequest + { + public string typeName; + } + + [Serializable] + public class FocusWindowResponse + { + public string typeName; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/FocusWindowHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 022f8e7be0f1c4b079824be74b34bfe1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/ListWindowHandler.cs`: + +```cs +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class ListWindowHandler : CommandHandler + { + public override string CommandName => "Window.List"; + public override string Description => "List all available EditorWindow types"; + + protected override bool TryWriteFormatted(ListWindowResponse response, bool success, IFormatWriter writer) + { + if (!success || response.windows == null || response.windows.Length == 0) + { + writer.WriteLine("No EditorWindow types found."); + return true; + } + + writer.WriteLine($"Available EditorWindows ({response.windows.Length}):"); + foreach (var w in response.windows) + { + writer.WriteLine($" {w.typeName}"); + } + + return true; + } + + protected override ValueTask ExecuteAsync(Unit request, CancellationToken cancellationToken) + { + var types = WindowResolver.GetAllWindowTypes(); + var windows = types + .Select(t => new WindowInfo { typeName = t.FullName }) + .ToArray(); + + return new ValueTask(new ListWindowResponse { windows = windows }); + } + } + + [Serializable] + public class ListWindowResponse + { + public WindowInfo[] windows; + } + + [Serializable] + public class WindowInfo + { + public string typeName; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/ListWindowHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 11e968b1e1abf45b087ea682d8b80359 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/OpenWindowHandler.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + public sealed class OpenWindowHandler : CommandHandler + { + public override string CommandName => "Window.Open"; + public override string Description => "Open an EditorWindow by type name"; + + protected override bool TryWriteFormatted(OpenWindowResponse response, bool success, IFormatWriter writer) + { + if (success) + writer.WriteLine($"Opened: {response.typeName}"); + return true; + } + + protected override ValueTask ExecuteAsync(OpenWindowRequest request, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.typeName)) + { + throw new CommandFailedException( + "typeName is required", + new OpenWindowResponse()); + } + + var type = WindowResolver.FindWindowType(request.typeName); + if (type == null) + { + throw new CommandFailedException( + $"EditorWindow type '{request.typeName}' not found. Use Window.List to see available types.", + new OpenWindowResponse()); + } + + EditorWindow.GetWindow(type); + + return new ValueTask(new OpenWindowResponse { typeName = type.FullName }); + } + } + + [Serializable] + public class OpenWindowRequest + { + public string typeName; + } + + [Serializable] + public class OpenWindowResponse + { + public string typeName; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/OpenWindowHandler.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1cc7a7f1850de43f5ad8c510f2decb4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/WindowResolver.cs`: + +```cs +using System; +using System.Linq; +using UnityEditor; + +namespace UniCli.Server.Editor.Handlers +{ + internal static class WindowResolver + { + public static Type[] GetAllWindowTypes() + { + return TypeCache.GetTypesDerivedFrom() + .Where(t => !t.IsAbstract) + .OrderBy(t => t.FullName) + .ToArray(); + } + + public static Type FindWindowType(string typeName) + { + var types = GetAllWindowTypes(); + + // 完全修飾名の完全一致 + var exact = types.FirstOrDefault(t => t.FullName == typeName); + if (exact != null) return exact; + + // 短縮名の完全一致 + var shortMatch = types.FirstOrDefault(t => t.Name == typeName); + if (shortMatch != null) return shortMatch; + + // 末尾一致(例: "Inspector" → "InspectorWindow") + var suffix = types.FirstOrDefault(t => + t.Name.StartsWith(typeName, StringComparison.OrdinalIgnoreCase) || + t.FullName.EndsWith("." + typeName, StringComparison.OrdinalIgnoreCase)); + return suffix; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Handlers/Window/WindowResolver.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ad9c8540eda99422f99ef5db7f88dbcc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/IDispatcherReloader.cs`: + +```cs +namespace UniCli.Server.Editor +{ + public interface IDispatcherReloader + { + void Reload(); + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/IDispatcherReloader.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: fa8cc8c79d01f4c348a5b2212260cf16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/IFormatWriter.cs`: + +```cs +using System.Text; + +namespace UniCli.Server.Editor +{ + public interface IFormatWriter + { + void WriteLine(string line); + } + + internal sealed class StringFormatWriter : IFormatWriter + { + private readonly StringBuilder _sb; + private bool _hasContent; + + public StringFormatWriter(StringBuilder sb) + { + _sb = sb; + } + + public void WriteLine(string line) + { + if (_hasContent) + _sb.AppendLine(); + _sb.Append(line); + _hasContent = true; + } + + public override string ToString() => _sb.ToString(); + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/IFormatWriter.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3ef2a3763c1ec4e41ba3f35a5c6814c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/IServiceInstaller.cs`: + +```cs +namespace UniCli.Server.Editor +{ + public interface IServiceInstaller + { + void Install(ServiceRegistry services); + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/IServiceInstaller.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: bc0cb1e4f52a943e88254c249b3fcbfe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Internal.meta`: + +```meta +fileFormatVersion: 2 +guid: 73e60acb70405416a9a8868401f0cc5a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Internal/CommandFieldInfoExtractor.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Reflection; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Internal +{ + internal readonly struct CommandFieldExtractionResult + { + public readonly CommandFieldInfo[] Fields; + public readonly CommandTypeDetail[] TypeDetails; + + public CommandFieldExtractionResult(CommandFieldInfo[] fields, CommandTypeDetail[] typeDetails) + { + Fields = fields; + TypeDetails = typeDetails; + } + } + + internal static class CommandFieldInfoExtractor + { + public static CommandFieldExtractionResult Extract(Type type) + { + var extraction = new ExtractionState(); + var fields = extraction.ExtractFields(type); + return new CommandFieldExtractionResult(fields, extraction.GetTypeDetails()); + } + + private sealed class ExtractionState + { + private readonly List _typeDetails = new(); + private readonly HashSet _collectedTypeIds = new(StringComparer.Ordinal); + private readonly HashSet _visitingTypeIds = new(StringComparer.Ordinal); + + public CommandFieldInfo[] ExtractFields(Type type) + { + if (type == typeof(Unit)) + return Array.Empty(); + + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + var result = new List(fields.Length); + + foreach (var field in fields) + { + var nestedType = ResolveNestedType(field.FieldType); + var nestedTypeId = nestedType == null ? "" : GetTypeId(nestedType); + result.Add(new CommandFieldInfo + { + name = field.Name, + type = ToSimpleTypeName(field.FieldType), + typeId = nestedTypeId, + defaultValue = GetDefaultValueString(field) + }); + + if (nestedType != null) + CollectTypeDetail(nestedType); + } + + return result.ToArray(); + } + + public CommandTypeDetail[] GetTypeDetails() + { + return _typeDetails.ToArray(); + } + + private void CollectTypeDetail(Type type) + { + var typeId = GetTypeId(type); + if (!_collectedTypeIds.Add(typeId)) + return; + + if (!_visitingTypeIds.Add(typeId)) + return; + + var detail = new CommandTypeDetail + { + typeName = NormalizeTypeName(ToSimpleTypeName(type)), + typeId = typeId, + fields = Array.Empty() + }; + _typeDetails.Add(detail); + + try + { + detail.fields = ExtractFields(type); + } + finally + { + _visitingTypeIds.Remove(typeId); + } + } + } + + private static Type ResolveNestedType(Type type) + { + var targetType = type.IsArray ? type.GetElementType() : type; + + if (targetType == null || !IsNestedFieldTarget(targetType)) + return null; + + return targetType; + } + + private static bool IsNestedFieldTarget(Type type) + { + if (type == typeof(Unit)) + return false; + + if (type.IsPrimitive || type.IsEnum) + return false; + + if (type == typeof(string) || type == typeof(decimal)) + return false; + + if (type.Namespace != null) + { + if (type.Namespace.StartsWith("System", StringComparison.Ordinal)) + return false; + + if (type.Namespace.StartsWith("UnityEngine", StringComparison.Ordinal) + || type.Namespace.StartsWith("UnityEditor", StringComparison.Ordinal)) + return false; + } + + return type.IsClass || type.IsValueType; + } + + public static string ToSimpleTypeName(Type type) + { + if (type == typeof(string)) return "string"; + if (type == typeof(int)) return "int"; + if (type == typeof(bool)) return "bool"; + if (type == typeof(float)) return "float"; + if (type == typeof(double)) return "double"; + if (type == typeof(string[])) return "string[]"; + if (type == typeof(int[])) return "int[]"; + if (type.IsArray) return type.GetElementType()?.Name + "[]"; + return type.Name; + } + + public static string NormalizeTypeName(string typeName) + { + var normalized = typeName; + while (normalized.EndsWith("[]", StringComparison.Ordinal)) + { + normalized = normalized.Substring(0, normalized.Length - 2); + } + + return normalized; + } + + public static string GetTypeId(Type type) + { + var targetType = type.IsArray ? type.GetElementType() : type; + if (targetType == null) + return ""; + + var assemblyName = targetType.Assembly.GetName().Name ?? ""; + var fullName = targetType.FullName ?? targetType.Name; + return $"{assemblyName}:{fullName.Replace('+', '.')}"; + } + + private static string GetDefaultValueString(FieldInfo field) + { + try + { + var instance = Activator.CreateInstance(field.DeclaringType); + var value = field.GetValue(instance); + + if (value == null) return ""; + + if (field.FieldType == typeof(string)) + return value is string s && s.Length > 0 ? s : ""; + + if (field.FieldType == typeof(int)) + { + var intValue = (int)value; + return intValue != 0 ? intValue.ToString() : ""; + } + + if (field.FieldType == typeof(bool)) + { + var boolValue = (bool)value; + return boolValue ? "true" : ""; + } + + return ""; + } + catch + { + return ""; + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Internal/CommandFieldInfoExtractor.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: c4cb4879ffe8e4a15b2e5ffdabdea6d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules.meta`: + +```meta +fileFormatVersion: 2 +guid: c30e9031eaee34299a9653ba003c65cb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleAttribute.cs`: + +```cs +using System; + +namespace UniCli.Server.Editor +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + internal sealed class ModuleAttribute : Attribute + { + public string Name { get; } + public ModuleAttribute(string name) => Name = name; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleAttribute.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 36b832f25f7874a3fb4055a402c00a46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleCommandScanner.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using UniCli.Server.Editor.Handlers; +using UnityEditor; + +namespace UniCli.Server.Editor +{ + internal static class ModuleCommandScanner + { + private static Dictionary> _cache; + + /// + /// Returns a mapping of module name to sorted command names. + /// Core commands (no module) are mapped to empty string key. + /// Results are cached per domain reload. + /// + public static Dictionary> GetCommandsByModule() + { + if (_cache != null) + return _cache; + + var result = new Dictionary>(StringComparer.Ordinal); + var handlerTypes = TypeCache.GetTypesDerivedFrom(); + + foreach (var type in handlerTypes) + { + if (type.IsAbstract || type.IsInterface) + continue; + + if (type.Assembly.GetName().Name.Contains(".Tests")) + continue; + + var moduleName = ModuleRegistry.ResolveModuleName(type) ?? ""; + + string commandName; + try + { + var instance = (ICommandHandler)FormatterServices.GetUninitializedObject(type); + commandName = instance.CommandName; + } + catch + { + continue; + } + + if (string.IsNullOrEmpty(commandName)) + continue; + + if (!result.TryGetValue(moduleName, out var list)) + { + list = new List(); + result[moduleName] = list; + } + + list.Add(commandName); + } + + foreach (var list in result.Values) + list.Sort(StringComparer.Ordinal); + + _cache = result; + return _cache; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleCommandScanner.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: bbefeb603079241b9ad86246fc46b7c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleDefinition.cs`: + +```cs +namespace UniCli.Server.Editor +{ + public sealed class ModuleDefinition + { + public string Name { get; } + public string Description { get; } + + public ModuleDefinition(string name, string description) + { + Name = name; + Description = description; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleDefinition.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b88e53b3d0e5f4b8aa8bee09eeaae6ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleRegistry.cs`: + +```cs +using System; +using System.Reflection; + +namespace UniCli.Server.Editor +{ + public static class ModuleRegistry + { + public static readonly ModuleDefinition[] All = new[] + { + new ModuleDefinition("Scene", "Scene operations"), + new ModuleDefinition("GameObject", "GameObject and Component operations"), + new ModuleDefinition("Assets", "AssetDatabase, Prefab, Material operations"), + new ModuleDefinition("Profiler", "Profiler operations"), + new ModuleDefinition("Animation", "Animator and AnimatorController operations"), + + new ModuleDefinition("Remote", "Remote debug and Connection operations"), + + new ModuleDefinition("Recorder", "Video recording operations (requires com.unity.recorder)"), + new ModuleDefinition("Search", "Unity Search API operations"), + new ModuleDefinition("NuGet", "NuGet package management (requires NuGetForUnity)"), + new ModuleDefinition("BuildMagic", "BuildMagic build scheme operations (requires jp.co.cyberagent.buildmagic)"), + }; + + /// + /// Resolve the module name for a handler type. + /// Returns the [Module("X")] value if present, or null for core/user commands (always enabled). + /// + public static string ResolveModuleName(Type handlerType) + { + var attr = handlerType.GetCustomAttribute(); + return attr?.Name; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/ModuleRegistry.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 7e3f29e90ca4d4e53b13d97986eba416 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/UniCliSettings.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using UniCli.Server.Editor.Handlers; +using UnityEditor; + +namespace UniCli.Server.Editor +{ + public class UniCliSettings + { + internal const string DisabledModulesConfigKey = "UniCli.disabledModules"; + internal const string EditorLoggingEnabledConfigKey = "UniCli.editor.logging.enabled"; + + HashSet LoadDisabledModules() + { + var raw = EditorUserSettings.GetConfigValue(DisabledModulesConfigKey); + return string.IsNullOrEmpty(raw) + ? new HashSet() + : new HashSet(raw.Split(',', StringSplitOptions.RemoveEmptyEntries)); + } + + void SaveDisabledModules(HashSet modules) + { + var value = modules.Count > 0 ? string.Join(",", modules) : ""; + EditorUserSettings.SetConfigValue(DisabledModulesConfigKey, value); + } + + public bool IsEditorLoggingEnabled() + { + return ReadEditorLoggingEnabled(); + } + + public void SetEditorLoggingEnabled(bool enabled) + { + var value = enabled ? "1" : "0"; + EditorUserSettings.SetConfigValue(EditorLoggingEnabledConfigKey, value); + + ApplyEditorLoggingEnabled(enabled); + } + + internal static bool ReadEditorLoggingEnabled() + { + var raw = EditorUserSettings.GetConfigValue(EditorLoggingEnabledConfigKey); + return ParseEnabledFlag(raw, defaultValue: true); + } + + internal static void ApplyEditorLoggingEnabled(bool enabled) + { + UniCliEditorLog.EnableLogs = enabled; + } + + internal static bool ParseEnabledFlag(string raw, bool defaultValue) + { + if (string.IsNullOrEmpty(raw)) + return defaultValue; + + return raw switch + { + "1" => true, + "0" => false, + _ => bool.TryParse(raw, out var enabled) ? enabled : defaultValue + }; + } + + public bool IsModuleEnabled(string name) + { + return !LoadDisabledModules().Contains(name); + } + + public void EnableModule(string name) + { + var modules = LoadDisabledModules(); + if (modules.Remove(name)) + SaveDisabledModules(modules); + } + + public void DisableModule(string name) + { + var modules = LoadDisabledModules(); + if (modules.Add(name)) + SaveDisabledModules(modules); + } + + public string[] DiscoverAllModuleNames() + { + var names = new HashSet(); + foreach (var m in ModuleRegistry.All) + names.Add(m.Name); + + var handlerTypes = TypeCache.GetTypesDerivedFrom(); + foreach (var type in handlerTypes) + { + if (type.IsAbstract || type.IsInterface) + continue; + + var moduleName = ModuleRegistry.ResolveModuleName(type); + if (moduleName != null) + names.Add(moduleName); + } + + var sorted = names.ToList(); + sorted.Sort(); + return sorted.ToArray(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/UniCliSettings.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: e786804419d3643acbb687bb34722709 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/UniCliSettingsProvider.cs`: + +```cs +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor +{ + public class UniCliSettingsProvider : SettingsProvider + { + private readonly Dictionary _commandFoldoutStates = new(); + + public UniCliSettingsProvider() + : base("Project/UniCli", SettingsScope.Project) + { + } + + [SettingsProvider] + public static SettingsProvider Create() => new UniCliSettingsProvider(); + + public override void OnGUI(string searchContext) + { + var settings = new UniCliSettings(); + var allNames = settings.DiscoverAllModuleNames(); + var commandsByModule = ModuleCommandScanner.GetCommandsByModule(); + + var registeredDescriptions = new Dictionary(); + foreach (var m in ModuleRegistry.All) + registeredDescriptions[m.Name] = m.Description; + + EditorGUILayout.Space(4); + + using (new EditorGUILayout.HorizontalScope()) + { + GUILayout.Space(10); + using (new EditorGUILayout.VerticalScope()) + { + EditorGUILayout.LabelField("Modules", EditorStyles.boldLabel); + + EditorGUILayout.Space(2); + EditorGUILayout.HelpBox( + "Enable or disable command modules. Core commands (Compile, Eval, Console, PlayMode, Menu, etc.) are always available. User-defined modules are enabled by default.", + MessageType.Info); + + EditorGUILayout.Space(8); + + var changed = false; + foreach (var name in allNames) + { + var enabled = settings.IsModuleEnabled(name); + registeredDescriptions.TryGetValue(name, out var description); + var tooltip = string.IsNullOrEmpty(description) ? "" : description; + + var newEnabled = EditorGUILayout.ToggleLeft( + new GUIContent(name, tooltip), enabled); + + if (newEnabled != enabled) + { + if (newEnabled) + settings.EnableModule(name); + else + settings.DisableModule(name); + changed = true; + } + + DrawModuleCommands(name, commandsByModule); + } + + if (changed) + { + EditorGUILayout.Space(8); + EditorGUILayout.HelpBox( + "Module changes will take effect after the server reloads. Click the button below to apply now.", + MessageType.Warning); + } + + EditorGUILayout.Space(12); + EditorGUILayout.LabelField("Logging", EditorStyles.boldLabel); + + EditorGUILayout.Space(2); + var editorLoggingEnabled = settings.IsEditorLoggingEnabled(); + var newEditorLoggingEnabled = EditorGUILayout.ToggleLeft( + new GUIContent( + "Enable UniCli editor logs", + "Controls UniCli log, warning, and error messages emitted while running in the Unity Editor."), + editorLoggingEnabled); + + if (newEditorLoggingEnabled != editorLoggingEnabled) + { + settings.SetEditorLoggingEnabled(newEditorLoggingEnabled); + } + + EditorGUILayout.Space(8); + + if (GUILayout.Button("Reload Server", GUILayout.MaxWidth(150))) + { + UniCliServerBootstrap.ReloadDispatcher(); + } + + EditorGUILayout.Space(4); + } + GUILayout.Space(4); + } + } + + private void DrawModuleCommands(string moduleName, Dictionary> commandsByModule) + { + if (!commandsByModule.TryGetValue(moduleName, out var commands) || commands.Count == 0) + return; + + using (new EditorGUI.IndentLevelScope(2)) + { + _commandFoldoutStates.TryGetValue(moduleName, out var isExpanded); + isExpanded = EditorGUILayout.Foldout(isExpanded, $"Commands ({commands.Count})", true); + _commandFoldoutStates[moduleName] = isExpanded; + + if (!isExpanded) + return; + + using (new EditorGUI.IndentLevelScope()) + { + foreach (var commandName in commands) + { + EditorGUILayout.LabelField(commandName, EditorStyles.miniLabel); + } + } + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Modules/UniCliSettingsProvider.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 28991a6234c064e5584e6fd4a6ffefe0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/PipeServer.cs`: + +```cs +using System; +using System.Buffers; +using System.IO; +using System.IO.Pipes; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; +using UnityEngine; + +namespace UniCli.Server.Editor +{ + public sealed class PipeServer : IDisposable + { + private const byte AckByte = 0x01; + private static readonly byte[] AckBuffer = { AckByte }; + + private readonly string _pipeName; + private readonly Action> _onCommandReceived; + private readonly CancellationTokenSource _cts = new(); + private readonly TaskCompletionSource _shutdownTcs = new(); + private readonly Task _serverLoop; + + public PipeServer( + string pipeName, + Action> onCommandReceived) + { + _pipeName = pipeName ?? throw new ArgumentNullException(nameof(pipeName)); + _onCommandReceived = onCommandReceived ?? throw new ArgumentNullException(nameof(onCommandReceived)); + + _serverLoop = Task.Run(async () => await RunLoopAsync(_cts.Token)); + } + + public Task WaitForShutdownAsync(CancellationToken cancellationToken = default) + { + using var registration = cancellationToken.Register(() => _shutdownTcs.TrySetCanceled()); + return _shutdownTcs.Task; + } + + private async Task RunLoopAsync(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + await AcceptClientAsync(cancellationToken); + } + _shutdownTcs.TrySetResult(true); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + _shutdownTcs.TrySetCanceled(); + } + catch (Exception ex) + { + _shutdownTcs.TrySetException(ex); + } + } + + private async Task AcceptClientAsync(CancellationToken cancellationToken) + { + var server = new NamedPipeServerStream( + _pipeName, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous); + + await server.WaitForConnectionAsync(cancellationToken); + + // Fire-and-forget: HandleClientAsync owns the server stream and will dispose it + _ = HandleClientAsync(server, cancellationToken); + } + + private async Task HandleClientAsync(NamedPipeServerStream server, CancellationToken cancellationToken) + { + await using (server) + { + try + { + if (!await PerformHandshakeAsync(server, cancellationToken)) + return; + + while (!cancellationToken.IsCancellationRequested && server.IsConnected) + { + var request = await ReadRequestAsync(server, cancellationToken); + if (request == null) + break; + + if (!await ProcessCommandAsync(server, request, cancellationToken)) + break; + } + } + catch (Exception ex) + { + UniCliEditorLog.LogError($"[UniCli] Client handling error: {ex.Message}\n{ex.StackTrace}"); + } + } + } + + private static async Task ReadRequestAsync( + NamedPipeServerStream server, CancellationToken cancellationToken) + { + int length; + var lengthBuffer = ArrayPool.Shared.Rent(4); + try + { + if (!await ReadExactAsync(server, lengthBuffer, 4, cancellationToken)) + return null; + + length = BitConverter.ToInt32(lengthBuffer, 0); + if (length <= 0 || length > ProtocolConstants.MaxMessageSize) + { + UniCliEditorLog.LogWarning($"[UniCli] Invalid request length: {length} bytes, closing connection"); + return null; + } + } + finally + { + ArrayPool.Shared.Return(lengthBuffer); + } + + var jsonBuffer = ArrayPool.Shared.Rent(length); + try + { + if (!await ReadExactAsync(server, jsonBuffer, length, cancellationToken)) + { + UniCliEditorLog.LogWarning($"[UniCli] Client disconnected while reading request body ({length} bytes)"); + return null; + } + + var json = Encoding.UTF8.GetString(jsonBuffer, 0, length); + return JsonUtility.FromJson(json); + } + finally + { + ArrayPool.Shared.Return(jsonBuffer); + } + } + + private async Task ProcessCommandAsync( + NamedPipeServerStream server, CommandRequest request, CancellationToken cancellationToken) + { + using var commandCts = new CancellationTokenSource(); + try + { + var responseTcs = new TaskCompletionSource(); + _onCommandReceived(request, commandCts.Token, response => responseTcs.TrySetResult(response)); + + await server.WriteAsync(AckBuffer, 0, 1, cancellationToken); + await server.FlushAsync(cancellationToken); + + var monitorTask = MonitorDisconnectAsync(server, commandCts); + await Task.WhenAny(responseTcs.Task, monitorTask); + if (!responseTcs.Task.IsCompleted) + { + commandCts.Cancel(); + return false; + } + + commandCts.Cancel(); + var commandResponse = await responseTcs.Task; + + await WriteResponseAsync(server, commandResponse, cancellationToken); + return true; + } + catch (IOException ex) + { + UniCliEditorLog.LogWarning($"[UniCli] Client disconnected during response write for '{request.command}': {ex.Message}"); + return false; + } + } + + private static async Task WriteResponseAsync( + NamedPipeServerStream server, CommandResponse response, CancellationToken cancellationToken) + { + var responseJson = JsonUtility.ToJson(response); + var responseByteCount = Encoding.UTF8.GetByteCount(responseJson); + var responseBuffer = ArrayPool.Shared.Rent(responseByteCount); + try + { + Encoding.UTF8.GetBytes(responseJson, 0, responseJson.Length, responseBuffer, 0); + var responseLengthBytes = BitConverter.GetBytes(responseByteCount); + + await server.WriteAsync(responseLengthBytes, 0, 4, cancellationToken); + await server.WriteAsync(responseBuffer, 0, responseByteCount, cancellationToken); + await server.FlushAsync(cancellationToken); + } + finally + { + ArrayPool.Shared.Return(responseBuffer); + } + } + + private static async Task PerformHandshakeAsync( + NamedPipeServerStream server, CancellationToken cancellationToken) + { + var recvBuffer = ArrayPool.Shared.Rent(ProtocolConstants.HandshakeSize); + try + { + if (!await ReadExactAsync(server, recvBuffer, ProtocolConstants.HandshakeSize, cancellationToken)) + { + UniCliEditorLog.LogWarning("[UniCli] Client disconnected during handshake"); + return false; + } + + if (!ProtocolConstants.ValidateMagicBytes(recvBuffer)) + { + UniCliEditorLog.LogWarning("[UniCli] Handshake failed: invalid magic bytes from client"); + return false; + } + + var clientVersion = BitConverter.ToUInt16(recvBuffer, 4); + if (clientVersion != ProtocolConstants.ProtocolVersion) + { + UniCliEditorLog.LogWarning( + $"[UniCli] Protocol version mismatch (server: {ProtocolConstants.ProtocolVersion}, client: {clientVersion}). " + + "Please update unicli or the Unity server package."); + return false; + } + } + finally + { + ArrayPool.Shared.Return(recvBuffer); + } + + var sendBuffer = ProtocolConstants.BuildHandshakeBuffer(); + + await server.WriteAsync(sendBuffer, 0, ProtocolConstants.HandshakeSize, cancellationToken); + await server.FlushAsync(cancellationToken); + + return true; + } + + private static async Task ReadExactAsync( + NamedPipeServerStream stream, byte[] buffer, int count, CancellationToken cancellationToken) + { + var totalRead = 0; + while (totalRead < count) + { + var bytesRead = await stream.ReadAsync(buffer, totalRead, count - totalRead, cancellationToken); + if (bytesRead == 0) + return false; + totalRead += bytesRead; + } + return true; + } + + private static async Task MonitorDisconnectAsync(NamedPipeServerStream server, CancellationTokenSource commandCts) + { + try + { + var buffer = new byte[1]; + var bytesRead = await server.ReadAsync(buffer, 0, 1, commandCts.Token); + if (bytesRead == 0) + commandCts.Cancel(); + } + catch (OperationCanceledException) { } + catch (Exception) { commandCts.Cancel(); } + } + + public void Dispose() + { + _cts.Cancel(); + _serverLoop?.Wait(TimeSpan.FromMilliseconds(500)); + _cts.Dispose(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/PipeServer.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4332971f56ea04157bbca345e6420c26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/ProjectIdentifier.cs`: + +```cs +using System; +using System.Security.Cryptography; +using System.Text; +using UnityEngine; + +namespace UniCli.Server.Editor +{ + public static class ProjectIdentifier + { + private static string GetProjectHash() + { + var projectPath = NormalizePathForHash(Application.dataPath); + + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(projectPath)); + var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); + + return hash.Substring(0, 8); + } + + internal static string NormalizePathForHash(string path) + { + path = path.Replace('\\', '/'); + + if (Application.platform == RuntimePlatform.WindowsEditor) + path = path.ToLowerInvariant(); + + return path; + } + + public static string GetPipeName() + { + return $"unicli-{GetProjectHash()}"; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/ProjectIdentifier.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 96a9c96b400884bc4bb9f21bf09c4386 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol.meta`: + +```meta +fileFormatVersion: 2 +guid: 4cb6d78ea3976492abe360d4c1118a09 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/CommandInfo.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandInfo + { + public string name; + public string description; + public bool builtIn; + public string module; + public CommandFieldInfo[] requestFields; + public CommandFieldInfo[] responseFields; + public CommandTypeDetail[] requestTypeDetails; + public CommandTypeDetail[] responseTypeDetails; + } + + [Serializable] + public class CommandFieldInfo + { + public string name; + public string type; + public string typeId; + public string defaultValue; + } + + [Serializable] + public class CommandTypeDetail + { + public string typeName; + public string typeId; + public CommandFieldInfo[] fields; + } + + [Serializable] + public class CommandListResponse + { + public CommandInfo[] commands; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/CommandInfo.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 72c9283a5ee7d4d0fabfef4b7fd0d407 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/CommandRequest.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandRequest + { + public string command; + public string data; + public string format; // "json" or "text"; empty/null treated as "json" + public string cwd; // client's working directory for resolving relative paths + public string clientVersion; // client application version for compatibility check + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/CommandRequest.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8f8762b6e595c4d07a2ef1ca8c910bc6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/CommandResponse.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class CommandResponse + { + public bool success; + public string message; + public string data; // Structured data (JSON string) or formatted text + public string format; // "json" or "text"; empty/null treated as "json" + public string serverVersion; // server package version (always set) + public string versionWarning; // warning message when client/server versions differ + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/CommandResponse.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: a8a0be06a5a3e4127867755b94b9360a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/ProtocolConstants.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + public static class ProtocolConstants + { + public static readonly byte[] MagicBytes = { 0x55, 0x43, 0x4C, 0x49 }; // "UCLI" + public const ushort ProtocolVersion = 1; + public const int HandshakeSize = 6; // 4 (magic) + 2 (version) + public const int MaxMessageSize = 1024 * 1024; // 1 MB + + public static bool ValidateMagicBytes(byte[] buffer) + { + return buffer.Length >= 4 + && buffer[0] == MagicBytes[0] + && buffer[1] == MagicBytes[1] + && buffer[2] == MagicBytes[2] + && buffer[3] == MagicBytes[3]; + } + + public static byte[] BuildHandshakeBuffer() + { + var buffer = new byte[HandshakeSize]; + Array.Copy(MagicBytes, 0, buffer, 0, 4); + BitConverter.GetBytes(ProtocolVersion).CopyTo(buffer, 4); + return buffer; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/ProtocolConstants.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 05f9ceeedf9d24faaa62197fd139a33f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/Result.cs`: + +```cs +#nullable enable +using System; + +namespace UniCli.Protocol +{ + /// + /// Represents either a success or failure result + /// + /// Type of the success value + /// Type of the error value + public readonly struct Result + { + private readonly bool _isSuccess; + private readonly TSuccess? _successValue; + private readonly TError? _errorValue; + + private Result(bool isSuccess, TSuccess? successValue, TError? errorValue) + { + _isSuccess = isSuccess; + _successValue = successValue; + _errorValue = errorValue; + } + + /// + /// Whether the result is a success + /// + public bool IsSuccess => _isSuccess; + + /// + /// Whether the result is an error + /// + public bool IsError => !_isSuccess; + + /// + /// The success value (throws if IsSuccess is false) + /// + public TSuccess SuccessValue + { + get + { + if (!_isSuccess) + throw new InvalidOperationException("Cannot access SuccessValue when result is an error"); + return _successValue!; + } + } + + /// + /// The error value (throws if IsSuccess is true) + /// + public TError ErrorValue + { + get + { + if (_isSuccess) + throw new InvalidOperationException("Cannot access ErrorValue when result is a success"); + return _errorValue!; + } + } + + /// + /// Creates a success result + /// + public static Result Success(TSuccess value) + { + return new Result(true, value, default); + } + + /// + /// Creates an error result + /// + public static Result Error(TError error) + { + return new Result(false, default, error); + } + + /// + /// Pattern matches on success or error, returning a value + /// + public TResult Match( + Func onSuccess, + Func onError) + { + if (onSuccess == null) + throw new ArgumentNullException(nameof(onSuccess)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + return _isSuccess ? onSuccess(_successValue!) : onError(_errorValue!); + } + + /// + /// Pattern matches on success or error, executing an action + /// + public void Match( + Action onSuccess, + Action onError) + { + if (onSuccess == null) + throw new ArgumentNullException(nameof(onSuccess)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + if (_isSuccess) + onSuccess(_successValue!); + else + onError(_errorValue!); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/Result.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3555c9dba68a34873a5806609bebf757 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/TestRunnerResponse.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + [Serializable] + public class TestRunnerResponse + { + public int passed; + public int failed; + public int skipped; + public int total; + public TestResult[] results; + } + + [Serializable] + public class TestResult + { + public string name; + public string status; // "Passed", "Failed", "Skipped" + public double duration; // In seconds + public string message; // Error message or skip reason + public string stackTrace; // Stack trace on failure + } +} + + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/TestRunnerResponse.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 6457e3b2565b14387a6373efaf88c22e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/Unit.cs`: + +```cs +using System; + +namespace UniCli.Protocol +{ + /// + /// Represents a type with no value + /// + [Serializable] + public struct Unit + { + public static readonly Unit Value = new Unit(); + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/Protocol/Unit.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: edd94db63f9c7434db3103b577653b7b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/ServerContext.cs`: + +```cs +#nullable enable +using System; + +namespace UniCli.Server.Editor +{ + public sealed class ServerContext + { + public string ServerId { get; } + public DateTime StartedAt { get; } + public string PipeName { get; } + + public ServerContext(string pipeName) + { + ServerId = Guid.NewGuid().ToString("N")[..8]; + StartedAt = DateTime.Now; + PipeName = pipeName; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/ServerContext.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 069bec8f4311f45bc85297ccb9d61253 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/ServiceRegistry.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace UniCli.Server.Editor +{ + public sealed class ServiceRegistry + { + private readonly Dictionary _instances = new(); + private readonly Dictionary _typeMap = new(); + + public ServiceRegistry AddSingleton(TService instance) where TService : class + { + _instances[typeof(TService)] = instance ?? throw new ArgumentNullException(nameof(instance)); + return this; + } + + public ServiceRegistry AddSingleton() + where TService : class + where TImplementation : class, TService + { + _typeMap[typeof(TService)] = typeof(TImplementation); + return this; + } + + public ServiceRegistry AddSingleton() where TService : class + { + _typeMap[typeof(TService)] = typeof(TService); + return this; + } + + public T Resolve() where T : class + { + return GetService(typeof(T)) as T; + } + + private object GetService(Type serviceType) + { + if (_instances.TryGetValue(serviceType, out var instance)) + { + return instance; + } + + if (!_typeMap.TryGetValue(serviceType, out var implType)) + { + return null; + } + + instance = CreateInstance(implType); + if (instance != null) + { + _instances[serviceType] = instance; + } + + return instance; + } + + public object CreateInstance(Type type) + { + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance) + .OrderBy(c => c.GetParameters().Length) + .ToArray(); + + foreach (var ctor in constructors) + { + var parameters = ctor.GetParameters(); + var args = new object[parameters.Length]; + var canResolve = true; + + for (var i = 0; i < parameters.Length; i++) + { + var service = GetService(parameters[i].ParameterType); + if (service == null) + { + canResolve = false; + break; + } + args[i] = service; + } + + if (canResolve) + { + return Activator.CreateInstance(type, args); + } + } + + return null; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/ServiceRegistry.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 310a1c8d3fa0e4c31b73ae6835ad8b4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/StackTraceHelper.cs`: + +```cs +namespace UniCli.Server.Editor +{ + internal static class StackTraceHelper + { + /// + /// Truncate a stack trace to the specified number of lines. + /// lines < 0: return full stack trace, 0: return empty string, N > 0: return first N lines. + /// + public static string Truncate(string stackTrace, int lines) + { + if (string.IsNullOrEmpty(stackTrace)) + return ""; + + if (lines < 0) + return stackTrace; + + if (lines == 0) + return ""; + + var count = 0; + var endIndex = 0; + for (var i = 0; i < stackTrace.Length; i++) + { + if (stackTrace[i] != '\n') + continue; + + count++; + if (count >= lines) + { + endIndex = i; + break; + } + } + + if (count < lines) + return stackTrace; + + return stackTrace.Substring(0, endIndex); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/StackTraceHelper.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3c09cdd1c6fa540dcb688ee98c992ce7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/TaskExtensions.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace UniCli.Server.Editor +{ + internal static class TaskExtensions + { + public static ValueTask WithCancellation(this Task task, CancellationToken ct) + { + if (!ct.CanBeCanceled || task.IsCompleted) + return new ValueTask(task); + + if (ct.IsCancellationRequested) + return new ValueTask(Task.FromCanceled(ct)); + + return new ValueTask(WaitWithCancellation(task, ct)); + + static async Task WaitWithCancellation(Task task, CancellationToken ct) + { + var tcs = new TaskCompletionSource(); + using (ct.Register(() => tcs.TrySetResult(true))) + { + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(ct); + } + return await task; + } + } + + public static ValueTask WithCancellation(this Task task, CancellationToken ct) + { + if (!ct.CanBeCanceled || task.IsCompleted) + return new ValueTask(task); + + if (ct.IsCancellationRequested) + return new ValueTask(Task.FromCanceled(ct)); + + return new ValueTask(WaitWithCancellation(task, ct)); + + static async Task WaitWithCancellation(Task task, CancellationToken ct) + { + var tcs = new TaskCompletionSource(); + using (ct.Register(() => tcs.TrySetResult(true))) + { + if (task != await Task.WhenAny(task, tcs.Task)) + throw new OperationCanceledException(ct); + } + await task; + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/TaskExtensions.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: e6862af0b50ba4ff6b4c8d9edc076e5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCli.Server.Editor.asmdef`: + +```asmdef +{ + "name": "UniCli.Server.Editor", + "rootNamespace": "UniCli.Server.Editor", + "references": [ + "UniCli.Remote", + "Unity.Recorder.Editor" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.recorder", + "expression": "", + "define": "UNICLI_RECORDER" + } + ], + "noEngineReferences": false +} +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCli.Server.Editor.asmdef.meta`: + +```meta +fileFormatVersion: 2 +guid: 7aed55315719d40019b85604fd4c4c21 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCliServer.cs`: + +```cs +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using UniCli.Protocol; + +namespace UniCli.Server.Editor +{ + /// + /// UniCli Server (pure C# implementation) + /// Unity-independent server logic + /// + public sealed class UniCliServer : IDisposable + { + private readonly string _pipeName; + private CommandDispatcher _dispatcher; + private readonly ConcurrentQueue<(CommandRequest request, CancellationToken cancellationToken, Action callback)> _commandQueue; + private readonly CancellationTokenSource _cts; + private readonly Action _logger; + private readonly Action _errorLogger; + private readonly Task _serverLoop; + private Task? _currentCommand; + + public string? CurrentCommandName { get; private set; } + public DateTime? CurrentCommandStartTime { get; private set; } + public string[] QueuedCommandNames => _commandQueue.ToArray().Select(item => item.request.command).ToArray(); + + public UniCliServer( + string pipeName, + CommandDispatcher dispatcher, + Action logger, + Action errorLogger) + { + _pipeName = pipeName ?? throw new ArgumentNullException(nameof(pipeName)); + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _errorLogger = errorLogger ?? throw new ArgumentNullException(nameof(errorLogger)); + + _commandQueue = new ConcurrentQueue<(CommandRequest, CancellationToken, Action)>(); + _cts = new CancellationTokenSource(); + + _serverLoop = Task.Run( + async () => await RunServerLoopAsync(_cts.Token), + _cts.Token); + } + + private void Stop() + { + _cts.Cancel(); + _serverLoop.Wait(TimeSpan.FromMilliseconds(500)); + } + + public void ReplaceDispatcher(CommandDispatcher dispatcher) + { + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + } + + public void ProcessCommands() + { + if (_currentCommand is { IsCompleted: false }) + return; + + _currentCommand = null; + + if (_commandQueue.TryDequeue(out var item)) + { + var (request, cancellationToken, callback) = item; + CurrentCommandName = request.command; + CurrentCommandStartTime = DateTime.UtcNow; + _currentCommand = ProcessCommandAsync(request, cancellationToken, callback); + } + } + + private async Task RunServerLoopAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + using var pipeServer = new PipeServer( + _pipeName, + OnCommandReceived); + + await pipeServer.WaitForShutdownAsync(cancellationToken); + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + _errorLogger($"[UniCli] Server error: {ex.Message}"); + } + + try + { + if (!cancellationToken.IsCancellationRequested) + await Task.Delay(2000, cancellationToken); + } + catch (OperationCanceledException) + { + break; + } + } + } + + private void OnCommandReceived(CommandRequest request, CancellationToken cancellationToken, Action callback) + { + if (_currentCommand is { IsCompleted: false } || !_commandQueue.IsEmpty) + { + var busyCommand = CurrentCommandName ?? "unknown"; + callback(new CommandResponse + { + success = false, + message = $"Server is busy executing '{busyCommand}'. Please retry after the current command completes.", + data = "" + }); + return; + } + + _commandQueue.Enqueue((request, cancellationToken, callback)); + } + + private async Task ProcessCommandAsync(CommandRequest request, CancellationToken cancellationToken, Action callback) + { + try + { + var response = await _dispatcher.DispatchAsync(request, cancellationToken); + callback(response); + } + catch (OperationCanceledException) + { + _logger($"[UniCli] Command '{request.command}' cancelled (client disconnected)"); + callback(new CommandResponse + { + success = false, + message = "Command cancelled: client disconnected", + data = "" + }); + } + catch (Exception ex) + { + _errorLogger($"[UniCli] Command processing error: {ex.Message}"); + callback(new CommandResponse + { + success = false, + message = $"Internal error: {ex.Message}", + data = "" + }); + } + finally + { + CurrentCommandName = null; + CurrentCommandStartTime = null; + } + } + + public void Dispose() + { + Stop(); + _cts.Dispose(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCliServer.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1c729cbb30fea485f9718146c1dc7dd7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCliServerBootstrap.cs`: + +```cs +#nullable enable +using System; +using System.Diagnostics; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor +{ + internal static class UniCliEditorLog + { + public static bool EnableLogs { get; set; } = UniCliSettings.ReadEditorLoggingEnabled(); + + public static void Log(string message) + { + if (EnableLogs) + UnityEngine.Debug.Log(message); + } + + public static void LogWarning(string message) + { + if (EnableLogs) + UnityEngine.Debug.LogWarning(message); + } + + public static void LogError(string message) + { + if (EnableLogs) + UnityEngine.Debug.LogError(message); + } + } + + [InitializeOnLoad] + public static class UniCliServerBootstrap + { + private static UniCliServer? _server; + private static CommandDispatcher? _dispatcher; + private static bool _originalRunInBackground; + + public static ServiceRegistry Services { get; } = new(); + public static CommandDispatcher? Dispatcher => _dispatcher; + public static bool IsRunning => _server != null; + public static string? CurrentCommandName => _server?.CurrentCommandName; + public static DateTime? CurrentCommandStartTime => _server?.CurrentCommandStartTime; + public static string[] QueuedCommandNames => _server?.QueuedCommandNames ?? Array.Empty(); + + static UniCliServerBootstrap() + { + EnsurePidFile(); + + EditorApplication.update -= Initialize; + EditorApplication.update += Initialize; + } + + private static void Initialize() + { + EditorApplication.update -= Initialize; + + RunServiceInstallers(Services); + + AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; + AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; + AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; + AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; + EditorApplication.quitting -= OnEditorQuitting; + EditorApplication.quitting += OnEditorQuitting; + + EditorApplication.update -= OnEditorUpdate; + EditorApplication.update += OnEditorUpdate; + + EditorApplication.update -= StartServer; + EditorApplication.update += StartServer; + } + + public static void StartServer() + { + EditorApplication.update -= StartServer; + + if (_server != null) + return; + + _originalRunInBackground = Application.runInBackground; + Application.runInBackground = true; + + var pipeName = ProjectIdentifier.GetPipeName(); + _dispatcher = new CommandDispatcher(Services); + _server = new UniCliServer( + pipeName, + _dispatcher, + logger: UniCliEditorLog.Log, + errorLogger: UniCliEditorLog.LogError + ); + } + + public static void ReloadDispatcher() + { + if (_server == null) + return; + + _dispatcher = new CommandDispatcher(Services); + _server.ReplaceDispatcher(_dispatcher); + UniCliEditorLog.Log("[UniCli] Dispatcher reloaded"); + } + + public static void StopServer() + { + if (_server == null) + return; + + Application.runInBackground = _originalRunInBackground; + _server.Dispose(); + _server = null; + _dispatcher = null; + } + + private static string GetPidFilePath() + { + return Path.Combine(Application.dataPath, "..", "Library", "UniCli", "server.pid"); + } + + private static void EnsurePidFile() + { + try + { + var path = GetPidFilePath(); + var pid = Process.GetCurrentProcess().Id.ToString(); + + if (File.Exists(path) && File.ReadAllText(path).Trim() == pid) + return; + + var dir = Path.GetDirectoryName(path); + if (dir != null && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + File.WriteAllText(path, pid); + } + catch (Exception ex) + { + UniCliEditorLog.LogWarning($"[UniCli] Failed to write PID file: {ex.Message}"); + } + } + + private static void DeletePidFile() + { + try + { + var path = GetPidFilePath(); + if (File.Exists(path)) + File.Delete(path); + } + catch + { + // Best-effort deletion + } + } + + private static void RunServiceInstallers(ServiceRegistry services) + { + var installerTypes = TypeCache.GetTypesDerivedFrom(); + + foreach (var type in installerTypes) + { + if (type.IsAbstract || type.IsInterface) + continue; + + try + { + var installer = (IServiceInstaller)Activator.CreateInstance(type); + installer.Install(services); + } + catch (Exception ex) + { + UniCliEditorLog.LogError($"[UniCli] Failed to run service installer {type.FullName}: {ex.Message}"); + } + } + } + + private static void OnBeforeAssemblyReload() + { + EditorApplication.update -= OnEditorUpdate; + AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; + + StopServer(); + } + + private static void OnAfterAssemblyReload() + { + AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; + EditorApplication.update += OnEditorUpdate; + + EditorApplication.update -= StartServer; + EditorApplication.update += StartServer; + } + + private static void OnEditorQuitting() + { + DeletePidFile(); + } + + private static void OnEditorUpdate() + { + _server?.ProcessCommands(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCliServerBootstrap.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8161365bbfb38441182fa5941ed09252 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCliServerWindow.cs`: + +```cs +using System; +using System.Collections.Generic; +using UniCli.Protocol; +using UnityEditor; +using UnityEngine; + +namespace UniCli.Server.Editor +{ + public sealed class UniCliServerWindow : EditorWindow + { + private static readonly Color SeparatorColor = new(0.5f, 0.5f, 0.5f, 0.3f); + + private readonly Dictionary _foldoutStates = new(); + private Vector2 _scrollPosition; + private bool _needsSeparator; + + [MenuItem("Window/UniCli Server")] + private static void Open() + { + GetWindow("UniCli Server"); + } + + private void OnInspectorUpdate() + { + Repaint(); + } + + private void OnGUI() + { + _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); + + DrawServerStatus(); + EditorGUILayout.Space(4); + DrawCommands(); + + EditorGUILayout.EndScrollView(); + } + + private void DrawServerStatus() + { + EditorGUILayout.LabelField("Server Status", EditorStyles.boldLabel); + + using (new EditorGUILayout.VerticalScope("box")) + { + var isRunning = UniCliServerBootstrap.IsRunning; + var commandName = UniCliServerBootstrap.CurrentCommandName; + var startTime = UniCliServerBootstrap.CurrentCommandStartTime; + + var statusStyle = new GUIStyle(EditorStyles.label) { fontStyle = FontStyle.Bold }; + var prevColor = GUI.contentColor; + + if (!isRunning) + { + GUI.contentColor = new Color(0.9f, 0.3f, 0.3f); + EditorGUILayout.LabelField("\u25cf Stopped", statusStyle); + } + else if (commandName != null && startTime.HasValue) + { + var elapsed = (DateTime.UtcNow - startTime.Value).TotalSeconds; + var dotCount = (int)(EditorApplication.timeSinceStartup * 2) % 3 + 1; + var dots = new string('.', dotCount); + + GUI.contentColor = new Color(1.0f, 0.7f, 0.2f); + EditorGUILayout.LabelField($"\u25cf Running \u2014 {commandName} ({elapsed:F1}s){dots}", statusStyle); + } + else + { + GUI.contentColor = new Color(0.2f, 0.8f, 0.2f); + EditorGUILayout.LabelField("\u25cf Running", statusStyle); + } + + GUI.contentColor = prevColor; + + EditorGUILayout.LabelField("Project", Application.dataPath); + EditorGUILayout.LabelField("Pipe", ProjectIdentifier.GetPipeName()); + + if (isRunning) + { + var queued = UniCliServerBootstrap.QueuedCommandNames; + if (queued.Length > 0) + { + EditorGUILayout.Space(2); + var queueLabel = $"Queued ({queued.Length}): {string.Join(", ", queued)}"; + EditorGUILayout.LabelField(queueLabel, EditorStyles.miniLabel); + } + } + + EditorGUILayout.Space(2); + + if (isRunning) + { + if (GUILayout.Button("Stop Server", GUILayout.Height(24))) + { + UniCliServerBootstrap.StopServer(); + } + } + else + { + if (GUILayout.Button("Start Server", GUILayout.Height(24))) + { + UniCliServerBootstrap.StartServer(); + } + } + } + } + + private void DrawCommands() + { + EditorGUILayout.LabelField("Commands", EditorStyles.boldLabel); + + var dispatcher = UniCliServerBootstrap.Dispatcher; + if (dispatcher == null) + { + using (new EditorGUILayout.VerticalScope("box")) + { + EditorGUILayout.LabelField("Server is not running."); + } + return; + } + + var commands = dispatcher.GetAllCommandInfo(); + Array.Sort(commands, (a, b) => string.Compare(a.name, b.name, StringComparison.Ordinal)); + + var moduleGroups = new SortedDictionary>(StringComparer.Ordinal); + foreach (var command in commands) + { + var moduleName = string.IsNullOrEmpty(command.module) ? "Core" : command.module; + if (!moduleGroups.TryGetValue(moduleName, out var list)) + { + list = new List(); + moduleGroups[moduleName] = list; + } + list.Add(command); + } + + _needsSeparator = false; + + using (new EditorGUILayout.VerticalScope("box")) + { + // Draw "Core" first if it exists, then the rest alphabetically + if (moduleGroups.TryGetValue("Core", out var coreCommands)) + { + DrawSeparatorIfNeeded(); + DrawModuleGroup("Core", coreCommands); + } + + foreach (var kvp in moduleGroups) + { + if (kvp.Key == "Core") + continue; + DrawSeparatorIfNeeded(); + DrawModuleGroup(kvp.Key, kvp.Value); + } + } + } + + private void DrawModuleGroup(string moduleName, List commands) + { + var key = $"__module__:{moduleName}"; + _foldoutStates.TryGetValue(key, out var isExpanded); + isExpanded = EditorGUILayout.Foldout(isExpanded, $"{moduleName} ({commands.Count} commands)", true); + _foldoutStates[key] = isExpanded; + + if (!isExpanded) + return; + + using (new EditorGUI.IndentLevelScope()) + { + var standaloneCommands = new List(); + var categorizedCommands = new SortedDictionary>(StringComparer.Ordinal); + + foreach (var command in commands) + { + var dotIndex = command.name.IndexOf('.'); + if (dotIndex < 0) + { + standaloneCommands.Add(command); + } + else + { + var category = command.name.Substring(0, dotIndex); + if (!categorizedCommands.TryGetValue(category, out var list)) + { + list = new List(); + categorizedCommands[category] = list; + } + list.Add(command); + } + } + + foreach (var command in standaloneCommands) + DrawCommandFoldout(command); + + foreach (var kvp in categorizedCommands) + DrawCategoryFoldout(kvp.Key, kvp.Value); + } + } + + private void DrawSeparatorIfNeeded() + { + if (!_needsSeparator) + { + _needsSeparator = true; + return; + } + + var rect = EditorGUILayout.GetControlRect(false, 1); + if (Event.current.type == EventType.Repaint) + { + EditorGUI.DrawRect(rect, SeparatorColor); + } + } + + private void DrawCategoryFoldout(string category, List commands) + { + var key = $"__category__:{category}"; + _foldoutStates.TryGetValue(key, out var isExpanded); + isExpanded = EditorGUILayout.Foldout(isExpanded, $"{category} ({commands.Count})", true); + _foldoutStates[key] = isExpanded; + + if (!isExpanded) + return; + + using (new EditorGUI.IndentLevelScope()) + { + foreach (var command in commands) + { + DrawCommandFoldout(command); + } + } + } + + private void DrawCommandFoldout(CommandInfo command) + { + _foldoutStates.TryGetValue(command.name, out var isExpanded); + isExpanded = EditorGUILayout.Foldout(isExpanded, command.name, true); + _foldoutStates[command.name] = isExpanded; + + if (!isExpanded) + return; + + using (new EditorGUI.IndentLevelScope()) + { + DrawCommandContent(command); + } + } + + private static void DrawCommandContent(CommandInfo command) + { + if (!string.IsNullOrEmpty(command.description)) + { + EditorGUILayout.LabelField("Description", command.description); + } + + DrawFieldTable("Request Fields", command.requestFields); + DrawFieldTable("Response Fields", command.responseFields); + } + + private static void DrawFieldTable(string label, CommandFieldInfo[] fields) + { + if (fields == null || fields.Length == 0) + { + EditorGUILayout.LabelField(label, "(none)"); + return; + } + + EditorGUILayout.LabelField(label); + + using (new EditorGUI.IndentLevelScope()) + { + foreach (var field in fields) + { + var defaultPart = string.IsNullOrEmpty(field.defaultValue) + ? "" + : $" = {field.defaultValue}"; + EditorGUILayout.LabelField(field.name, $"{field.type}{defaultPart}"); + } + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Editor/UniCliServerWindow.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ff34ba451c4f84ad2b4d7c3f797d6b8e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime.meta`: + +```meta +fileFormatVersion: 2 +guid: d4da746d89286474f831fec700b59cf5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote.meta`: + +```meta +fileFormatVersion: 2 +guid: 50e62cf1ea53a4d3295fe6cd5e0c2ffc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands.meta`: + +```meta +fileFormatVersion: 2 +guid: 44e27635763084860bb47b4152b2f183 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/FindGameObjectsCommand.cs`: + +```cs +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace UniCli.Remote.Commands +{ + public sealed class FindGameObjectsCommand : DebugCommand + { + public override string CommandName => "Debug.FindGameObjects"; + public override string Description => "Find GameObjects by name (substring match)"; + + protected override Response ExecuteCommand(Request request) + { + if (string.IsNullOrEmpty(request.name)) + throw new ArgumentException("'name' is required"); + + var results = new List(); + var allObjects = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + + foreach (var go in allObjects) + { + if (go.name.IndexOf(request.name, StringComparison.OrdinalIgnoreCase) < 0) + continue; + + results.Add(new GameObjectInfo + { + name = go.name, + path = GetPath(go.transform), + isActive = go.activeInHierarchy, + scene = go.scene.name, + componentCount = go.GetComponents().Length + }); + } + + return new Response + { + query = request.name, + count = results.Count, + results = results.ToArray() + }; + } + + private static string GetPath(Transform transform) + { + var path = transform.name; + while (transform.parent != null) + { + transform = transform.parent; + path = transform.name + "/" + path; + } + return path; + } + + [Serializable] + public class Request + { + public string name; + } + + [Serializable] + public class Response + { + public string query; + public int count; + public GameObjectInfo[] results; + } + + [Serializable] + public class GameObjectInfo + { + public string name; + public string path; + public bool isActive; + public string scene; + public int componentCount; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/FindGameObjectsCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 151d7c24829f94b8c80413afc8cc5739 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetHierarchyCommand.cs`: + +```cs +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace UniCli.Remote.Commands +{ + public sealed class GetHierarchyCommand : DebugCommand + { + public override string CommandName => "Debug.GetHierarchy"; + public override string Description => "Get scene hierarchy including inactive objects"; + + protected override Response ExecuteCommand(Unit request) + { + var scene = SceneManager.GetActiveScene(); + var rootObjects = scene.GetRootGameObjects(); + var nodes = new List(); + + foreach (var root in rootObjects) + CollectNodes(root.transform, 0, nodes); + + return new Response + { + sceneName = scene.name, + nodes = nodes.ToArray() + }; + } + + private static void CollectNodes(Transform transform, int depth, List nodes) + { + var go = transform.gameObject; + var components = go.GetComponents(); + var componentNames = new List(components.Length); + foreach (var component in components) + { + if (component != null) + componentNames.Add(component.GetType().FullName); + } + + nodes.Add(new HierarchyNode + { + name = go.name, + depth = depth, + isActive = go.activeSelf, + componentCount = components.Length, + components = componentNames.ToArray() + }); + + for (var i = 0; i < transform.childCount; i++) + CollectNodes(transform.GetChild(i), depth + 1, nodes); + } + + [Serializable] + public class Response + { + public string sceneName; + public HierarchyNode[] nodes; + } + + [Serializable] + public class HierarchyNode + { + public string name; + public int depth; + public bool isActive; + public int componentCount; + public string[] components; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetHierarchyCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 33d2f02dcf4754fca8aacc713cc5463e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetLogsCommand.cs`: + +```cs +using System; +using UnityEngine; + +namespace UniCli.Remote.Commands +{ + public sealed class GetLogsCommand : DebugCommand + { + public override string CommandName => "Debug.GetLogs"; + public override string Description => "Get recent log entries"; + + protected override Response ExecuteCommand(Request request) + { + var capture = RuntimeDebugReceiver.RuntimeLogCapture; + if (capture == null) + throw new InvalidOperationException("RuntimeLogCapture is not initialized"); + + LogType? typeFilter = null; + if (!string.IsNullOrEmpty(request.type)) + { + if (Enum.TryParse(request.type, true, out var parsed)) + typeFilter = parsed; + } + + var entries = capture.GetEntries(request.limit, typeFilter); + + return new Response + { + count = entries.Length, + entries = entries + }; + } + + [Serializable] + public class Request + { + public int limit = 50; + public string type; + } + + [Serializable] + public class Response + { + public int count; + public RuntimeLogCapture.LogEntry[] entries; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetLogsCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ea372f783b42a4bf9835dcb765538505 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetPlayerPrefCommand.cs`: + +```cs +using System; +using UnityEngine; + +namespace UniCli.Remote.Commands +{ + public sealed class GetPlayerPrefCommand : DebugCommand + { + public override string CommandName => "Debug.GetPlayerPref"; + public override string Description => "Get a PlayerPrefs value by key"; + + protected override Response ExecuteCommand(Request request) + { + if (string.IsNullOrEmpty(request.key)) + throw new ArgumentException("'key' is required"); + + if (!PlayerPrefs.HasKey(request.key)) + { + return new Response + { + key = request.key, + exists = false + }; + } + + var stringValue = PlayerPrefs.GetString(request.key, null); + var intValue = PlayerPrefs.GetInt(request.key, 0); + var floatValue = PlayerPrefs.GetFloat(request.key, 0f); + + return new Response + { + key = request.key, + exists = true, + stringValue = stringValue, + intValue = intValue, + floatValue = floatValue + }; + } + + [Serializable] + public class Request + { + public string key; + } + + [Serializable] + public class Response + { + public string key; + public bool exists; + public string stringValue; + public int intValue; + public float floatValue; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetPlayerPrefCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 38ed614abd0954cfaa556118fb61511b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetScenesCommand.cs`: + +```cs +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace UniCli.Remote.Commands +{ + public sealed class GetScenesCommand : DebugCommand + { + public override string CommandName => "Debug.GetScenes"; + public override string Description => "List all loaded scenes"; + + protected override Response ExecuteCommand(Unit request) + { + var activeScene = SceneManager.GetActiveScene(); + var scenes = new List(); + + for (var i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + scenes.Add(new SceneInfo + { + name = scene.name, + path = scene.path, + buildIndex = scene.buildIndex, + isLoaded = scene.isLoaded, + isActive = scene == activeScene, + rootCount = scene.isLoaded ? scene.rootCount : 0 + }); + } + + return new Response + { + scenes = scenes.ToArray() + }; + } + + [Serializable] + public class Response + { + public SceneInfo[] scenes; + } + + [Serializable] + public class SceneInfo + { + public string name; + public string path; + public int buildIndex; + public bool isLoaded; + public bool isActive; + public int rootCount; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/GetScenesCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 2e28b60852d0143a0980ae597cbf266a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/StatsCommand.cs`: + +```cs +using System; +using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.SceneManagement; + +namespace UniCli.Remote.Commands +{ + public sealed class StatsCommand : DebugCommand + { + public override string CommandName => "Debug.Stats"; + public override string Description => "Get runtime performance statistics"; + + protected override Response ExecuteCommand(Unit request) + { + var resolution = Screen.currentResolution; + + return new Response + { + fps = 1f / Time.unscaledDeltaTime, + unscaledDeltaTime = Time.unscaledDeltaTime, + frameCount = Time.frameCount, + timeScale = Time.timeScale, + fixedDeltaTime = Time.fixedDeltaTime, + realtimeSinceStartup = Time.realtimeSinceStartup, + totalAllocatedMemory = Profiler.GetTotalAllocatedMemoryLong(), + totalReservedMemory = Profiler.GetTotalReservedMemoryLong(), + totalUnusedReservedMemory = Profiler.GetTotalUnusedReservedMemoryLong(), + monoUsedSize = Profiler.GetMonoUsedSizeLong(), + monoHeapSize = Profiler.GetMonoHeapSizeLong(), + gcCollectionCount0 = GC.CollectionCount(0), + gcCollectionCount1 = GC.CollectionCount(1), + gcCollectionCount2 = GC.CollectionCount(2), + gameObjectCount = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None).Length, + loadedSceneCount = SceneManager.sceneCount, + screenOrientation = Screen.orientation.ToString(), + currentResolutionWidth = resolution.width, + currentResolutionHeight = resolution.height + }; + } + + [Serializable] + public class Response + { + public float fps; + public float unscaledDeltaTime; + public int frameCount; + public float timeScale; + public float fixedDeltaTime; + public float realtimeSinceStartup; + public long totalAllocatedMemory; + public long totalReservedMemory; + public long totalUnusedReservedMemory; + public long monoUsedSize; + public long monoHeapSize; + public int gcCollectionCount0; + public int gcCollectionCount1; + public int gcCollectionCount2; + public int gameObjectCount; + public int loadedSceneCount; + public string screenOrientation; + public int currentResolutionWidth; + public int currentResolutionHeight; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/StatsCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: eb3c2b4ce6b664647ab0ea3402fe5764 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/SystemInfoCommand.cs`: + +```cs +using System; +using UnityEngine; + +namespace UniCli.Remote.Commands +{ + public sealed class SystemInfoCommand : DebugCommand + { + public override string CommandName => "Debug.SystemInfo"; + public override string Description => "Get device and application information"; + + protected override Response ExecuteCommand(Unit request) + { + return new Response + { + platform = Application.platform.ToString(), + unityVersion = Application.unityVersion, + appVersion = Application.version, + productName = Application.productName, + companyName = Application.companyName, + identifier = Application.identifier, + systemLanguage = Application.systemLanguage.ToString(), + installerName = Application.installerName, + deviceModel = SystemInfo.deviceModel, + deviceName = SystemInfo.deviceName, + operatingSystem = SystemInfo.operatingSystem, + processorType = SystemInfo.processorType, + processorCount = SystemInfo.processorCount, + systemMemorySize = SystemInfo.systemMemorySize, + graphicsDeviceName = SystemInfo.graphicsDeviceName, + graphicsDeviceType = SystemInfo.graphicsDeviceType.ToString(), + graphicsMemorySize = SystemInfo.graphicsMemorySize, + graphicsShaderLevel = SystemInfo.graphicsShaderLevel, + maxTextureSize = SystemInfo.maxTextureSize, + supportsGyroscope = SystemInfo.supportsGyroscope, + supportsLocationService = SystemInfo.supportsLocationService, + batteryLevel = SystemInfo.batteryLevel, + batteryStatus = SystemInfo.batteryStatus.ToString(), + internetReachability = Application.internetReachability.ToString(), + screenWidth = Screen.width, + screenHeight = Screen.height, + screenDpi = Screen.dpi, + qualityLevel = QualitySettings.GetQualityLevel(), + qualityName = QualitySettings.names[QualitySettings.GetQualityLevel()], + targetFrameRate = Application.targetFrameRate + }; + } + + [Serializable] + public class Response + { + public string platform; + public string unityVersion; + public string appVersion; + public string productName; + public string companyName; + public string identifier; + public string systemLanguage; + public string installerName; + public string deviceModel; + public string deviceName; + public string operatingSystem; + public string processorType; + public int processorCount; + public int systemMemorySize; + public string graphicsDeviceName; + public string graphicsDeviceType; + public int graphicsMemorySize; + public int graphicsShaderLevel; + public int maxTextureSize; + public bool supportsGyroscope; + public bool supportsLocationService; + public float batteryLevel; + public string batteryStatus; + public string internetReachability; + public int screenWidth; + public int screenHeight; + public float screenDpi; + public int qualityLevel; + public string qualityName; + public int targetFrameRate; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Commands/SystemInfoCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ba1f65f65fdf04cca87abbc109b8c017 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/DebugCommand.cs`: + +```cs +using UnityEngine; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + [RequireDerived] + public abstract class DebugCommand + { + public abstract string CommandName { get; } + public virtual string Description => ""; + public abstract string Execute(string requestJson); + } + + [Preserve] + [RequireDerived] + public abstract class DebugCommand : DebugCommand + { + public override string Execute(string requestJson) + { + TRequest request; + if (typeof(TRequest) == typeof(Unit)) + { + request = (TRequest)(object)Unit.Value; + } + else if (string.IsNullOrEmpty(requestJson)) + { + request = JsonUtility.FromJson("{}"); + } + else + { + request = JsonUtility.FromJson(requestJson); + } + + var response = ExecuteCommand(request); + + if (response is Unit) + return ""; + + return JsonUtility.ToJson(response); + } + + protected abstract TResponse ExecuteCommand(TRequest request); + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/DebugCommand.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b09d1a0a3d91947148e12087148a139d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/DebugCommandRegistry.cs`: + +```cs +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + public sealed class DebugCommandRegistry + { + private readonly Dictionary _commands = new(); + + public void DiscoverCommands() + { + _commands.Clear(); + + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + Type[] types; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + types = e.Types; + } + + foreach (var type in types) + { + if (type == null || type.IsAbstract || type.IsInterface) + continue; + + if (!typeof(DebugCommand).IsAssignableFrom(type)) + continue; + + try + { + var instance = (DebugCommand)Activator.CreateInstance(type); + if (!_commands.TryAdd(instance.CommandName, instance)) + continue; + } + catch + { + // Silently skip commands that fail to instantiate. + } + } + } + } + + public bool TryGetCommand(string name, out DebugCommand command) + { + return _commands.TryGetValue(name, out command); + } + + public RuntimeCommandInfo[] GetCommandInfos() + { + var infos = new List(_commands.Count); + foreach (var kvp in _commands) + { + infos.Add(new RuntimeCommandInfo + { + name = kvp.Value.CommandName, + description = kvp.Value.Description + }); + } + return infos.ToArray(); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/DebugCommandRegistry.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1052731e310c34886a95f85d41589d47 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol.meta`: + +```meta +fileFormatVersion: 2 +guid: 292580c8f1f1b4022b6edc7c1fba29cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeChunkedMessage.cs`: + +```cs +using System; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + [Serializable] + public class RuntimeChunkedMessage + { + public string requestId; + public int chunkIndex; + public int totalChunks; + public string data; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeChunkedMessage.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 32128f79cf1ad4a20a8c3734179960dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeCommandInfo.cs`: + +```cs +using System; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + [Serializable] + public class RuntimeCommandInfo + { + public string name; + public string description; + } + + [Preserve] + [Serializable] + public class RuntimeListRequest + { + public string requestId; + } + + [Preserve] + [Serializable] + public class RuntimeListResponse + { + public string requestId; + public RuntimeCommandInfo[] commands; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeCommandInfo.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 4db8bcdef116245068dd2b2f0a32b54c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeCommandRequest.cs`: + +```cs +using System; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + [Serializable] + public class RuntimeCommandRequest + { + public string requestId; + public string command; + public string data; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeCommandRequest.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 1ecabd96737594000b4fc9f7aeb7b2c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeCommandResponse.cs`: + +```cs +using System; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + [Serializable] + public class RuntimeCommandResponse + { + public string requestId; + public bool success; + public string message; + public string data; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeCommandResponse.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 0910182d1eb3d46d68dadf944956e718 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeMessageGuids.cs`: + +```cs +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + public static class RuntimeMessageGuids + { + public static readonly System.Guid CommandRequest = + new System.Guid("a1b2c3d4-e5f6-7890-abcd-ef0123456789"); + + public static readonly System.Guid CommandResponse = + new System.Guid("a1b2c3d4-e5f6-7890-abcd-ef0123456790"); + + public static readonly System.Guid ListRequest = + new System.Guid("a1b2c3d4-e5f6-7890-abcd-ef0123456791"); + + public static readonly System.Guid ListResponse = + new System.Guid("a1b2c3d4-e5f6-7890-abcd-ef0123456792"); + + public static readonly System.Guid ChunkedResponse = + new System.Guid("a1b2c3d4-e5f6-7890-abcd-ef0123456793"); + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Protocol/RuntimeMessageGuids.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: ac8b87cd04c5a437ebe633a10dbe74b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/RuntimeDebugReceiver.cs`: + +```cs +using System; +using System.Text; +using UnityEngine; +using UnityEngine.Networking.PlayerConnection; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + public sealed class RuntimeDebugReceiver : MonoBehaviour + { + private const int ChunkSize = 16 * 1024; + + private static RuntimeDebugReceiver _instance; + + private DebugCommandRegistry _registry; + private RuntimeLogCapture _logCapture; + + public static RuntimeLogCapture RuntimeLogCapture => _instance != null ? _instance._logCapture : null; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + private static void AutoInitialize() + { + if (_instance != null) + return; + + var go = new GameObject("[UniCli.Remote]"); + DontDestroyOnLoad(go); + _instance = go.AddComponent(); + } + + private void Awake() + { + if (_instance != null && _instance != this) + { + Destroy(gameObject); + return; + } + + _instance = this; + + _logCapture = new RuntimeLogCapture(); + _logCapture.Start(); + + _registry = new DebugCommandRegistry(); + _registry.DiscoverCommands(); + + PlayerConnection.instance.Register(RuntimeMessageGuids.CommandRequest, OnCommandRequest); + PlayerConnection.instance.Register(RuntimeMessageGuids.ListRequest, OnListRequest); + + } + + private void OnDestroy() + { + if (_instance == this) + { + PlayerConnection.instance.Unregister(RuntimeMessageGuids.CommandRequest, OnCommandRequest); + PlayerConnection.instance.Unregister(RuntimeMessageGuids.ListRequest, OnListRequest); + _logCapture?.Stop(); + _instance = null; + } + } + + private void OnCommandRequest(MessageEventArgs args) + { + var json = Encoding.UTF8.GetString(args.data); + var request = JsonUtility.FromJson(json); + + var response = new RuntimeCommandResponse + { + requestId = request.requestId + }; + + try + { + if (!_registry.TryGetCommand(request.command, out var command)) + { + response.success = false; + response.message = $"Unknown debug command: {request.command}"; + response.data = ""; + } + else + { + var resultJson = command.Execute(request.data); + response.success = true; + response.message = $"Command '{request.command}' succeeded"; + response.data = resultJson; + } + } + catch (Exception ex) + { + response.success = false; + response.message = $"Command '{request.command}' failed: {ex.Message}"; + response.data = ""; + } + + var responseJson = JsonUtility.ToJson(response); + SendChunked(request.requestId, responseJson); + } + + private void OnListRequest(MessageEventArgs args) + { + var json = Encoding.UTF8.GetString(args.data); + var request = JsonUtility.FromJson(json); + + var response = new RuntimeListResponse + { + requestId = request.requestId, + commands = _registry.GetCommandInfos() + }; + + var responseJson = JsonUtility.ToJson(response); + SendChunked(request.requestId, responseJson); + } + + private void SendChunked(string requestId, string responseJson) + { + var totalChunks = (responseJson.Length + ChunkSize - 1) / ChunkSize; + if (totalChunks == 0) + totalChunks = 1; + + for (var i = 0; i < totalChunks; i++) + { + var start = i * ChunkSize; + var length = Math.Min(ChunkSize, responseJson.Length - start); + + var chunk = new RuntimeChunkedMessage + { + requestId = requestId, + chunkIndex = i, + totalChunks = totalChunks, + data = responseJson.Substring(start, length) + }; + + var chunkJson = JsonUtility.ToJson(chunk); + var chunkBytes = Encoding.UTF8.GetBytes(chunkJson); + PlayerConnection.instance.Send(RuntimeMessageGuids.ChunkedResponse, chunkBytes); + } + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/RuntimeDebugReceiver.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3f742703e3fdd47c3bed0860955a0fc9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/RuntimeLogCapture.cs`: + +```cs +using System; +using UnityEngine; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + public sealed class RuntimeLogCapture + { + private const int DefaultCapacity = 256; + + private readonly LogEntry[] _buffer; + private int _head; + private int _count; + + public RuntimeLogCapture(int capacity = DefaultCapacity) + { + _buffer = new LogEntry[capacity]; + } + + public void Start() + { + Application.logMessageReceived += OnLogMessage; + } + + public void Stop() + { + Application.logMessageReceived -= OnLogMessage; + } + + public LogEntry[] GetEntries(int limit, LogType? typeFilter) + { + var resultCount = Mathf.Min(limit > 0 ? limit : _count, _count); + var entries = new LogEntry[resultCount]; + + if (typeFilter.HasValue) + { + var filtered = new System.Collections.Generic.List(resultCount); + var startIndex = (_head - _count + _buffer.Length) % _buffer.Length; + for (var i = 0; i < _count && filtered.Count < resultCount; i++) + { + var entry = _buffer[(startIndex + i) % _buffer.Length]; + if (entry.type == typeFilter.Value.ToString()) + filtered.Add(entry); + } + return filtered.ToArray(); + } + + var offset = _count - resultCount; + var start = (_head - _count + _buffer.Length) % _buffer.Length; + for (var i = 0; i < resultCount; i++) + entries[i] = _buffer[(start + offset + i) % _buffer.Length]; + + return entries; + } + + private void OnLogMessage(string condition, string stackTrace, LogType type) + { + _buffer[_head] = new LogEntry + { + message = condition, + stackTrace = stackTrace, + type = type.ToString(), + timestamp = Time.realtimeSinceStartup + }; + _head = (_head + 1) % _buffer.Length; + if (_count < _buffer.Length) + _count++; + } + + [Serializable] + public struct LogEntry + { + public string message; + public string stackTrace; + public string type; + public float timestamp; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/RuntimeLogCapture.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: d63bf083011ae44c1a3dca850fb87d29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/UniCli.Remote.asmdef`: + +```asmdef +{ + "name": "UniCli.Remote", + "rootNamespace": "UniCli.Remote", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [ + "UNICLI_REMOTE || UNITY_EDITOR", + "DEVELOPMENT_BUILD || UNITY_EDITOR" + ], + "versionDefines": [], + "noEngineReferences": false +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/UniCli.Remote.asmdef.meta`: + +```meta +fileFormatVersion: 2 +guid: 887c0489b60934c08834664f94ea85b2 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Unit.cs`: + +```cs +using System; +using UnityEngine.Scripting; + +namespace UniCli.Remote +{ + [Preserve] + [Serializable] + public struct Unit + { + public static readonly Unit Value = default; + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Runtime/Remote/Unit.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 2778d7c9acfd54a24a2efe070cd819d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests.meta`: + +```meta +fileFormatVersion: 2 +guid: f2e1e4b78e8cb4365ac0ffbe627db966 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor.meta`: + +```meta +fileFormatVersion: 2 +guid: 9b383127109d940e885b88bc718167b3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/CommandDispatcherResponseTests.cs`: + +```cs +using System; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using UniCli.Protocol; +using UniCli.Server.Editor.Handlers; +using UnityEditor; + +namespace UniCli.Server.Editor.Tests +{ + [TestFixture] + public class CommandDispatcherResponseTests + { + [Serializable] + private class TestResponse + { + public string value; + } + + private class StubHandler : ICommandHandler + { + public string CommandName => "Test.Stub"; + public string Description => "Stub handler for tests"; + public CommandInfo GetCommandInfo() => new() { name = CommandName, description = Description }; + public ValueTask ExecuteAsync(object request, CancellationToken cancellationToken) => default; + } + + private class StubFormatterHandler : ICommandHandler, IResponseFormatter + { + public string CommandName => "Test.Formatter"; + public string Description => "Stub formatter handler"; + public CommandInfo GetCommandInfo() => new() { name = CommandName, description = Description }; + public ValueTask ExecuteAsync(object request, CancellationToken cancellationToken) => default; + + public bool TryWriteFormatted(object response, bool success, IFormatWriter writer) + { + writer.WriteLine($"formatted:{((TestResponse)response).value}"); + return true; + } + } + + private static ServiceRegistry CreateServiceRegistry() + { + var services = new ServiceRegistry(); + var installerTypes = TypeCache.GetTypesDerivedFrom(); + foreach (var type in installerTypes) + { + if (type.IsAbstract || type.IsInterface) + continue; + + var installer = (IServiceInstaller)Activator.CreateInstance(type); + installer.Install(services); + } + + return services; + } + + [Test] + public void MakeResponse_Success_SetsFieldsCorrectly() + { + var response = CommandDispatcher.MakeResponse(true, "ok", "{}", "json"); + Assert.IsTrue(response.success); + Assert.AreEqual("ok", response.message); + Assert.AreEqual("{}", response.data); + Assert.AreEqual("json", response.format); + } + + [Test] + public void MakeResponse_Error_SetsFieldsCorrectly() + { + var response = CommandDispatcher.MakeResponse(false, "fail"); + Assert.IsFalse(response.success); + Assert.AreEqual("fail", response.message); + Assert.AreEqual("", response.data); + Assert.AreEqual("json", response.format); + } + + [Test] + public void BuildResponse_UnitData_ReturnsEmptyData() + { + var dispatcher = new CommandDispatcher(CreateServiceRegistry()); + var handler = new StubHandler(); + var response = dispatcher.BuildResponse(true, "ok", Unit.Value, handler, false); + Assert.IsTrue(response.success); + Assert.AreEqual("", response.data); + } + + [Test] + public void BuildResponse_NullData_ReturnsEmptyData() + { + var dispatcher = new CommandDispatcher(CreateServiceRegistry()); + var handler = new StubHandler(); + var response = dispatcher.BuildResponse(true, "ok", null, handler, false); + Assert.IsTrue(response.success); + Assert.AreEqual("", response.data); + } + + [Test] + public void BuildResponse_JsonMode_ReturnsJsonData() + { + var dispatcher = new CommandDispatcher(CreateServiceRegistry()); + var handler = new StubHandler(); + var data = new TestResponse { value = "hello" }; + var response = dispatcher.BuildResponse(true, "ok", data, handler, false); + Assert.AreEqual("json", response.format); + StringAssert.Contains("hello", response.data); + } + + [Test] + public void BuildResponse_TextMode_WithFormatter_ReturnsTextData() + { + var dispatcher = new CommandDispatcher(CreateServiceRegistry()); + var handler = new StubFormatterHandler(); + var data = new TestResponse { value = "world" }; + var response = dispatcher.BuildResponse(true, "ok", data, handler, true); + Assert.AreEqual("text", response.format); + StringAssert.Contains("formatted:world", response.data); + } + + [Test] + public void BuildResponse_TextMode_WithoutFormatter_FallsBackToJson() + { + var dispatcher = new CommandDispatcher(CreateServiceRegistry()); + var handler = new StubHandler(); + var data = new TestResponse { value = "fallback" }; + var response = dispatcher.BuildResponse(true, "ok", data, handler, true); + Assert.AreEqual("json", response.format); + StringAssert.Contains("fallback", response.data); + } + + [Test] + public void BuildResponse_CommandListResponse_WithFlatTypeDetails_UsesDefaultJsonSerialization() + { + var dispatcher = new CommandDispatcher(CreateServiceRegistry()); + var handler = new StubHandler(); + var data = new CommandListResponse + { + commands = new[] + { + new CommandInfo + { + name = "Test.Command", + description = "Command metadata", + builtIn = true, + module = "", + requestFields = Array.Empty(), + responseFields = new[] + { + new CommandFieldInfo + { + name = "root", + type = "Level0", + typeId = "Tests:Level0", + defaultValue = "" + } + }, + requestTypeDetails = Array.Empty(), + responseTypeDetails = CreateTypeDetailChain(12) + } + } + }; + + var response = dispatcher.BuildResponse(true, "ok", data, handler, false); + + Assert.AreEqual("json", response.format); + + for (var i = 0; i < 12; i++) + { + StringAssert.Contains($"\"typeName\":\"Level{i}\"", response.data); + } + + StringAssert.Contains("\"responseTypeDetails\":", response.data); + StringAssert.Contains("\"name\":\"root\",\"type\":\"Level0\",\"typeId\":\"Tests:Level0\",\"defaultValue\":\"\"", response.data); + Assert.AreEqual(12, CountOccurrences(response.data, "\"typeName\":")); + Assert.AreEqual(25, CountOccurrences(response.data, "\"typeId\":")); + Assert.IsFalse(response.data.Contains("\"children\":", StringComparison.Ordinal)); + } + + [Test] + public void BuildResponse_CommandListResponse_WithCollidingTypeNames_PreservesDistinctTypeIds() + { + var dispatcher = new CommandDispatcher(CreateServiceRegistry()); + var handler = new StubHandler(); + var data = new CommandListResponse + { + commands = new[] + { + new CommandInfo + { + name = "Test.Collision", + description = "Collision metadata", + builtIn = true, + module = "", + requestFields = new[] + { + new CommandFieldInfo + { + name = "alpha", + type = "Duplicate", + typeId = "Tests:Alpha.Duplicate", + defaultValue = "" + }, + new CommandFieldInfo + { + name = "beta", + type = "Duplicate", + typeId = "Tests:Beta.Duplicate", + defaultValue = "" + } + }, + responseFields = Array.Empty(), + requestTypeDetails = new[] + { + new CommandTypeDetail + { + typeName = "Duplicate", + typeId = "Tests:Alpha.Duplicate", + fields = new[] + { + new CommandFieldInfo + { + name = "profile", + type = "string", + typeId = "", + defaultValue = "" + } + } + }, + new CommandTypeDetail + { + typeName = "Duplicate", + typeId = "Tests:Beta.Duplicate", + fields = new[] + { + new CommandFieldInfo + { + name = "retries", + type = "int", + typeId = "", + defaultValue = "" + } + } + } + }, + responseTypeDetails = Array.Empty() + } + } + }; + + var response = dispatcher.BuildResponse(true, "ok", data, handler, false); + + StringAssert.Contains("\"typeName\":\"Duplicate\",\"typeId\":\"Tests:Alpha.Duplicate\"", response.data); + StringAssert.Contains("\"typeName\":\"Duplicate\",\"typeId\":\"Tests:Beta.Duplicate\"", response.data); + StringAssert.Contains("\"name\":\"alpha\",\"type\":\"Duplicate\",\"typeId\":\"Tests:Alpha.Duplicate\"", response.data); + StringAssert.Contains("\"name\":\"beta\",\"type\":\"Duplicate\",\"typeId\":\"Tests:Beta.Duplicate\"", response.data); + } + + private static CommandTypeDetail[] CreateTypeDetailChain(int depth) + { + var result = new CommandTypeDetail[depth]; + + for (var i = 0; i < depth; i++) + { + result[i] = new CommandTypeDetail + { + typeName = $"Level{i}", + typeId = $"Tests:Level{i}", + fields = i == depth - 1 + ? new[] + { + new CommandFieldInfo + { + name = "value", + type = "string", + typeId = "", + defaultValue = "" + } + } + : new[] + { + new CommandFieldInfo + { + name = $"level{i + 1}", + type = $"Level{i + 1}", + typeId = $"Tests:Level{i + 1}", + defaultValue = "" + } + } + }; + } + + return result; + } + + private static int CountOccurrences(string text, string value) + { + var count = 0; + var startIndex = 0; + + while ((startIndex = text.IndexOf(value, startIndex, StringComparison.Ordinal)) >= 0) + { + count++; + startIndex += value.Length; + } + + return count; + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/CommandDispatcherResponseTests.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 8d78fb72a076740c592cd1de7c4675b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/CommandFieldInfoExtractorTests.cs`: + +```cs +using System; +using NUnit.Framework; +using UniCli.Protocol; +using UniCli.Server.Editor.Internal; + +namespace UniCli.Server.Editor.Tests +{ + [TestFixture] + public class CommandFieldInfoExtractorTests + { + [Serializable] + private class MultiFieldRequest + { + public string name; + public int count; + public bool enabled; + } + + [Serializable] + private class DefaultValuesRequest + { + public int count = 42; + public bool verbose = true; + public string prefix = "test"; + } + + [Serializable] + private class ArrayFieldRequest + { + public string[] tags; + public int[] ids; + } + + [Serializable] + private class FloatDoubleRequest + { + public float speed; + public double precision; + } + + [Serializable] + private class NestedConfig + { + public string profile; + public int retries; + } + + [Serializable] + private class NestedTypeRequest + { + public NestedConfig config; + public NestedConfig[] configs; + } + + [Serializable] + private class RecursiveRequest + { + public RecursiveRequest child; + } + + [Serializable] + private class MutualRecursiveA + { + public MutualRecursiveB child; + } + + [Serializable] + private class MutualRecursiveB + { + public MutualRecursiveA parent; + } + + [Serializable] + private class AlphaContainer + { + [Serializable] + public class Duplicate + { + public string profile; + } + } + + [Serializable] + private class BetaContainer + { + [Serializable] + public class Duplicate + { + public int retries; + } + } + + [Serializable] + private class CollidingTypeRequest + { + public AlphaContainer.Duplicate alpha; + public BetaContainer.Duplicate beta; + public AlphaContainer.Duplicate[] alphaArray; + } + + [Test] + public void Extract_UnitType_ReturnsEmptyMetadata() + { + var result = CommandFieldInfoExtractor.Extract(typeof(Unit)); + + Assert.AreEqual(0, result.Fields.Length); + Assert.AreEqual(0, result.TypeDetails.Length); + } + + [Test] + public void Extract_MultipleFields_ExtractsCorrectly() + { + var result = CommandFieldInfoExtractor.Extract(typeof(MultiFieldRequest)); + + Assert.AreEqual(3, result.Fields.Length); + Assert.AreEqual(0, result.TypeDetails.Length); + + Assert.AreEqual("name", result.Fields[0].name); + Assert.AreEqual("string", result.Fields[0].type); + Assert.AreEqual("", result.Fields[0].typeId); + + Assert.AreEqual("count", result.Fields[1].name); + Assert.AreEqual("int", result.Fields[1].type); + Assert.AreEqual("", result.Fields[1].typeId); + + Assert.AreEqual("enabled", result.Fields[2].name); + Assert.AreEqual("bool", result.Fields[2].type); + Assert.AreEqual("", result.Fields[2].typeId); + } + + [Test] + public void ToSimpleTypeName_Primitives_ReturnsExpectedNames() + { + Assert.AreEqual("string", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(string))); + Assert.AreEqual("int", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(int))); + Assert.AreEqual("bool", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(bool))); + Assert.AreEqual("float", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(float))); + Assert.AreEqual("double", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(double))); + } + + [Test] + public void ToSimpleTypeName_Arrays_ReturnsExpectedNames() + { + Assert.AreEqual("string[]", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(string[]))); + Assert.AreEqual("int[]", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(int[]))); + Assert.AreEqual("MultiFieldRequest[]", CommandFieldInfoExtractor.ToSimpleTypeName(typeof(MultiFieldRequest[]))); + } + + [Test] + public void Extract_DefaultValues_ExtractsNonDefaultValues() + { + var result = CommandFieldInfoExtractor.Extract(typeof(DefaultValuesRequest)); + + Assert.AreEqual(3, result.Fields.Length); + + Assert.AreEqual("count", result.Fields[0].name); + Assert.AreEqual("42", result.Fields[0].defaultValue); + + Assert.AreEqual("verbose", result.Fields[1].name); + Assert.AreEqual("true", result.Fields[1].defaultValue); + + Assert.AreEqual("prefix", result.Fields[2].name); + Assert.AreEqual("test", result.Fields[2].defaultValue); + } + + [Test] + public void Extract_DefaultZeroValues_ReturnsEmptyString() + { + var result = CommandFieldInfoExtractor.Extract(typeof(MultiFieldRequest)); + + Assert.AreEqual("", result.Fields[0].defaultValue); // string null -> "" + Assert.AreEqual("", result.Fields[1].defaultValue); // int 0 -> "" + Assert.AreEqual("", result.Fields[2].defaultValue); // bool false -> "" + } + + [Test] + public void Extract_FloatDoubleFields_ExtractsTypes() + { + var result = CommandFieldInfoExtractor.Extract(typeof(FloatDoubleRequest)); + + Assert.AreEqual(2, result.Fields.Length); + Assert.AreEqual("float", result.Fields[0].type); + Assert.AreEqual("double", result.Fields[1].type); + } + + [Test] + public void Extract_ArrayFields_ExtractsTypes() + { + var result = CommandFieldInfoExtractor.Extract(typeof(ArrayFieldRequest)); + + Assert.AreEqual(2, result.Fields.Length); + Assert.AreEqual("string[]", result.Fields[0].type); + Assert.AreEqual("int[]", result.Fields[1].type); + Assert.AreEqual(0, result.TypeDetails.Length); + } + + [Test] + public void Extract_NestedTypes_ExtractsTypeDetails() + { + var result = CommandFieldInfoExtractor.Extract(typeof(NestedTypeRequest)); + + Assert.AreEqual(2, result.Fields.Length); + + Assert.AreEqual("config", result.Fields[0].name); + Assert.AreEqual("NestedConfig", result.Fields[0].type); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(NestedConfig)), + result.Fields[0].typeId); + + Assert.AreEqual("configs", result.Fields[1].name); + Assert.AreEqual("NestedConfig[]", result.Fields[1].type); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(NestedConfig)), + result.Fields[1].typeId); + + Assert.AreEqual(1, result.TypeDetails.Length); + Assert.AreEqual("NestedConfig", result.TypeDetails[0].typeName); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(NestedConfig)), + result.TypeDetails[0].typeId); + Assert.AreEqual(2, result.TypeDetails[0].fields.Length); + Assert.AreEqual("profile", result.TypeDetails[0].fields[0].name); + Assert.AreEqual("string", result.TypeDetails[0].fields[0].type); + Assert.AreEqual("", result.TypeDetails[0].fields[0].typeId); + Assert.AreEqual("retries", result.TypeDetails[0].fields[1].name); + Assert.AreEqual("int", result.TypeDetails[0].fields[1].type); + Assert.AreEqual("", result.TypeDetails[0].fields[1].typeId); + } + + [Test] + public void Extract_RecursiveType_CollectsFiniteTypeDetails() + { + var result = CommandFieldInfoExtractor.Extract(typeof(RecursiveRequest)); + + Assert.AreEqual(1, result.Fields.Length); + Assert.AreEqual("RecursiveRequest", result.Fields[0].type); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(RecursiveRequest)), + result.Fields[0].typeId); + Assert.AreEqual(1, result.TypeDetails.Length); + Assert.AreEqual("RecursiveRequest", result.TypeDetails[0].typeName); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(RecursiveRequest)), + result.TypeDetails[0].typeId); + Assert.AreEqual(1, result.TypeDetails[0].fields.Length); + Assert.AreEqual("child", result.TypeDetails[0].fields[0].name); + Assert.AreEqual("RecursiveRequest", result.TypeDetails[0].fields[0].type); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(RecursiveRequest)), + result.TypeDetails[0].fields[0].typeId); + } + + [Test] + public void Extract_MutualRecursiveTypes_CollectsEachTypeOnce() + { + var result = CommandFieldInfoExtractor.Extract(typeof(MutualRecursiveA)); + + Assert.AreEqual(1, result.Fields.Length); + Assert.AreEqual("MutualRecursiveB", result.Fields[0].type); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(MutualRecursiveB)), + result.Fields[0].typeId); + Assert.AreEqual(2, result.TypeDetails.Length); + + Assert.AreEqual("MutualRecursiveB", result.TypeDetails[0].typeName); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(MutualRecursiveB)), + result.TypeDetails[0].typeId); + Assert.AreEqual(1, result.TypeDetails[0].fields.Length); + Assert.AreEqual("MutualRecursiveA", result.TypeDetails[0].fields[0].type); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(MutualRecursiveA)), + result.TypeDetails[0].fields[0].typeId); + + Assert.AreEqual("MutualRecursiveA", result.TypeDetails[1].typeName); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(MutualRecursiveA)), + result.TypeDetails[1].typeId); + Assert.AreEqual(1, result.TypeDetails[1].fields.Length); + Assert.AreEqual("MutualRecursiveB", result.TypeDetails[1].fields[0].type); + Assert.AreEqual( + CommandFieldInfoExtractor.GetTypeId(typeof(MutualRecursiveB)), + result.TypeDetails[1].fields[0].typeId); + } + + [Test] + public void Extract_CollidingTypeNames_UsesDistinctTypeIds() + { + var result = CommandFieldInfoExtractor.Extract(typeof(CollidingTypeRequest)); + var alphaTypeId = CommandFieldInfoExtractor.GetTypeId(typeof(AlphaContainer.Duplicate)); + var betaTypeId = CommandFieldInfoExtractor.GetTypeId(typeof(BetaContainer.Duplicate)); + + Assert.AreEqual(3, result.Fields.Length); + + Assert.AreEqual("Duplicate", result.Fields[0].type); + Assert.AreEqual(alphaTypeId, result.Fields[0].typeId); + + Assert.AreEqual("Duplicate", result.Fields[1].type); + Assert.AreEqual(betaTypeId, result.Fields[1].typeId); + + Assert.AreEqual("Duplicate[]", result.Fields[2].type); + Assert.AreEqual(alphaTypeId, result.Fields[2].typeId); + + Assert.AreEqual(2, result.TypeDetails.Length); + Assert.AreEqual("Duplicate", result.TypeDetails[0].typeName); + Assert.AreEqual(alphaTypeId, result.TypeDetails[0].typeId); + Assert.AreEqual("Duplicate", result.TypeDetails[1].typeName); + Assert.AreEqual(betaTypeId, result.TypeDetails[1].typeId); + Assert.AreNotEqual(result.TypeDetails[0].typeId, result.TypeDetails[1].typeId); + + Assert.AreEqual("profile", result.TypeDetails[0].fields[0].name); + Assert.AreEqual("", result.TypeDetails[0].fields[0].typeId); + Assert.AreEqual("retries", result.TypeDetails[1].fields[0].name); + Assert.AreEqual("", result.TypeDetails[1].fields[0].typeId); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/CommandFieldInfoExtractorTests.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: b3df7545d9cc343c495033f4bc86d5d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/ProtocolConstantsTests.cs`: + +```cs +using System; +using NUnit.Framework; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Tests +{ + [TestFixture] + public class ProtocolConstantsTests + { + [Test] + public void ValidateMagicBytes_CorrectBytes_ReturnsTrue() + { + var buffer = new byte[] { 0x55, 0x43, 0x4C, 0x49, 0x00, 0x00 }; + Assert.IsTrue(ProtocolConstants.ValidateMagicBytes(buffer)); + } + + [Test] + public void ValidateMagicBytes_WrongBytes_ReturnsFalse() + { + var buffer = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + Assert.IsFalse(ProtocolConstants.ValidateMagicBytes(buffer)); + } + + [Test] + public void ValidateMagicBytes_TooShort_ReturnsFalse() + { + var buffer = new byte[] { 0x55, 0x43 }; + Assert.IsFalse(ProtocolConstants.ValidateMagicBytes(buffer)); + } + + [Test] + public void ValidateMagicBytes_EmptyBuffer_ReturnsFalse() + { + Assert.IsFalse(ProtocolConstants.ValidateMagicBytes(Array.Empty())); + } + + [Test] + public void ValidateMagicBytes_ExactFourBytes_ReturnsTrue() + { + var buffer = new byte[] { 0x55, 0x43, 0x4C, 0x49 }; + Assert.IsTrue(ProtocolConstants.ValidateMagicBytes(buffer)); + } + + [Test] + public void BuildHandshakeBuffer_ReturnsCorrectLength() + { + var buffer = ProtocolConstants.BuildHandshakeBuffer(); + Assert.AreEqual(ProtocolConstants.HandshakeSize, buffer.Length); + } + + [Test] + public void BuildHandshakeBuffer_StartsWithMagicBytes() + { + var buffer = ProtocolConstants.BuildHandshakeBuffer(); + Assert.IsTrue(ProtocolConstants.ValidateMagicBytes(buffer)); + } + + [Test] + public void BuildHandshakeBuffer_ContainsProtocolVersion() + { + var buffer = ProtocolConstants.BuildHandshakeBuffer(); + var version = BitConverter.ToUInt16(buffer, 4); + Assert.AreEqual(ProtocolConstants.ProtocolVersion, version); + } + + [Test] + public void HandshakeSize_IsSix() + { + Assert.AreEqual(6, ProtocolConstants.HandshakeSize); + } + + [Test] + public void MaxMessageSize_IsOneMB() + { + Assert.AreEqual(1024 * 1024, ProtocolConstants.MaxMessageSize); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/ProtocolConstantsTests.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: bc41db4d93ea54e1197187792a52c039 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/ResultTests.cs`: + +```cs +using System; +using NUnit.Framework; +using UniCli.Protocol; + +namespace UniCli.Server.Editor.Tests +{ + [TestFixture] + public class ResultTests + { + [Test] + public void Success_IsSuccess_ReturnsTrue() + { + var result = Result.Success(42); + Assert.IsTrue(result.IsSuccess); + Assert.IsFalse(result.IsError); + } + + [Test] + public void Success_SuccessValue_ReturnsValue() + { + var result = Result.Success(42); + Assert.AreEqual(42, result.SuccessValue); + } + + [Test] + public void Success_ErrorValue_ThrowsInvalidOperation() + { + var result = Result.Success(42); + Assert.Throws(() => { var _ = result.ErrorValue; }); + } + + [Test] + public void Error_IsError_ReturnsTrue() + { + var result = Result.Error("fail"); + Assert.IsTrue(result.IsError); + Assert.IsFalse(result.IsSuccess); + } + + [Test] + public void Error_ErrorValue_ReturnsValue() + { + var result = Result.Error("fail"); + Assert.AreEqual("fail", result.ErrorValue); + } + + [Test] + public void Error_SuccessValue_ThrowsInvalidOperation() + { + var result = Result.Error("fail"); + Assert.Throws(() => { var _ = result.SuccessValue; }); + } + + [Test] + public void MatchFunc_Success_CallsOnSuccess() + { + var result = Result.Success(10); + var output = result.Match( + v => v * 2, + e => -1); + Assert.AreEqual(20, output); + } + + [Test] + public void MatchFunc_Error_CallsOnError() + { + var result = Result.Error("bad"); + var output = result.Match( + v => "ok", + e => e.ToUpper()); + Assert.AreEqual("BAD", output); + } + + [Test] + public void MatchFunc_NullOnSuccess_ThrowsArgumentNull() + { + var result = Result.Success(1); + Assert.Throws(() => + result.Match(null, e => 0)); + } + + [Test] + public void MatchFunc_NullOnError_ThrowsArgumentNull() + { + var result = Result.Success(1); + Assert.Throws(() => + result.Match(v => v, null)); + } + + [Test] + public void MatchAction_Success_CallsOnSuccess() + { + var result = Result.Success(5); + var called = ""; + result.Match( + v => called = $"success:{v}", + e => called = $"error:{e}"); + Assert.AreEqual("success:5", called); + } + + [Test] + public void MatchAction_Error_CallsOnError() + { + var result = Result.Error("oops"); + var called = ""; + result.Match( + v => called = $"success:{v}", + e => called = $"error:{e}"); + Assert.AreEqual("error:oops", called); + } + + [Test] + public void MatchAction_NullOnSuccess_ThrowsArgumentNull() + { + var result = Result.Success(1); + Assert.Throws(() => + result.Match(null, e => { })); + } + + [Test] + public void MatchAction_NullOnError_ThrowsArgumentNull() + { + var result = Result.Success(1); + Assert.Throws(() => + result.Match(v => { }, null)); + } + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/ResultTests.cs.meta`: + +```meta +fileFormatVersion: 2 +guid: 3ac56943773b944f18bed8bc87e92f8d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/UniCli.Server.Editor.Tests.asmdef`: + +```asmdef +{ + "name": "UniCli.Server.Editor.Tests", + "rootNamespace": "UniCli.Server.Editor.Tests", + "references": [ + "UniCli.Server.Editor" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/Tests/Editor/UniCli.Server.Editor.Tests.asmdef.meta`: + +```meta +fileFormatVersion: 2 +guid: 3aacd41ac00da4773bafe6e8dff6cabc +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/package.json`: + +```json +{ + "name": "com.yucchiy.unicli-server", + "version": "1.2.0", + "displayName": "UniCli Server", + "description": "Unity Editor plugin - Server for controlling Unity via CLI", + "unity": "2022.3", + "author": { + "name": "Yuichiro Mukai", + "url": "https://github.com/yucchiy" + } +} + +``` + +`src/UniCli.Unity/Packages/com.yucchiy.unicli-server/package.json.meta`: + +```meta +fileFormatVersion: 2 +guid: 31dba9f54635d45f489768b087ef1925 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + +``` + +`src/UniCli.Unity/Packages/manifest.json`: + +```json +{ + "dependencies": { + "com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", + "com.unity.collab-proxy": "2.11.3", + "com.unity.feature.development": "1.0.1", + "com.unity.memoryprofiler": "1.1.1", + "com.unity.recorder": "4.0.3", + "com.unity.textmeshpro": "3.0.7", + "com.unity.timeline": "1.7.7", + "com.unity.ugui": "1.0.0", + "com.unity.visualscripting": "1.9.4", + "com.yucchiy.unicli-server": "file:../com.yucchiy.unicli-server", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} + +``` + +`src/UniCli.Unity/Packages/packages-lock.json`: + +```json +{ + "dependencies": { + "com.github-glitchenzo.nugetforunity": { + "version": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", + "depth": 0, + "source": "git", + "dependencies": {}, + "hash": "c2af83c9d4f8cdaada9d4a0e94de2f195d8e1d01" + }, + "com.unity.collab-proxy": { + "version": "2.11.3", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.editorcoroutines": { + "version": "1.0.0", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "1.0.6", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.feature.development": { + "version": "1.0.1", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.ide.visualstudio": "2.0.22", + "com.unity.ide.rider": "3.0.36", + "com.unity.ide.vscode": "1.2.5", + "com.unity.editorcoroutines": "1.0.0", + "com.unity.performance.profile-analyzer": "1.2.3", + "com.unity.test-framework": "1.1.33", + "com.unity.testtools.codecoverage": "1.2.6" + } + }, + "com.unity.ide.rider": { + "version": "3.0.36", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.22", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.memoryprofiler": { + "version": "1.1.1", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.editorcoroutines": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.performance.profile-analyzer": { + "version": "1.2.3", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.recorder": { + "version": "4.0.3", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.timeline": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.settings-manager": { + "version": "2.1.0", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.test-framework": { + "version": "1.1.33", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.testtools.codecoverage": { + "version": "1.2.6", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.0.16", + "com.unity.settings-manager": "1.0.1" + }, + "url": "https://packages.unity.com" + }, + "com.unity.textmeshpro": { + "version": "3.0.7", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.7.7", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.visualscripting": { + "version": "1.9.4", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.yucchiy.unicli-server": { + "version": "file:com.yucchiy.unicli-server", + "depth": 0, + "source": "embedded", + "dependencies": {} + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} + +``` + +`src/UniCli.Unity/ProjectSettings/AudioManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 1024 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 + m_RequestedDSPBufferSize: 1024 + +``` + +`src/UniCli.Unity/ProjectSettings/ClusterInputManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] + +``` + +`src/UniCli.Unity/ProjectSettings/DynamicsManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_ClothInterCollisionDistance: 0 + m_ClothInterCollisionStiffness: 0 + m_ContactsGeneration: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 + m_AutoSyncTransforms: 0 + m_ReuseCollisionCallbacks: 1 + m_ClothInterCollisionSettingsToggle: 0 + m_ContactPairsMode: 0 + m_BroadphaseType: 0 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 250, y: 250, z: 250} + m_WorldSubdivisions: 8 + m_FrictionType: 0 + m_EnableEnhancedDeterminism: 0 + m_EnableUnifiedHeightmaps: 1 + m_DefaultMaxAngluarSpeed: 7 + +``` + +`src/UniCli.Unity/ProjectSettings/EditorBuildSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: [] + m_configObjects: {} + +``` + +`src/UniCli.Unity/ProjectSettings/EditorSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 0 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerCacheSize: 10 + m_SpritePackerPaddingPower: 1 + m_Bc7TextureCompressor: 0 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref + m_ProjectGenerationRootNamespace: + m_EnableTextureStreamingInEditMode: 1 + m_EnableTextureStreamingInPlayMode: 1 + m_EnableEditorAsyncCPUTextureLoading: 0 + m_AsyncShaderCompilation: 1 + m_PrefabModeAllowAutoSave: 1 + m_EnterPlayModeOptionsEnabled: 1 + m_EnterPlayModeOptions: 3 + m_GameObjectNamingDigits: 1 + m_GameObjectNamingScheme: 0 + m_AssetNamingUsesSpace: 1 + m_InspectorUseIMGUIDefaultInspector: 0 + m_UseLegacyProbeSampleCount: 0 + m_SerializeInlineMappingsOnOneLine: 1 + m_DisableCookiesInLightmapper: 0 + m_AssetPipelineMode: 1 + m_RefreshImportMode: 0 + m_CacheServerMode: 0 + m_CacheServerEndpoint: + m_CacheServerNamespacePrefix: default + m_CacheServerEnableDownload: 1 + m_CacheServerEnableUpload: 1 + m_CacheServerEnableAuth: 0 + m_CacheServerEnableTls: 0 + m_CacheServerValidationMode: 2 + m_CacheServerDownloadBatchSize: 128 + m_EnableEnlightenBakedGI: 0 + +``` + +`src/UniCli.Unity/ProjectSettings/GraphicsSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_LegacyDeferred: + m_Mode: 1 + m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, + type: 0} + m_CustomRenderPipeline: {fileID: 0} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_LightsUseLinearIntensity: 0 + m_LightsUseColorTemperature: 0 + m_LogWhenShaderIsCompiled: 0 + m_AllowEnlightenSupportForUpgradedProject: 0 + +``` + +`src/UniCli.Unity/ProjectSettings/InputManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + +``` + +`src/UniCli.Unity/ProjectSettings/MemorySettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} + +``` + +`src/UniCli.Unity/ProjectSettings/NavMeshAreas.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_SettingNames: + - Humanoid + +``` + +`src/UniCli.Unity/ProjectSettings/PackageManagerSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 61 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} + m_Name: + m_EditorClassIdentifier: + m_EnablePreReleasePackages: 0 + m_EnablePackageDependencies: 0 + m_AdvancedSettingsExpanded: 1 + m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + oneTimeWarningShown: 0 + m_Registries: + - m_Id: main + m_Name: + m_Url: https://packages.unity.com + m_Scopes: [] + m_IsDefault: 1 + m_Capabilities: 7 + m_UserSelectedRegistryName: + m_UserAddingNewScopedRegistry: 0 + m_RegistryInfoDraft: + m_Modified: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -830 + m_OriginalInstanceId: -832 + m_LoadAssets: 0 + +``` + +`src/UniCli.Unity/ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json`: + +```json +{ + "m_Dictionary": { + "m_DictionaryValues": [] + } +} +``` + +`src/UniCli.Unity/ProjectSettings/Physics2DSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_JobOptions: + serializedVersion: 2 + useMultithreading: 0 + useConsistencySorting: 0 + m_InterpolationPosesPerJob: 100 + m_NewContactsPerJob: 30 + m_CollideContactsPerJob: 100 + m_ClearFlagsPerJob: 200 + m_ClearBodyForcesPerJob: 200 + m_SyncDiscreteFixturesPerJob: 50 + m_SyncContinuousFixturesPerJob: 50 + m_FindNearestContactsPerJob: 100 + m_UpdateTriggerContactsPerJob: 100 + m_IslandSolverCostThreshold: 100 + m_IslandSolverBodyCostScale: 1 + m_IslandSolverContactCostScale: 10 + m_IslandSolverJointCostScale: 10 + m_IslandSolverBodiesPerJob: 50 + m_IslandSolverContactsPerJob: 50 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_CallbacksOnDisable: 1 + m_ReuseCollisionCallbacks: 1 + m_AutoSyncTransforms: 0 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + +``` + +`src/UniCli.Unity/ProjectSettings/PresetManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1386491679 &1 +PresetManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_DefaultPresets: {} + +``` + +`src/UniCli.Unity/ProjectSettings/ProjectSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 26 + productGUID: 7123956cf2e4443d180f8115b6e01282 + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: UniCli.Unity + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1920 + defaultScreenHeight: 1080 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 1 + unsupportedMSAAFallback: 0 + m_SpriteBatchVertexThreshold: 300 + m_MTRendering: 1 + mipStripping: 0 + numberOfMipsStripped: 0 + numberOfMipsStrippedPerMipmapLimitGroup: {} + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + iosUseCustomAppBackgroundBehavior: 0 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 1 + androidBlitType: 0 + androidResizableWindow: 0 + androidDefaultWindowWidth: 1920 + androidDefaultWindowHeight: 1080 + androidMinimumWindowWidth: 400 + androidMinimumWindowHeight: 300 + androidFullscreenMode: 1 + androidAutoRotationBehavior: 1 + androidPredictiveBackSupport: 1 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 1 + captureSingleScreen: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + audioSpatialExperience: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + dedicatedServerOptimizations: 0 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 1 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOneEnableTypeOptimization: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 0 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + switchGpuScratchPoolGranularity: 2097152 + switchAllowGpuScratchShrinking: 0 + switchNVNMaxPublicTextureIDCount: 0 + switchNVNMaxPublicSamplerIDCount: 0 + switchNVNGraphicsFirmwareMemory: 32 + switchMaxWorkerMultiple: 8 + stadiaPresentMode: 0 + stadiaTargetFramerate: 0 + vulkanNumSwapchainBuffers: 3 + vulkanEnableSetSRGBWrite: 0 + vulkanEnablePreTransform: 1 + vulkanEnableLateAcquireNextImage: 0 + vulkanEnableCommandBufferRecycling: 1 + loadStoreDebugModeEnabled: 0 + visionOSBundleVersion: 1.0 + tvOSBundleVersion: 1.0 + bundleVersion: 0.1 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + enableFrameTimingStats: 0 + enableOpenGLProfilerGPURecorders: 1 + allowHDRDisplaySupport: 0 + useHDRDisplay: 0 + hdrBitDepth: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + resetResolutionOnWindowResize: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 + applicationIdentifier: + Standalone: com.DefaultCompany.UniCli.Unity + buildNumber: + Standalone: 0 + VisionOS: 0 + iPhone: 0 + tvOS: 0 + overrideDefaultApplicationIdentifier: 0 + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 22 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + AndroidPreferredDataLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + APKExpansionFiles: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 1 + strictShaderVariantMatching: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSSimulatorArchitecture: 0 + iOSTargetOSVersionString: 12.0 + tvOSSdkVersion: 0 + tvOSSimulatorArchitecture: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 12.0 + VisionOSSdkVersion: 0 + VisionOSTargetOSVersionString: 1.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreenCustomXibPath: + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreeniPadCustomXibPath: + iOSLaunchScreenCustomStoryboardPath: + iOSLaunchScreeniPadCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + macOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + metalCompileShaderBinary: 0 + iOSRenderExtraFrameOnPause: 0 + iosCopyPluginsCodeInsteadOfSymlink: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + VisionOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + VisionOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + shaderPrecisionModel: 0 + clonedFromGUID: c0afd0d1d80e3634a9dac47e8a0426ea + templatePackageId: com.unity.template.3d@8.1.3 + templateDefaultScene: Assets/Scenes/SampleScene.unity + useCustomMainManifest: 0 + useCustomLauncherManifest: 0 + useCustomMainGradleTemplate: 0 + useCustomLauncherGradleManifest: 0 + useCustomBaseGradleTemplate: 0 + useCustomGradlePropertiesTemplate: 0 + useCustomGradleSettingsTemplate: 0 + useCustomProguardFile: 0 + AndroidTargetArchitectures: 2 + AndroidTargetDevices: 0 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidEnableArmv9SecurityFeatures: 0 + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + chromeosInputEmulation: 1 + AndroidMinifyRelease: 0 + AndroidMinifyDebug: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: + m_BuildTargetBatching: + - m_BuildTarget: Standalone + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: tvOS + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: Android + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: iPhone + m_StaticBatching: 1 + m_DynamicBatching: 0 + - m_BuildTarget: WebGL + m_StaticBatching: 0 + m_DynamicBatching: 0 + m_BuildTargetShaderSettings: [] + m_BuildTargetGraphicsJobs: + - m_BuildTarget: MacStandaloneSupport + m_GraphicsJobs: 0 + - m_BuildTarget: Switch + m_GraphicsJobs: 1 + - m_BuildTarget: MetroSupport + m_GraphicsJobs: 1 + - m_BuildTarget: AppleTVSupport + m_GraphicsJobs: 0 + - m_BuildTarget: BJMSupport + m_GraphicsJobs: 1 + - m_BuildTarget: LinuxStandaloneSupport + m_GraphicsJobs: 1 + - m_BuildTarget: PS4Player + m_GraphicsJobs: 1 + - m_BuildTarget: iOSSupport + m_GraphicsJobs: 0 + - m_BuildTarget: WindowsStandaloneSupport + m_GraphicsJobs: 1 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobs: 1 + - m_BuildTarget: LuminSupport + m_GraphicsJobs: 0 + - m_BuildTarget: AndroidPlayer + m_GraphicsJobs: 0 + - m_BuildTarget: WebGLSupport + m_GraphicsJobs: 0 + m_BuildTargetGraphicsJobMode: + - m_BuildTarget: PS4Player + m_GraphicsJobMode: 0 + - m_BuildTarget: XboxOnePlayer + m_GraphicsJobMode: 0 + m_BuildTargetGraphicsAPIs: + - m_BuildTarget: AndroidPlayer + m_APIs: 150000000b000000 + m_Automatic: 1 + - m_BuildTarget: iOSSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: AppleTVSupport + m_APIs: 10000000 + m_Automatic: 1 + - m_BuildTarget: WebGLSupport + m_APIs: 0b000000 + m_Automatic: 1 + m_BuildTargetVRSettings: + - m_BuildTarget: Standalone + m_Enabled: 0 + m_Devices: + - Oculus + - OpenVR + m_DefaultShaderChunkSizeInMB: 16 + m_DefaultShaderChunkCount: 0 + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - m_BuildTarget: Android + m_EncodingQuality: 1 + - m_BuildTarget: iPhone + m_EncodingQuality: 1 + - m_BuildTarget: tvOS + m_EncodingQuality: 1 + m_BuildTargetGroupHDRCubemapEncodingQuality: + - m_BuildTarget: Android + m_EncodingQuality: 1 + - m_BuildTarget: iPhone + m_EncodingQuality: 1 + - m_BuildTarget: tvOS + m_EncodingQuality: 1 + m_BuildTargetGroupLightmapSettings: [] + m_BuildTargetGroupLoadStoreDebugModeSettings: [] + m_BuildTargetNormalMapEncoding: + - m_BuildTarget: Android + m_Encoding: 1 + - m_BuildTarget: iPhone + m_Encoding: 1 + - m_BuildTarget: tvOS + m_Encoding: 1 + m_BuildTargetDefaultTextureCompressionFormat: + - m_BuildTarget: Android + m_Format: 3 + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + bluetoothUsageDescription: + macOSTargetOSVersion: 10.13.0 + switchNMETAOverride: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchEnableFileSystemTrace: 0 + switchLTOSetting: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchCompilerFlags: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchTitleNames_15: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchPublisherNames_15: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchIcons_15: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchSmallIcons_15: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchRatingsInt_12: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 22 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 0 + switchSupportedNpadCount: 8 + switchEnableTouchScreen: 1 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchDisableHTCSPlayerConnection: 0 + switchUseNewStyleFilepaths: 1 + switchUseLegacyFmodPriorities: 0 + switchUseMicroSleepForYield: 1 + switchEnableRamDiskSupport: 0 + switchMicroSleepForYieldTime: 25 + switchRamDiskSpaceSize: 12 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4ExtraSceSysFile: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 1 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4UseLowGarlicFragmentationMode: 1 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 0 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4CompatibilityPS5: 0 + ps4AllowPS5Detection: 0 + ps4GPU800MHz: 1 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + ps4attribVROutputEnabled: 0 + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 16 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLShowDiagnostics: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 1 + webGLWasmArithmeticExceptions: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLDecompressionFallback: 0 + webGLInitialMemorySize: 32 + webGLMaximumMemorySize: 2048 + webGLMemoryGrowthMode: 2 + webGLMemoryLinearGrowthStep: 16 + webGLMemoryGeometricGrowthStep: 0.2 + webGLMemoryGeometricGrowthCap: 96 + webGLPowerPreference: 2 + scriptingDefineSymbols: + Android: UNICLI_REMOTE + additionalCompilerArguments: {} + platformArchitecture: {} + scriptingBackend: + Android: 1 + il2cppCompilerConfiguration: {} + il2cppCodeGeneration: {} + managedStrippingLevel: + Android: 4 + EmbeddedLinux: 1 + GameCoreScarlett: 1 + GameCoreXboxOne: 1 + Nintendo Switch: 1 + PS4: 1 + PS5: 1 + QNX: 1 + Stadia: 1 + VisionOS: 1 + WebGL: 1 + Windows Store Apps: 1 + XboxOne: 1 + iPhone: 1 + tvOS: 1 + incrementalIl2cppBuild: {} + suppressCommonWarnings: 1 + allowUnsafeCode: 0 + useDeterministicCompilation: 1 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 1 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: UniCli.Unity + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: UniCli.Unity + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} + metroSplashScreenUseBackgroundColor: 0 + syncCapabilities: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + vcxProjDefaultLanguage: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnhancedXboxCompatibilityMode: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + XboxOneOverrideIdentityName: + XboxOneOverrideIdentityPublisher: + vrEditorSettings: {} + cloudServicesEnabled: + UNet: 1 + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + hmiPlayerDataPath: + hmiForceSRGBBlit: 1 + embeddedLinuxEnableGamepadInput: 1 + hmiLogStartupTiming: 0 + hmiCpuConfiguration: + apiCompatibilityLevel: 6 + activeInputHandler: 0 + windowsGamepadBackendHint: 0 + cloudProjectId: + framebufferDepthMemorylessMode: 0 + qualitySettingsNames: [] + projectName: + organizationId: + cloudEnabled: 0 + legacyClampBlendShapeWeights: 0 + hmiLoadingImage: {fileID: 0} + platformRequiresReadableAssets: 0 + virtualTexturingSupportEnabled: 0 + insecureHttpOption: 0 + +``` + +`src/UniCli.Unity/ProjectSettings/ProjectVersion.txt`: + +```txt +m_EditorVersion: 2022.3.62f3 +m_EditorVersionWithRevision: 2022.3.62f3 (96770f904ca7) + +``` + +`src/UniCli.Unity/ProjectSettings/QualitySettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 5 + m_QualitySettings: + - serializedVersion: 2 + name: Very Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 1 + textureQuality: 1 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.3 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 4 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.4 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 16 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Medium + pixelLightCount: 1 + shadows: 1 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 1 + lodBias: 0.7 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 64 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: High + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Very High + pixelLightCount: 3 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 70 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1.5 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 1024 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Ultra + pixelLightCount: 4 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 4 + shadowDistance: 150 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 2 + maximumLODLevel: 0 + streamingMipmapsActive: 0 + streamingMipmapsAddAllCameras: 1 + streamingMipmapsMemoryBudget: 512 + streamingMipmapsRenderersPerFrame: 512 + streamingMipmapsMaxLevelReduction: 2 + streamingMipmapsMaxFileIORequests: 1024 + particleRaycastBudget: 4096 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 16 + asyncUploadPersistentBuffer: 1 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + m_PerPlatformDefaultQuality: + Android: 2 + Lumin: 5 + GameCoreScarlett: 5 + GameCoreXboxOne: 5 + Nintendo 3DS: 5 + Nintendo Switch: 5 + PS4: 5 + PS5: 5 + Stadia: 5 + Standalone: 5 + WebGL: 3 + Windows Store Apps: 5 + XboxOne: 5 + iPhone: 2 + tvOS: 2 + +``` + +`src/UniCli.Unity/ProjectSettings/SceneTemplateSettings.json`: + +```json +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicMaterial", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "defaultInstantiationMode": 1 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "defaultInstantiationMode": 0 + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "defaultInstantiationMode": 1 + }, + "newSceneOverride": 0 +} +``` + +`src/UniCli.Unity/ProjectSettings/TagManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 + +``` + +`src/UniCli.Unity/ProjectSettings/TimeManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 + +``` + +`src/UniCli.Unity/ProjectSettings/TimelineSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 53 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a287be6c49135cd4f9b2b8666c39d999, type: 3} + m_Name: + m_EditorClassIdentifier: + assetDefaultFramerate: 60 + m_DefaultFrameRate: 60 + +``` + +`src/UniCli.Unity/ProjectSettings/UniCliSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 53 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e786804419d3643acbb687bb34722709, type: 3} + m_Name: + m_EditorClassIdentifier: + disabledModules: [] + +``` + +`src/UniCli.Unity/ProjectSettings/UnityConnectSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 1 + m_Enabled: 0 + m_TestMode: 0 + m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events + m_EventUrl: https://cdp.cloud.unity3d.com/v1/events + m_ConfigUrl: https://config.uca.cloud.unity3d.com + m_DashboardUrl: https://dashboard.unity3d.com + m_TestInitMode: 0 + CrashReportingSettings: + m_EventUrl: https://perf-events.cloud.unity3d.com + m_Enabled: 0 + m_LogBufferSize: 10 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_TestMode: 0 + m_InitializeOnStartup: 1 + m_PackageRequiringCoreStatsPresent: 0 + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 + +``` + +`src/UniCli.Unity/ProjectSettings/VFXManager.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!937362698 &1 +VFXManager: + m_ObjectHideFlags: 0 + m_IndirectShader: {fileID: 0} + m_CopyBufferShader: {fileID: 0} + m_SortShader: {fileID: 0} + m_StripUpdateShader: {fileID: 0} + m_RenderPipeSettingsPath: + m_FixedTimeStep: 0.016666668 + m_MaxDeltaTime: 0.05 + +``` + +`src/UniCli.Unity/ProjectSettings/VersionControlSettings.asset`: + +```asset +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 + +``` + +`src/UniCli.Unity/ProjectSettings/XRSettings.asset`: + +```asset +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} +``` + +`src/UniCli.Unity/UniCli.Server.Editor.csproj.DotSettings`: + +```DotSettings + + True + True + True + True + True + True + True + True + True + True + True + True +``` + +`src/UniCli.Unity/UniCli.Server.Runtime.csproj.DotSettings`: + +```DotSettings + + True +``` + +`tools/generate-command-reference.sh`: + +```sh +#!/usr/bin/env bash +set -euo pipefail + +# Generate doc/commands.md from unicli commands --json output. + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +UNICLI_BIN="${ROOT_DIR}/.build/unicli" +OUTPUT="${ROOT_DIR}/doc/commands.md" + +if ! command -v jq &>/dev/null; then + echo "Error: jq is required. Install with 'brew install jq'." >&2 + exit 1 +fi + +if [[ ! -f "$UNICLI_BIN" ]]; then + echo "Error: .build/unicli not found. Run 'dotnet publish src/UniCli.Client -o .build' first." >&2 + exit 1 +fi + +export UNICLI_PROJECT="${UNICLI_PROJECT:-${ROOT_DIR}/src/UniCli.Unity}" + +JSON=$("$UNICLI_BIN" commands --json 2>/dev/null) + +mkdir -p "$(dirname "$OUTPUT")" + +# Use jq to transform JSON into markdown +echo "$JSON" | jq -r ' + .data | sort_by(.name) | + + # Group by module (or derive from command name) + group_by( + if (.module // "") != "" then .module + elif (.name | contains(".")) then (.name | split(".")[0]) + else "Core" + end + ) | + + # Sort groups: Core first, then alphabetical + sort_by( + if .[0] | + (if (.module // "") != "" then .module + elif (.name | contains(".")) then (.name | split(".")[0]) + else "Core" + end) == "Core" + then "" + else + .[0] | + if (.module // "") != "" then .module + elif (.name | contains(".")) then (.name | split(".")[0]) + else "Core" + end + end + ) | + + # Header + "# Command Reference\n\n> Auto-generated from `unicli commands --json`. Run `tools/generate-command-reference.sh` to update.\n", + + # Each group + (.[] | + # Derive group name + (.[0] | + if (.module // "") != "" then .module + elif (.name | contains(".")) then (.name | split(".")[0]) + else "Core" + end + ) as $group | + + "\n## \($group)\n", + + (.[] | + "\n### \(.name)\n", + + (if (.description // "") != "" then "\(.description)\n" else "" end), + + # Parameters + (if (.requestFields // [] | length) == 0 then + "**Parameters:** None\n" + else + "**Parameters:**\n\n| Field | Type |\n|---|---|\n" + + ([.requestFields[] | + "`\(.name)` | `\(.type)`" + + (if (.defaultValue // "") != "" then " (default: `\(.defaultValue)`)" else "" end) + ] | map("| \(.) |") | join("\n")) + "\n" + end), + + # Response + (if (.responseFields // [] | length) == 0 then + "**Response:** None\n" + else + "**Response:**\n\n| Field | Type |\n|---|---|\n" + + ([.responseFields[] | + "`\(.name)` | `\(.type)`" + + (if (.defaultValue // "") != "" then " (default: `\(.defaultValue)`)" else "" end) + ] | map("| \(.) |") | join("\n")) + "\n" + end), + + "---\n" + ) + ) +' > "$OUTPUT" + +COUNT=$(echo "$JSON" | jq '.data | length') +echo "Generated ${OUTPUT} (${COUNT} commands)" + +``` \ No newline at end of file