@atelierai/uco 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +29 -8
  2. package/package.json +2 -2
  3. package/vendor/plugin/com.atelierai.unity.copilot/CHANGELOG.md +4 -4
  4. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetConfig.cs +132 -132
  5. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDependencyResolver.cs +237 -237
  6. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDownloader.cs +82 -82
  7. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/MenuItems.cs +247 -247
  8. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/ToolGroups.cs +153 -153
  9. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/SystemTool/Skills.Create.SkillBody.cs +155 -155
  10. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Console.GetLogs.cs +95 -95
  11. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Docs.HttpFetcher.cs +139 -139
  12. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Instance.ListAll.cs +72 -72
  13. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/PathReadHelper.cs +106 -106
  14. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Script.Validate.cs +252 -252
  15. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests/TestResultCollector.cs +537 -537
  16. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests.cs +400 -400
  17. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/ToolGroups.SetEnabled.cs +92 -92
  18. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Branding/ProductInfo.cs +62 -62
  19. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +887 -1034
  20. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Skills/Skill_InitialSetup.cs +197 -197
  21. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.Editor.cs +201 -201
  22. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.cs +67 -67
  23. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotListWindowBase.cs +393 -393
  24. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotWindowBase.cs +113 -113
  25. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.Connection.cs +3 -3
  26. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotFeatures.cs +195 -195
  27. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +22 -22
  28. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CreateGUI.cs +354 -354
  29. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.cs +78 -78
  30. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowInitializer.cs +47 -47
  31. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/SerializationCheckWindow.cs +190 -190
  32. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Build.cs +91 -91
  33. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Config.cs +298 -298
  34. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Static.cs +296 -296
  35. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.cs +69 -69
  36. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PackageUtils.cs +168 -168
  37. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PrefsKeyMigration.cs +119 -119
  38. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/SafeDefaultsGuard.cs +180 -180
  39. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ScriptUtils.cs +229 -229
  40. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ToolGroupRegistry.cs +461 -460
  41. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ToolsManifestGenerator.cs +200 -200
  42. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringAdapters.cs +1467 -1467
  43. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringSafetyReloadGuard.cs +65 -65
  44. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityCopilotUpdateProjectSettings.cs +66 -66
  45. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotPromptsWindow.uss +11 -11
  46. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotResourcesWindow.uss +16 -16
  47. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotToolsWindow.uss +36 -36
  48. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/MainWindow.uss +183 -183
  49. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/{_mcp-list-window.uss → _copilot-list-window.uss} +18 -18
  50. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/_list-view.uss +77 -77
  51. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotPromptsWindow.uxml +18 -18
  52. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotResourcesWindow.uxml +18 -18
  53. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotToolsWindow.uxml +18 -18
  54. package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/MainWindow.uxml +115 -115
  55. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
  56. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
  57. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
  58. package/vendor/plugin/com.atelierai.unity.copilot/README.md +88 -88
  59. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Converter/Json/Structures/EntityIdConverter.cs +120 -120
  60. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.cs +110 -110
  61. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.pre-Unity.6.5.cs +110 -110
  62. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Unity/Logs/FileLogStorage.cs +547 -547
  63. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Build.cs +243 -243
  64. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +3 -3
  65. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +295 -295
  66. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginBuilder.cs +105 -105
  67. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.Static.cs +155 -155
  68. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.cs +59 -59
  69. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +11 -11
  70. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/AtomicApiTestFixtures.cs +99 -99
  71. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/TryModifyAtTests.cs +227 -227
  72. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AuthoringSafetyPilotTests.cs +1050 -1050
  73. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetExtractorDevelopmentDependencyTests.cs +148 -148
  74. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetExtractorFlatLayoutTests.cs +166 -166
  75. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetInstallManifestTests.cs +436 -436
  76. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetLegacyMigrationTests.cs +304 -304
  77. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetPackageInstallerStaleVersionCleanupTests.cs +200 -200
  78. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +2 -2
  79. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/BlacklistTypeTests.cs +416 -416
  80. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.cs +172 -172
  81. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.pre-Unity.6.5.cs +169 -169
  82. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.cs +182 -182
  83. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.pre-Unity.6.5.cs +179 -179
  84. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.cs +175 -175
  85. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.pre-Unity.6.5.cs +174 -174
  86. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.cs +171 -171
  87. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.pre-Unity.6.5.cs +170 -170
  88. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsCreateFolderTests.cs +243 -243
  89. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsMaterialTests.cs +101 -101
  90. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.cs +348 -348
  91. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.pre-Unity.6.5.cs +348 -348
  92. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsShaderTests.cs +194 -194
  93. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/GameObject/TestJsonSchema.cs +199 -199
  94. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.cs +573 -573
  95. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.pre-Unity.6.5.cs +513 -513
  96. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.cs +154 -154
  97. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.pre-Unity.6.5.cs +154 -154
  98. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotGameViewTests.cs +164 -164
  99. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotSceneViewTests.cs +155 -155
  100. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolParameterTests.cs +56 -56
  101. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.cs +651 -651
  102. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.pre-Unity.6.5.cs +651 -651
  103. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ToolBatchExecutionIntegrationTests.cs +407 -407
  104. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs → UcoPluginTests.cs} +105 -105
  105. package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
  106. /package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/{_mcp-list-window.uss.meta → _copilot-list-window.uss.meta} +0 -0
  107. /package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs.meta → UcoPluginTests.cs.meta} +0 -0
@@ -1,247 +1,247 @@
1
- /*
2
- * Design inspired by MCP for Unity (CoplayDev/unity-mcp), Copyright (c) Coplay Inc., MIT License.
3
- */
4
-
5
- /*
6
- ┌──────────────────────────────────────────────────────────────────┐
7
- │ Author: Ivan Murzak (https://github.com/IvanMurzak) │
8
- │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
9
- │ Copyright (c) 2025 Ivan Murzak │
10
- │ Licensed under the Apache License, Version 2.0. │
11
- │ See the LICENSE file in the project root for more information. │
12
- └──────────────────────────────────────────────────────────────────┘
13
- */
14
-
15
- #nullable enable
16
- using System.Collections.Generic;
17
- using System.ComponentModel;
18
- using System.Reflection;
19
- using com.AtelierAI.Uco.Framework;
20
- using com.AtelierAI.Uco.Framework.Common;
21
- using com.AtelierAI.Uco.Framework.Common.Model;
22
- using com.IvanMurzak.ReflectorNet.Utils;
23
- using UnityEditor;
24
-
25
- namespace AIGD
26
- {
27
- [Description("A single Unity Editor [MenuItem] entry discovered via reflection.")]
28
- public class MenuItemEntry
29
- {
30
- [Description("Full menu path as registered with UnityEditor.MenuItem (without shortcut modifiers).")]
31
- public string Path { get; set; } = string.Empty;
32
-
33
- [Description("Keyboard shortcut suffix parsed from the menu path (e.g. '&%a'). Null when not present.")]
34
- public string? Shortcut { get; set; }
35
-
36
- [Description("Fully-qualified name of the assembly that declares the [MenuItem] method.")]
37
- public string AssemblyName { get; set; } = string.Empty;
38
-
39
- [Description("Fully-qualified name of the type that declares the [MenuItem] method.")]
40
- public string DeclaringType { get; set; } = string.Empty;
41
-
42
- [Description("True when this entry is a validator method (the attribute's 'validate' flag is set).")]
43
- public bool HasValidate { get; set; }
44
- }
45
- }
46
-
47
- namespace com.AtelierAI.Unity.Copilot.Editor.API
48
- {
49
- using AIGD;
50
- using Consts = com.AtelierAI.Uco.Framework.Common.Consts;
51
-
52
- [UcoResourceType]
53
- public partial class Resource_MenuItems
54
- {
55
- public const string MenuItemsResourceUri = "editor://menu-items";
56
-
57
- [UcoResource
58
- (
59
- Name = "Menu Items",
60
- Route = MenuItemsResourceUri,
61
- MimeType = Consts.MimeType.TextJson,
62
- ListResources = nameof(ListAll),
63
- Description = "Enumerate every Unity Editor [MenuItem] discovered via reflection. " +
64
- "Use it to pick a canonical menu path before calling the 'editor-execute-menu-item' tool.",
65
- Enabled = true
66
- )]
67
- public ResponseResourceContent[] GetMenuItems(string uri)
68
- {
69
- return MainThread.Instance.Run(() =>
70
- {
71
- var entries = CollectMenuItems();
72
- var json = SerializeEntries(entries);
73
- return ResponseResourceContent.CreateText(
74
- uri: uri,
75
- mimeType: Consts.MimeType.TextJson,
76
- text: json
77
- ).MakeArray();
78
- });
79
- }
80
-
81
- public ResponseListResource[] ListAll() => new[]
82
- {
83
- new ResponseListResource(
84
- uri: MenuItemsResourceUri,
85
- name: "Menu Items",
86
- enabled: true,
87
- mimeType: Consts.MimeType.TextJson)
88
- };
89
-
90
- // ---------------------------------------------------------------------
91
- // Reflection helpers
92
- // ---------------------------------------------------------------------
93
-
94
- private static IReadOnlyList<MenuItemEntry> CollectMenuItems()
95
- {
96
- // TypeCache.GetMethodsWithAttribute is available in Unity 2019.2+ and indexes every loaded
97
- // assembly's methods bearing a [MenuItem]. Domain reload keeps the cache fresh, so calls
98
- // from MCP requests will always see the current project's menu items.
99
- var methods = TypeCache.GetMethodsWithAttribute<UnityEditor.MenuItem>();
100
- var result = new List<MenuItemEntry>(methods.Count);
101
-
102
- foreach (var method in methods)
103
- {
104
- var attrs = method.GetCustomAttributes<UnityEditor.MenuItem>(inherit: false);
105
- foreach (var attr in attrs)
106
- {
107
- if (attr == null || string.IsNullOrEmpty(attr.menuItem))
108
- continue;
109
-
110
- SplitPathAndShortcut(attr.menuItem, out var path, out var shortcut);
111
- var declaring = method.DeclaringType;
112
- result.Add(new MenuItemEntry
113
- {
114
- Path = path,
115
- Shortcut = shortcut,
116
- DeclaringType = declaring?.FullName ?? string.Empty,
117
- AssemblyName = declaring?.Assembly.GetName().Name ?? string.Empty,
118
- HasValidate = attr.validate
119
- });
120
- }
121
- }
122
-
123
- // Stable ordering: path, then validators after the executable entry for the same path.
124
- result.Sort((a, b) =>
125
- {
126
- var cmp = string.CompareOrdinal(a.Path, b.Path);
127
- if (cmp != 0) return cmp;
128
- return a.HasValidate.CompareTo(b.HasValidate);
129
- });
130
- return result;
131
- }
132
-
133
- /// <summary>
134
- /// MenuItem paths can carry a keyboard shortcut as a suffix separated by a space, e.g.
135
- /// <c>"My/Menu/Do Thing %#a"</c>. The shortcut tokens start with one of '&', '%', '#', '_',
136
- /// or the special <c>LEFT</c>/<c>RIGHT</c>/<c>UP</c>/<c>DOWN</c>/<c>F\d+</c> keywords. We
137
- /// detect the boundary by locating the last space whose right-hand side looks like a
138
- /// shortcut spec — this matches Unity's own behaviour without depending on internal APIs.
139
- /// </summary>
140
- private static void SplitPathAndShortcut(string raw, out string path, out string? shortcut)
141
- {
142
- path = raw;
143
- shortcut = null;
144
- if (string.IsNullOrEmpty(raw))
145
- return;
146
-
147
- var spaceIdx = raw.LastIndexOf(' ');
148
- if (spaceIdx <= 0 || spaceIdx >= raw.Length - 1)
149
- return;
150
-
151
- var tail = raw.Substring(spaceIdx + 1);
152
- if (LooksLikeShortcut(tail))
153
- {
154
- path = raw.Substring(0, spaceIdx).TrimEnd();
155
- shortcut = tail;
156
- }
157
- }
158
-
159
- private static bool LooksLikeShortcut(string tail)
160
- {
161
- // Modifier-prefixed: %, &, #, _
162
- if (tail.Length > 0)
163
- {
164
- var c = tail[0];
165
- if (c == '%' || c == '&' || c == '#' || c == '_')
166
- return true;
167
- }
168
- // Special key names recognised by Unity's MenuItem parser
169
- switch (tail)
170
- {
171
- case "LEFT":
172
- case "RIGHT":
173
- case "UP":
174
- case "DOWN":
175
- case "HOME":
176
- case "END":
177
- case "PGUP":
178
- case "PGDN":
179
- return true;
180
- }
181
- if (tail.Length >= 2 && tail[0] == 'F'
182
- && int.TryParse(tail.Substring(1), out var fn) && fn >= 1 && fn <= 24)
183
- return true;
184
- return false;
185
- }
186
-
187
- // ---------------------------------------------------------------------
188
- // Minimal JSON serializer for the resource payload.
189
- // We avoid pulling in Newtonsoft / System.Text.Json runtime configuration
190
- // here because the schema is tiny and fully under our control.
191
- // ---------------------------------------------------------------------
192
- private static string SerializeEntries(IReadOnlyList<MenuItemEntry> entries)
193
- {
194
- var sb = new System.Text.StringBuilder(entries.Count * 64);
195
- sb.Append('[');
196
- for (var i = 0; i < entries.Count; i++)
197
- {
198
- if (i > 0) sb.Append(',');
199
- var e = entries[i];
200
- sb.Append('{');
201
- sb.Append("\"path\":"); AppendJsonString(sb, e.Path);
202
- sb.Append(",\"shortcut\":"); AppendJsonStringOrNull(sb, e.Shortcut);
203
- sb.Append(",\"assemblyName\":"); AppendJsonString(sb, e.AssemblyName);
204
- sb.Append(",\"declaringType\":"); AppendJsonString(sb, e.DeclaringType);
205
- sb.Append(",\"hasValidate\":").Append(e.HasValidate ? "true" : "false");
206
- sb.Append('}');
207
- }
208
- sb.Append(']');
209
- return sb.ToString();
210
- }
211
-
212
- private static void AppendJsonStringOrNull(System.Text.StringBuilder sb, string? value)
213
- {
214
- if (value == null)
215
- {
216
- sb.Append("null");
217
- return;
218
- }
219
- AppendJsonString(sb, value);
220
- }
221
-
222
- private static void AppendJsonString(System.Text.StringBuilder sb, string value)
223
- {
224
- sb.Append('"');
225
- foreach (var c in value)
226
- {
227
- switch (c)
228
- {
229
- case '"': sb.Append("\\\""); break;
230
- case '\\': sb.Append("\\\\"); break;
231
- case '\b': sb.Append("\\b"); break;
232
- case '\f': sb.Append("\\f"); break;
233
- case '\n': sb.Append("\\n"); break;
234
- case '\r': sb.Append("\\r"); break;
235
- case '\t': sb.Append("\\t"); break;
236
- default:
237
- if (c < 0x20)
238
- sb.Append("\\u").Append(((int)c).ToString("x4"));
239
- else
240
- sb.Append(c);
241
- break;
242
- }
243
- }
244
- sb.Append('"');
245
- }
246
- }
247
- }
1
+ /*
2
+ * Design inspired by MCP for Unity (CoplayDev/unity-mcp), Copyright (c) Coplay Inc., MIT License.
3
+ */
4
+
5
+ /*
6
+ ┌──────────────────────────────────────────────────────────────────┐
7
+ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │
8
+ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
9
+ │ Copyright (c) 2025 Ivan Murzak │
10
+ │ Licensed under the Apache License, Version 2.0. │
11
+ │ See the LICENSE file in the project root for more information. │
12
+ └──────────────────────────────────────────────────────────────────┘
13
+ */
14
+
15
+ #nullable enable
16
+ using System.Collections.Generic;
17
+ using System.ComponentModel;
18
+ using System.Reflection;
19
+ using com.AtelierAI.Uco.Framework;
20
+ using com.AtelierAI.Uco.Framework.Common;
21
+ using com.AtelierAI.Uco.Framework.Common.Model;
22
+ using com.IvanMurzak.ReflectorNet.Utils;
23
+ using UnityEditor;
24
+
25
+ namespace AIGD
26
+ {
27
+ [Description("A single Unity Editor [MenuItem] entry discovered via reflection.")]
28
+ public class MenuItemEntry
29
+ {
30
+ [Description("Full menu path as registered with UnityEditor.MenuItem (without shortcut modifiers).")]
31
+ public string Path { get; set; } = string.Empty;
32
+
33
+ [Description("Keyboard shortcut suffix parsed from the menu path (e.g. '&%a'). Null when not present.")]
34
+ public string? Shortcut { get; set; }
35
+
36
+ [Description("Fully-qualified name of the assembly that declares the [MenuItem] method.")]
37
+ public string AssemblyName { get; set; } = string.Empty;
38
+
39
+ [Description("Fully-qualified name of the type that declares the [MenuItem] method.")]
40
+ public string DeclaringType { get; set; } = string.Empty;
41
+
42
+ [Description("True when this entry is a validator method (the attribute's 'validate' flag is set).")]
43
+ public bool HasValidate { get; set; }
44
+ }
45
+ }
46
+
47
+ namespace com.AtelierAI.Unity.Copilot.Editor.API
48
+ {
49
+ using AIGD;
50
+ using Consts = com.AtelierAI.Uco.Framework.Common.Consts;
51
+
52
+ [UcoResourceType]
53
+ public partial class Resource_MenuItems
54
+ {
55
+ public const string MenuItemsResourceUri = "editor://menu-items";
56
+
57
+ [UcoResource
58
+ (
59
+ Name = "Menu Items",
60
+ Route = MenuItemsResourceUri,
61
+ MimeType = Consts.MimeType.TextJson,
62
+ ListResources = nameof(ListAll),
63
+ Description = "Enumerate every Unity Editor [MenuItem] discovered via reflection. " +
64
+ "Use it to pick a canonical menu path before calling the 'editor-execute-menu-item' tool.",
65
+ Enabled = true
66
+ )]
67
+ public ResponseResourceContent[] GetMenuItems(string uri)
68
+ {
69
+ return MainThread.Instance.Run(() =>
70
+ {
71
+ var entries = CollectMenuItems();
72
+ var json = SerializeEntries(entries);
73
+ return ResponseResourceContent.CreateText(
74
+ uri: uri,
75
+ mimeType: Consts.MimeType.TextJson,
76
+ text: json
77
+ ).MakeArray();
78
+ });
79
+ }
80
+
81
+ public ResponseListResource[] ListAll() => new[]
82
+ {
83
+ new ResponseListResource(
84
+ uri: MenuItemsResourceUri,
85
+ name: "Menu Items",
86
+ enabled: true,
87
+ mimeType: Consts.MimeType.TextJson)
88
+ };
89
+
90
+ // ---------------------------------------------------------------------
91
+ // Reflection helpers
92
+ // ---------------------------------------------------------------------
93
+
94
+ private static IReadOnlyList<MenuItemEntry> CollectMenuItems()
95
+ {
96
+ // TypeCache.GetMethodsWithAttribute is available in Unity 2019.2+ and indexes every loaded
97
+ // assembly's methods bearing a [MenuItem]. Domain reload keeps the cache fresh, so calls
98
+ // from Uco requests will always see the current project's menu items.
99
+ var methods = TypeCache.GetMethodsWithAttribute<UnityEditor.MenuItem>();
100
+ var result = new List<MenuItemEntry>(methods.Count);
101
+
102
+ foreach (var method in methods)
103
+ {
104
+ var attrs = method.GetCustomAttributes<UnityEditor.MenuItem>(inherit: false);
105
+ foreach (var attr in attrs)
106
+ {
107
+ if (attr == null || string.IsNullOrEmpty(attr.menuItem))
108
+ continue;
109
+
110
+ SplitPathAndShortcut(attr.menuItem, out var path, out var shortcut);
111
+ var declaring = method.DeclaringType;
112
+ result.Add(new MenuItemEntry
113
+ {
114
+ Path = path,
115
+ Shortcut = shortcut,
116
+ DeclaringType = declaring?.FullName ?? string.Empty,
117
+ AssemblyName = declaring?.Assembly.GetName().Name ?? string.Empty,
118
+ HasValidate = attr.validate
119
+ });
120
+ }
121
+ }
122
+
123
+ // Stable ordering: path, then validators after the executable entry for the same path.
124
+ result.Sort((a, b) =>
125
+ {
126
+ var cmp = string.CompareOrdinal(a.Path, b.Path);
127
+ if (cmp != 0) return cmp;
128
+ return a.HasValidate.CompareTo(b.HasValidate);
129
+ });
130
+ return result;
131
+ }
132
+
133
+ /// <summary>
134
+ /// MenuItem paths can carry a keyboard shortcut as a suffix separated by a space, e.g.
135
+ /// <c>"My/Menu/Do Thing %#a"</c>. The shortcut tokens start with one of '&', '%', '#', '_',
136
+ /// or the special <c>LEFT</c>/<c>RIGHT</c>/<c>UP</c>/<c>DOWN</c>/<c>F\d+</c> keywords. We
137
+ /// detect the boundary by locating the last space whose right-hand side looks like a
138
+ /// shortcut spec — this matches Unity's own behaviour without depending on internal APIs.
139
+ /// </summary>
140
+ private static void SplitPathAndShortcut(string raw, out string path, out string? shortcut)
141
+ {
142
+ path = raw;
143
+ shortcut = null;
144
+ if (string.IsNullOrEmpty(raw))
145
+ return;
146
+
147
+ var spaceIdx = raw.LastIndexOf(' ');
148
+ if (spaceIdx <= 0 || spaceIdx >= raw.Length - 1)
149
+ return;
150
+
151
+ var tail = raw.Substring(spaceIdx + 1);
152
+ if (LooksLikeShortcut(tail))
153
+ {
154
+ path = raw.Substring(0, spaceIdx).TrimEnd();
155
+ shortcut = tail;
156
+ }
157
+ }
158
+
159
+ private static bool LooksLikeShortcut(string tail)
160
+ {
161
+ // Modifier-prefixed: %, &, #, _
162
+ if (tail.Length > 0)
163
+ {
164
+ var c = tail[0];
165
+ if (c == '%' || c == '&' || c == '#' || c == '_')
166
+ return true;
167
+ }
168
+ // Special key names recognised by Unity's MenuItem parser
169
+ switch (tail)
170
+ {
171
+ case "LEFT":
172
+ case "RIGHT":
173
+ case "UP":
174
+ case "DOWN":
175
+ case "HOME":
176
+ case "END":
177
+ case "PGUP":
178
+ case "PGDN":
179
+ return true;
180
+ }
181
+ if (tail.Length >= 2 && tail[0] == 'F'
182
+ && int.TryParse(tail.Substring(1), out var fn) && fn >= 1 && fn <= 24)
183
+ return true;
184
+ return false;
185
+ }
186
+
187
+ // ---------------------------------------------------------------------
188
+ // Minimal JSON serializer for the resource payload.
189
+ // We avoid pulling in Newtonsoft / System.Text.Json runtime configuration
190
+ // here because the schema is tiny and fully under our control.
191
+ // ---------------------------------------------------------------------
192
+ private static string SerializeEntries(IReadOnlyList<MenuItemEntry> entries)
193
+ {
194
+ var sb = new System.Text.StringBuilder(entries.Count * 64);
195
+ sb.Append('[');
196
+ for (var i = 0; i < entries.Count; i++)
197
+ {
198
+ if (i > 0) sb.Append(',');
199
+ var e = entries[i];
200
+ sb.Append('{');
201
+ sb.Append("\"path\":"); AppendJsonString(sb, e.Path);
202
+ sb.Append(",\"shortcut\":"); AppendJsonStringOrNull(sb, e.Shortcut);
203
+ sb.Append(",\"assemblyName\":"); AppendJsonString(sb, e.AssemblyName);
204
+ sb.Append(",\"declaringType\":"); AppendJsonString(sb, e.DeclaringType);
205
+ sb.Append(",\"hasValidate\":").Append(e.HasValidate ? "true" : "false");
206
+ sb.Append('}');
207
+ }
208
+ sb.Append(']');
209
+ return sb.ToString();
210
+ }
211
+
212
+ private static void AppendJsonStringOrNull(System.Text.StringBuilder sb, string? value)
213
+ {
214
+ if (value == null)
215
+ {
216
+ sb.Append("null");
217
+ return;
218
+ }
219
+ AppendJsonString(sb, value);
220
+ }
221
+
222
+ private static void AppendJsonString(System.Text.StringBuilder sb, string value)
223
+ {
224
+ sb.Append('"');
225
+ foreach (var c in value)
226
+ {
227
+ switch (c)
228
+ {
229
+ case '"': sb.Append("\\\""); break;
230
+ case '\\': sb.Append("\\\\"); break;
231
+ case '\b': sb.Append("\\b"); break;
232
+ case '\f': sb.Append("\\f"); break;
233
+ case '\n': sb.Append("\\n"); break;
234
+ case '\r': sb.Append("\\r"); break;
235
+ case '\t': sb.Append("\\t"); break;
236
+ default:
237
+ if (c < 0x20)
238
+ sb.Append("\\u").Append(((int)c).ToString("x4"));
239
+ else
240
+ sb.Append(c);
241
+ break;
242
+ }
243
+ }
244
+ sb.Append('"');
245
+ }
246
+ }
247
+ }