@atelierai/uco 1.0.7 → 1.0.8

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 (87) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +1 -1
  3. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetConfig.cs +132 -132
  4. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDependencyResolver.cs +237 -237
  5. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDownloader.cs +82 -82
  6. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/MenuItems.cs +247 -247
  7. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/ToolGroups.cs +153 -153
  8. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/SystemTool/Skills.Create.SkillBody.cs +155 -155
  9. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Console.GetLogs.cs +95 -95
  10. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Docs.HttpFetcher.cs +139 -139
  11. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Instance.ListAll.cs +72 -72
  12. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/PathReadHelper.cs +106 -106
  13. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Script.Validate.cs +252 -252
  14. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests/TestResultCollector.cs +537 -537
  15. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests.cs +400 -400
  16. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Branding/ProductInfo.cs +62 -62
  17. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +1034 -1034
  18. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Skills/Skill_InitialSetup.cs +197 -197
  19. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.Editor.cs +201 -201
  20. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.cs +67 -67
  21. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotListWindowBase.cs +393 -393
  22. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotWindowBase.cs +113 -113
  23. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.Connection.cs +3 -3
  24. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotFeatures.cs +195 -195
  25. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +20 -20
  26. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CreateGUI.cs +354 -354
  27. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.cs +78 -78
  28. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowInitializer.cs +47 -47
  29. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/SerializationCheckWindow.cs +190 -190
  30. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Build.cs +91 -91
  31. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Config.cs +298 -298
  32. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Static.cs +296 -296
  33. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.cs +69 -69
  34. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PackageUtils.cs +168 -168
  35. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PrefsKeyMigration.cs +119 -119
  36. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/SafeDefaultsGuard.cs +180 -180
  37. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ScriptUtils.cs +229 -229
  38. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ToolsManifestGenerator.cs +200 -200
  39. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringAdapters.cs +1467 -1467
  40. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringSafetyReloadGuard.cs +65 -65
  41. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityCopilotUpdateProjectSettings.cs +66 -66
  42. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
  43. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
  44. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
  45. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Converter/Json/Structures/EntityIdConverter.cs +120 -120
  46. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.cs +110 -110
  47. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.pre-Unity.6.5.cs +110 -110
  48. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Unity/Logs/FileLogStorage.cs +547 -547
  49. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Build.cs +243 -243
  50. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +3 -3
  51. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +295 -295
  52. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginBuilder.cs +105 -105
  53. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.Static.cs +155 -155
  54. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.cs +59 -59
  55. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +11 -11
  56. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/AtomicApiTestFixtures.cs +99 -99
  57. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/TryModifyAtTests.cs +227 -227
  58. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AuthoringSafetyPilotTests.cs +1050 -1050
  59. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetLegacyMigrationTests.cs +304 -304
  60. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +1 -1
  61. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/BlacklistTypeTests.cs +416 -416
  62. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.cs +172 -172
  63. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.pre-Unity.6.5.cs +169 -169
  64. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.cs +182 -182
  65. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.pre-Unity.6.5.cs +179 -179
  66. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.cs +175 -175
  67. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.pre-Unity.6.5.cs +174 -174
  68. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.cs +171 -171
  69. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.pre-Unity.6.5.cs +170 -170
  70. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsCreateFolderTests.cs +243 -243
  71. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsMaterialTests.cs +101 -101
  72. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.cs +348 -348
  73. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.pre-Unity.6.5.cs +348 -348
  74. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsShaderTests.cs +194 -194
  75. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.cs +573 -573
  76. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.pre-Unity.6.5.cs +513 -513
  77. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.cs +154 -154
  78. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.pre-Unity.6.5.cs +154 -154
  79. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotGameViewTests.cs +164 -164
  80. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotSceneViewTests.cs +155 -155
  81. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolParameterTests.cs +56 -56
  82. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.cs +651 -651
  83. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.pre-Unity.6.5.cs +651 -651
  84. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ToolBatchExecutionIntegrationTests.cs +407 -407
  85. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs → UcoPluginTests.cs} +105 -105
  86. package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
  87. /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
+ }