mcpforunityserver 9.4.0b20260203025228__py3-none-any.whl
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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +3 -0
- cli/commands/animation.py +84 -0
- cli/commands/asset.py +280 -0
- cli/commands/audio.py +125 -0
- cli/commands/batch.py +171 -0
- cli/commands/code.py +182 -0
- cli/commands/component.py +190 -0
- cli/commands/editor.py +447 -0
- cli/commands/gameobject.py +487 -0
- cli/commands/instance.py +93 -0
- cli/commands/lighting.py +123 -0
- cli/commands/material.py +239 -0
- cli/commands/prefab.py +248 -0
- cli/commands/scene.py +231 -0
- cli/commands/script.py +222 -0
- cli/commands/shader.py +226 -0
- cli/commands/texture.py +540 -0
- cli/commands/tool.py +58 -0
- cli/commands/ui.py +258 -0
- cli/commands/vfx.py +421 -0
- cli/main.py +281 -0
- cli/utils/__init__.py +31 -0
- cli/utils/config.py +58 -0
- cli/utils/confirmation.py +37 -0
- cli/utils/connection.py +254 -0
- cli/utils/constants.py +23 -0
- cli/utils/output.py +195 -0
- cli/utils/parsers.py +112 -0
- cli/utils/suggestions.py +34 -0
- core/__init__.py +0 -0
- core/config.py +67 -0
- core/constants.py +4 -0
- core/logging_decorator.py +37 -0
- core/telemetry.py +551 -0
- core/telemetry_decorator.py +164 -0
- main.py +845 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/METADATA +328 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/RECORD +105 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/WHEEL +5 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/entry_points.txt +3 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/licenses/LICENSE +21 -0
- mcpforunityserver-9.4.0b20260203025228.dist-info/top_level.txt +7 -0
- models/__init__.py +4 -0
- models/models.py +56 -0
- models/unity_response.py +70 -0
- services/__init__.py +0 -0
- services/api_key_service.py +235 -0
- services/custom_tool_service.py +499 -0
- services/registry/__init__.py +22 -0
- services/registry/resource_registry.py +53 -0
- services/registry/tool_registry.py +51 -0
- services/resources/__init__.py +86 -0
- services/resources/active_tool.py +48 -0
- services/resources/custom_tools.py +57 -0
- services/resources/editor_state.py +304 -0
- services/resources/gameobject.py +243 -0
- services/resources/layers.py +30 -0
- services/resources/menu_items.py +35 -0
- services/resources/prefab.py +191 -0
- services/resources/prefab_stage.py +40 -0
- services/resources/project_info.py +40 -0
- services/resources/selection.py +56 -0
- services/resources/tags.py +31 -0
- services/resources/tests.py +88 -0
- services/resources/unity_instances.py +125 -0
- services/resources/windows.py +48 -0
- services/state/external_changes_scanner.py +245 -0
- services/tools/__init__.py +83 -0
- services/tools/batch_execute.py +93 -0
- services/tools/debug_request_context.py +86 -0
- services/tools/execute_custom_tool.py +43 -0
- services/tools/execute_menu_item.py +32 -0
- services/tools/find_gameobjects.py +110 -0
- services/tools/find_in_file.py +181 -0
- services/tools/manage_asset.py +119 -0
- services/tools/manage_components.py +131 -0
- services/tools/manage_editor.py +64 -0
- services/tools/manage_gameobject.py +260 -0
- services/tools/manage_material.py +111 -0
- services/tools/manage_prefabs.py +209 -0
- services/tools/manage_scene.py +111 -0
- services/tools/manage_script.py +645 -0
- services/tools/manage_scriptable_object.py +87 -0
- services/tools/manage_shader.py +71 -0
- services/tools/manage_texture.py +581 -0
- services/tools/manage_vfx.py +120 -0
- services/tools/preflight.py +110 -0
- services/tools/read_console.py +151 -0
- services/tools/refresh_unity.py +153 -0
- services/tools/run_tests.py +317 -0
- services/tools/script_apply_edits.py +1006 -0
- services/tools/set_active_instance.py +120 -0
- services/tools/utils.py +348 -0
- transport/__init__.py +0 -0
- transport/legacy/port_discovery.py +329 -0
- transport/legacy/stdio_port_registry.py +65 -0
- transport/legacy/unity_connection.py +910 -0
- transport/models.py +68 -0
- transport/plugin_hub.py +787 -0
- transport/plugin_registry.py +182 -0
- transport/unity_instance_middleware.py +262 -0
- transport/unity_transport.py +94 -0
- utils/focus_nudge.py +589 -0
- utils/module_discovery.py +55 -0
cli/commands/scene.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Scene CLI commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
|
|
7
|
+
from cli.utils.config import get_config
|
|
8
|
+
from cli.utils.output import format_output, print_error, print_success
|
|
9
|
+
from cli.utils.connection import run_command, handle_unity_errors
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
def scene():
|
|
14
|
+
"""Scene operations - hierarchy, load, save, create scenes."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@scene.command("hierarchy")
|
|
19
|
+
@click.option(
|
|
20
|
+
"--parent",
|
|
21
|
+
default=None,
|
|
22
|
+
help="Parent GameObject to list children of (name, path, or instance ID)."
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--max-depth", "-d",
|
|
26
|
+
default=None,
|
|
27
|
+
type=int,
|
|
28
|
+
help="Maximum depth to traverse."
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--include-transform", "-t",
|
|
32
|
+
is_flag=True,
|
|
33
|
+
help="Include transform data for each node."
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--limit", "-l",
|
|
37
|
+
default=50,
|
|
38
|
+
type=int,
|
|
39
|
+
help="Maximum nodes to return."
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--cursor", "-c",
|
|
43
|
+
default=0,
|
|
44
|
+
type=int,
|
|
45
|
+
help="Pagination cursor."
|
|
46
|
+
)
|
|
47
|
+
@handle_unity_errors
|
|
48
|
+
def hierarchy(
|
|
49
|
+
parent: Optional[str],
|
|
50
|
+
max_depth: Optional[int],
|
|
51
|
+
include_transform: bool,
|
|
52
|
+
limit: int,
|
|
53
|
+
cursor: int,
|
|
54
|
+
):
|
|
55
|
+
"""Get the scene hierarchy.
|
|
56
|
+
|
|
57
|
+
\b
|
|
58
|
+
Examples:
|
|
59
|
+
unity-mcp scene hierarchy
|
|
60
|
+
unity-mcp scene hierarchy --max-depth 3
|
|
61
|
+
unity-mcp scene hierarchy --parent "Canvas" --include-transform
|
|
62
|
+
unity-mcp scene hierarchy --format json
|
|
63
|
+
"""
|
|
64
|
+
config = get_config()
|
|
65
|
+
|
|
66
|
+
params: dict[str, Any] = {
|
|
67
|
+
"action": "get_hierarchy",
|
|
68
|
+
"pageSize": limit,
|
|
69
|
+
"cursor": cursor,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if parent:
|
|
73
|
+
params["parent"] = parent
|
|
74
|
+
if max_depth is not None:
|
|
75
|
+
params["maxDepth"] = max_depth
|
|
76
|
+
if include_transform:
|
|
77
|
+
params["includeTransform"] = True
|
|
78
|
+
|
|
79
|
+
result = run_command("manage_scene", params, config)
|
|
80
|
+
click.echo(format_output(result, config.format))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@scene.command("active")
|
|
84
|
+
@handle_unity_errors
|
|
85
|
+
def active():
|
|
86
|
+
"""Get information about the active scene."""
|
|
87
|
+
config = get_config()
|
|
88
|
+
result = run_command("manage_scene", {"action": "get_active"}, config)
|
|
89
|
+
click.echo(format_output(result, config.format))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@scene.command("load")
|
|
93
|
+
@click.argument("scene")
|
|
94
|
+
@click.option(
|
|
95
|
+
"--by-index", "-i",
|
|
96
|
+
is_flag=True,
|
|
97
|
+
help="Load by build index instead of path/name."
|
|
98
|
+
)
|
|
99
|
+
@handle_unity_errors
|
|
100
|
+
def load(scene: str, by_index: bool):
|
|
101
|
+
"""Load a scene.
|
|
102
|
+
|
|
103
|
+
\b
|
|
104
|
+
Examples:
|
|
105
|
+
unity-mcp scene load "Assets/Scenes/Main.unity"
|
|
106
|
+
unity-mcp scene load "MainScene"
|
|
107
|
+
unity-mcp scene load 0 --by-index
|
|
108
|
+
"""
|
|
109
|
+
config = get_config()
|
|
110
|
+
|
|
111
|
+
params: dict[str, Any] = {"action": "load"}
|
|
112
|
+
|
|
113
|
+
if by_index:
|
|
114
|
+
try:
|
|
115
|
+
params["buildIndex"] = int(scene)
|
|
116
|
+
except ValueError:
|
|
117
|
+
print_error(f"Invalid build index: {scene}")
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
else:
|
|
120
|
+
if scene.endswith(".unity"):
|
|
121
|
+
params["path"] = scene
|
|
122
|
+
else:
|
|
123
|
+
params["name"] = scene
|
|
124
|
+
|
|
125
|
+
result = run_command("manage_scene", params, config)
|
|
126
|
+
click.echo(format_output(result, config.format))
|
|
127
|
+
if result.get("success"):
|
|
128
|
+
print_success(f"Loaded scene: {scene}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@scene.command("save")
|
|
132
|
+
@click.option(
|
|
133
|
+
"--path",
|
|
134
|
+
default=None,
|
|
135
|
+
help="Path to save the scene to (for new scenes)."
|
|
136
|
+
)
|
|
137
|
+
@handle_unity_errors
|
|
138
|
+
def save(path: Optional[str]):
|
|
139
|
+
"""Save the current scene.
|
|
140
|
+
|
|
141
|
+
\b
|
|
142
|
+
Examples:
|
|
143
|
+
unity-mcp scene save
|
|
144
|
+
unity-mcp scene save --path "Assets/Scenes/NewScene.unity"
|
|
145
|
+
"""
|
|
146
|
+
config = get_config()
|
|
147
|
+
|
|
148
|
+
params: dict[str, Any] = {"action": "save"}
|
|
149
|
+
if path:
|
|
150
|
+
params["path"] = path
|
|
151
|
+
|
|
152
|
+
result = run_command("manage_scene", params, config)
|
|
153
|
+
click.echo(format_output(result, config.format))
|
|
154
|
+
if result.get("success"):
|
|
155
|
+
print_success("Scene saved")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@scene.command("create")
|
|
159
|
+
@click.argument("name")
|
|
160
|
+
@click.option(
|
|
161
|
+
"--path",
|
|
162
|
+
default=None,
|
|
163
|
+
help="Path to create the scene at."
|
|
164
|
+
)
|
|
165
|
+
@handle_unity_errors
|
|
166
|
+
def create(name: str, path: Optional[str]):
|
|
167
|
+
"""Create a new scene.
|
|
168
|
+
|
|
169
|
+
\b
|
|
170
|
+
Examples:
|
|
171
|
+
unity-mcp scene create "NewLevel"
|
|
172
|
+
unity-mcp scene create "TestScene" --path "Assets/Scenes/Test"
|
|
173
|
+
"""
|
|
174
|
+
config = get_config()
|
|
175
|
+
|
|
176
|
+
params: dict[str, Any] = {
|
|
177
|
+
"action": "create",
|
|
178
|
+
"name": name,
|
|
179
|
+
}
|
|
180
|
+
if path:
|
|
181
|
+
params["path"] = path
|
|
182
|
+
|
|
183
|
+
result = run_command("manage_scene", params, config)
|
|
184
|
+
click.echo(format_output(result, config.format))
|
|
185
|
+
if result.get("success"):
|
|
186
|
+
print_success(f"Created scene: {name}")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@scene.command("build-settings")
|
|
190
|
+
@handle_unity_errors
|
|
191
|
+
def build_settings():
|
|
192
|
+
"""Get scenes in build settings."""
|
|
193
|
+
config = get_config()
|
|
194
|
+
result = run_command("manage_scene", {"action": "get_build_settings"}, config)
|
|
195
|
+
click.echo(format_output(result, config.format))
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@scene.command("screenshot")
|
|
199
|
+
@click.option(
|
|
200
|
+
"--filename", "-f",
|
|
201
|
+
default=None,
|
|
202
|
+
help="Output filename (default: timestamp)."
|
|
203
|
+
)
|
|
204
|
+
@click.option(
|
|
205
|
+
"--supersize", "-s",
|
|
206
|
+
default=1,
|
|
207
|
+
type=int,
|
|
208
|
+
help="Supersize multiplier (1-4)."
|
|
209
|
+
)
|
|
210
|
+
@handle_unity_errors
|
|
211
|
+
def screenshot(filename: Optional[str], supersize: int):
|
|
212
|
+
"""Capture a screenshot of the scene.
|
|
213
|
+
|
|
214
|
+
\b
|
|
215
|
+
Examples:
|
|
216
|
+
unity-mcp scene screenshot
|
|
217
|
+
unity-mcp scene screenshot --filename "level_preview"
|
|
218
|
+
unity-mcp scene screenshot --supersize 2
|
|
219
|
+
"""
|
|
220
|
+
config = get_config()
|
|
221
|
+
|
|
222
|
+
params: dict[str, Any] = {"action": "screenshot"}
|
|
223
|
+
if filename:
|
|
224
|
+
params["fileName"] = filename
|
|
225
|
+
if supersize > 1:
|
|
226
|
+
params["superSize"] = supersize
|
|
227
|
+
|
|
228
|
+
result = run_command("manage_scene", params, config)
|
|
229
|
+
click.echo(format_output(result, config.format))
|
|
230
|
+
if result.get("success"):
|
|
231
|
+
print_success("Screenshot captured")
|
cli/commands/script.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Script CLI commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import click
|
|
6
|
+
from typing import Optional, Any
|
|
7
|
+
|
|
8
|
+
from cli.utils.config import get_config
|
|
9
|
+
from cli.utils.output import format_output, print_error, print_success
|
|
10
|
+
from cli.utils.connection import run_command, handle_unity_errors
|
|
11
|
+
from cli.utils.parsers import parse_json_list_or_exit
|
|
12
|
+
from cli.utils.confirmation import confirm_destructive_action
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
def script():
|
|
17
|
+
"""Script operations - create, read, edit C# scripts."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@script.command("create")
|
|
22
|
+
@click.argument("name")
|
|
23
|
+
@click.option(
|
|
24
|
+
"--path", "-p",
|
|
25
|
+
default="Assets/Scripts",
|
|
26
|
+
help="Directory to create the script in."
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--type", "-t",
|
|
30
|
+
"script_type",
|
|
31
|
+
type=click.Choice(["MonoBehaviour", "ScriptableObject",
|
|
32
|
+
"Editor", "EditorWindow", "Plain"]),
|
|
33
|
+
default="MonoBehaviour",
|
|
34
|
+
help="Type of script to create."
|
|
35
|
+
)
|
|
36
|
+
@click.option(
|
|
37
|
+
"--namespace", "-n",
|
|
38
|
+
default=None,
|
|
39
|
+
help="Namespace for the script."
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--contents", "-c",
|
|
43
|
+
default=None,
|
|
44
|
+
help="Full script contents (overrides template)."
|
|
45
|
+
)
|
|
46
|
+
@handle_unity_errors
|
|
47
|
+
def create(name: str, path: str, script_type: str, namespace: Optional[str], contents: Optional[str]):
|
|
48
|
+
"""Create a new C# script.
|
|
49
|
+
|
|
50
|
+
\b
|
|
51
|
+
Examples:
|
|
52
|
+
unity-mcp script create "PlayerController"
|
|
53
|
+
unity-mcp script create "GameManager" --path "Assets/Scripts/Managers"
|
|
54
|
+
unity-mcp script create "EnemyData" --type ScriptableObject
|
|
55
|
+
unity-mcp script create "CustomEditor" --type Editor --namespace "MyGame.Editor"
|
|
56
|
+
"""
|
|
57
|
+
config = get_config()
|
|
58
|
+
|
|
59
|
+
params: dict[str, Any] = {
|
|
60
|
+
"action": "create",
|
|
61
|
+
"name": name,
|
|
62
|
+
"path": path,
|
|
63
|
+
"scriptType": script_type,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if namespace:
|
|
67
|
+
params["namespace"] = namespace
|
|
68
|
+
if contents:
|
|
69
|
+
params["contents"] = contents
|
|
70
|
+
|
|
71
|
+
result = run_command("manage_script", params, config)
|
|
72
|
+
click.echo(format_output(result, config.format))
|
|
73
|
+
if result.get("success"):
|
|
74
|
+
print_success(f"Created script: {name}.cs")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@script.command("read")
|
|
78
|
+
@click.argument("path")
|
|
79
|
+
@click.option(
|
|
80
|
+
"--start-line", "-s",
|
|
81
|
+
default=None,
|
|
82
|
+
type=int,
|
|
83
|
+
help="Starting line number (1-based)."
|
|
84
|
+
)
|
|
85
|
+
@click.option(
|
|
86
|
+
"--line-count", "-n",
|
|
87
|
+
default=None,
|
|
88
|
+
type=int,
|
|
89
|
+
help="Number of lines to read."
|
|
90
|
+
)
|
|
91
|
+
@handle_unity_errors
|
|
92
|
+
def read(path: str, start_line: Optional[int], line_count: Optional[int]):
|
|
93
|
+
"""Read a C# script file.
|
|
94
|
+
|
|
95
|
+
\b
|
|
96
|
+
Examples:
|
|
97
|
+
unity-mcp script read "Assets/Scripts/Player.cs"
|
|
98
|
+
unity-mcp script read "Assets/Scripts/Player.cs" --start-line 10 --line-count 20
|
|
99
|
+
"""
|
|
100
|
+
config = get_config()
|
|
101
|
+
|
|
102
|
+
parts = path.rsplit("/", 1)
|
|
103
|
+
filename = parts[-1]
|
|
104
|
+
directory = parts[0] if len(parts) > 1 else "Assets"
|
|
105
|
+
name = filename[:-3] if filename.endswith(".cs") else filename
|
|
106
|
+
|
|
107
|
+
params: dict[str, Any] = {
|
|
108
|
+
"action": "read",
|
|
109
|
+
"name": name,
|
|
110
|
+
"path": directory,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if start_line:
|
|
114
|
+
params["startLine"] = start_line
|
|
115
|
+
if line_count:
|
|
116
|
+
params["lineCount"] = line_count
|
|
117
|
+
|
|
118
|
+
result = run_command("manage_script", params, config)
|
|
119
|
+
# For read, just output the content directly
|
|
120
|
+
if result.get("success") and result.get("data"):
|
|
121
|
+
data = result.get("data", {})
|
|
122
|
+
if isinstance(data, dict) and "contents" in data:
|
|
123
|
+
click.echo(data["contents"])
|
|
124
|
+
else:
|
|
125
|
+
click.echo(format_output(result, config.format))
|
|
126
|
+
else:
|
|
127
|
+
click.echo(format_output(result, config.format))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@script.command("delete")
|
|
131
|
+
@click.argument("path")
|
|
132
|
+
@click.option(
|
|
133
|
+
"--force", "-f",
|
|
134
|
+
is_flag=True,
|
|
135
|
+
help="Skip confirmation prompt."
|
|
136
|
+
)
|
|
137
|
+
@handle_unity_errors
|
|
138
|
+
def delete(path: str, force: bool):
|
|
139
|
+
"""Delete a C# script.
|
|
140
|
+
|
|
141
|
+
\b
|
|
142
|
+
Examples:
|
|
143
|
+
unity-mcp script delete "Assets/Scripts/OldScript.cs"
|
|
144
|
+
"""
|
|
145
|
+
config = get_config()
|
|
146
|
+
|
|
147
|
+
confirm_destructive_action("Delete", "script", path, force)
|
|
148
|
+
|
|
149
|
+
parts = path.rsplit("/", 1)
|
|
150
|
+
filename = parts[-1]
|
|
151
|
+
directory = parts[0] if len(parts) > 1 else "Assets"
|
|
152
|
+
name = filename[:-3] if filename.endswith(".cs") else filename
|
|
153
|
+
|
|
154
|
+
params: dict[str, Any] = {
|
|
155
|
+
"action": "delete",
|
|
156
|
+
"name": name,
|
|
157
|
+
"path": directory,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
result = run_command("manage_script", params, config)
|
|
161
|
+
click.echo(format_output(result, config.format))
|
|
162
|
+
if result.get("success"):
|
|
163
|
+
print_success(f"Deleted: {path}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@script.command("edit")
|
|
167
|
+
@click.argument("path")
|
|
168
|
+
@click.option(
|
|
169
|
+
"--edits", "-e",
|
|
170
|
+
required=True,
|
|
171
|
+
help='Edits as JSON array of {startLine, startCol, endLine, endCol, newText}.'
|
|
172
|
+
)
|
|
173
|
+
@handle_unity_errors
|
|
174
|
+
def edit(path: str, edits: str):
|
|
175
|
+
"""Apply text edits to a script.
|
|
176
|
+
|
|
177
|
+
\b
|
|
178
|
+
Examples:
|
|
179
|
+
unity-mcp script edit "Assets/Scripts/Player.cs" --edits '[{"startLine": 10, "startCol": 1, "endLine": 10, "endCol": 20, "newText": "// Modified"}]'
|
|
180
|
+
"""
|
|
181
|
+
config = get_config()
|
|
182
|
+
|
|
183
|
+
edits_list = parse_json_list_or_exit(edits, "edits")
|
|
184
|
+
|
|
185
|
+
params: dict[str, Any] = {
|
|
186
|
+
"uri": path,
|
|
187
|
+
"edits": edits_list,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
result = run_command("apply_text_edits", params, config)
|
|
191
|
+
click.echo(format_output(result, config.format))
|
|
192
|
+
if result.get("success"):
|
|
193
|
+
print_success(f"Applied edits to: {path}")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@script.command("validate")
|
|
197
|
+
@click.argument("path")
|
|
198
|
+
@click.option(
|
|
199
|
+
"--level", "-l",
|
|
200
|
+
type=click.Choice(["basic", "standard"]),
|
|
201
|
+
default="basic",
|
|
202
|
+
help="Validation level."
|
|
203
|
+
)
|
|
204
|
+
@handle_unity_errors
|
|
205
|
+
def validate(path: str, level: str):
|
|
206
|
+
"""Validate a C# script for errors.
|
|
207
|
+
|
|
208
|
+
\b
|
|
209
|
+
Examples:
|
|
210
|
+
unity-mcp script validate "Assets/Scripts/Player.cs"
|
|
211
|
+
unity-mcp script validate "Assets/Scripts/Player.cs" --level standard
|
|
212
|
+
"""
|
|
213
|
+
config = get_config()
|
|
214
|
+
|
|
215
|
+
params: dict[str, Any] = {
|
|
216
|
+
"uri": path,
|
|
217
|
+
"level": level,
|
|
218
|
+
"include_diagnostics": True,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
result = run_command("validate_script", params, config)
|
|
222
|
+
click.echo(format_output(result, config.format))
|
cli/commands/shader.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Shader CLI commands for managing Unity shaders."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from cli.utils.config import get_config
|
|
8
|
+
from cli.utils.output import format_output, print_error, print_success
|
|
9
|
+
from cli.utils.connection import run_command, handle_unity_errors
|
|
10
|
+
from cli.utils.confirmation import confirm_destructive_action
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def shader():
|
|
15
|
+
"""Shader operations - create, read, update, delete shaders."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@shader.command("read")
|
|
20
|
+
@click.argument("path")
|
|
21
|
+
@handle_unity_errors
|
|
22
|
+
def read_shader(path: str):
|
|
23
|
+
"""Read a shader file.
|
|
24
|
+
|
|
25
|
+
\\b
|
|
26
|
+
Examples:
|
|
27
|
+
unity-mcp shader read "Assets/Shaders/MyShader.shader"
|
|
28
|
+
"""
|
|
29
|
+
config = get_config()
|
|
30
|
+
|
|
31
|
+
# Extract name from path
|
|
32
|
+
import os
|
|
33
|
+
name = os.path.splitext(os.path.basename(path))[0]
|
|
34
|
+
directory = os.path.dirname(path)
|
|
35
|
+
|
|
36
|
+
result = run_command("manage_shader", {
|
|
37
|
+
"action": "read",
|
|
38
|
+
"name": name,
|
|
39
|
+
"path": directory or "Assets/",
|
|
40
|
+
}, config)
|
|
41
|
+
|
|
42
|
+
# If successful, display the contents nicely
|
|
43
|
+
if result.get("success") and result.get("data", {}).get("contents"):
|
|
44
|
+
click.echo(result["data"]["contents"])
|
|
45
|
+
else:
|
|
46
|
+
click.echo(format_output(result, config.format))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@shader.command("create")
|
|
50
|
+
@click.argument("name")
|
|
51
|
+
@click.option(
|
|
52
|
+
"--path", "-p",
|
|
53
|
+
default="Assets/Shaders",
|
|
54
|
+
help="Directory to create shader in."
|
|
55
|
+
)
|
|
56
|
+
@click.option(
|
|
57
|
+
"--contents", "-c",
|
|
58
|
+
default=None,
|
|
59
|
+
help="Shader code (reads from stdin if not provided)."
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--file", "-f",
|
|
63
|
+
"file_path",
|
|
64
|
+
default=None,
|
|
65
|
+
type=click.Path(exists=True),
|
|
66
|
+
help="Read shader code from file."
|
|
67
|
+
)
|
|
68
|
+
@handle_unity_errors
|
|
69
|
+
def create_shader(name: str, path: str, contents: Optional[str], file_path: Optional[str]):
|
|
70
|
+
"""Create a new shader.
|
|
71
|
+
|
|
72
|
+
\\b
|
|
73
|
+
Examples:
|
|
74
|
+
unity-mcp shader create "MyShader" --path "Assets/Shaders"
|
|
75
|
+
unity-mcp shader create "MyShader" --file local_shader.shader
|
|
76
|
+
echo "Shader code..." | unity-mcp shader create "MyShader"
|
|
77
|
+
"""
|
|
78
|
+
config = get_config()
|
|
79
|
+
|
|
80
|
+
# Get contents from file, option, or stdin
|
|
81
|
+
if file_path:
|
|
82
|
+
with open(file_path, 'r') as f:
|
|
83
|
+
shader_contents = f.read()
|
|
84
|
+
elif contents:
|
|
85
|
+
shader_contents = contents
|
|
86
|
+
else:
|
|
87
|
+
# Read from stdin if available
|
|
88
|
+
import sys
|
|
89
|
+
if not sys.stdin.isatty():
|
|
90
|
+
shader_contents = sys.stdin.read()
|
|
91
|
+
else:
|
|
92
|
+
# Provide default shader template
|
|
93
|
+
shader_contents = f'''Shader "Custom/{name}"
|
|
94
|
+
{{
|
|
95
|
+
Properties
|
|
96
|
+
{{
|
|
97
|
+
_Color ("Color", Color) = (1,1,1,1)
|
|
98
|
+
_MainTex ("Albedo (RGB)", 2D) = "white" {{}}
|
|
99
|
+
}}
|
|
100
|
+
SubShader
|
|
101
|
+
{{
|
|
102
|
+
Tags {{ "RenderType"="Opaque" }}
|
|
103
|
+
LOD 200
|
|
104
|
+
|
|
105
|
+
CGPROGRAM
|
|
106
|
+
#pragma surface surf Standard fullforwardshadows
|
|
107
|
+
#pragma target 3.0
|
|
108
|
+
|
|
109
|
+
sampler2D _MainTex;
|
|
110
|
+
fixed4 _Color;
|
|
111
|
+
|
|
112
|
+
struct Input
|
|
113
|
+
{{
|
|
114
|
+
float2 uv_MainTex;
|
|
115
|
+
}};
|
|
116
|
+
|
|
117
|
+
void surf (Input IN, inout SurfaceOutputStandard o)
|
|
118
|
+
{{
|
|
119
|
+
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
|
|
120
|
+
o.Albedo = c.rgb;
|
|
121
|
+
o.Alpha = c.a;
|
|
122
|
+
}}
|
|
123
|
+
ENDCG
|
|
124
|
+
}}
|
|
125
|
+
FallBack "Diffuse"
|
|
126
|
+
}}
|
|
127
|
+
'''
|
|
128
|
+
|
|
129
|
+
result = run_command("manage_shader", {
|
|
130
|
+
"action": "create",
|
|
131
|
+
"name": name,
|
|
132
|
+
"path": path,
|
|
133
|
+
"contents": shader_contents,
|
|
134
|
+
}, config)
|
|
135
|
+
click.echo(format_output(result, config.format))
|
|
136
|
+
if result.get("success"):
|
|
137
|
+
print_success(f"Created shader: {path}/{name}.shader")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@shader.command("update")
|
|
141
|
+
@click.argument("path")
|
|
142
|
+
@click.option(
|
|
143
|
+
"--contents", "-c",
|
|
144
|
+
default=None,
|
|
145
|
+
help="New shader code."
|
|
146
|
+
)
|
|
147
|
+
@click.option(
|
|
148
|
+
"--file", "-f",
|
|
149
|
+
"file_path",
|
|
150
|
+
default=None,
|
|
151
|
+
type=click.Path(exists=True),
|
|
152
|
+
help="Read shader code from file."
|
|
153
|
+
)
|
|
154
|
+
@handle_unity_errors
|
|
155
|
+
def update_shader(path: str, contents: Optional[str], file_path: Optional[str]):
|
|
156
|
+
"""Update an existing shader.
|
|
157
|
+
|
|
158
|
+
\\b
|
|
159
|
+
Examples:
|
|
160
|
+
unity-mcp shader update "Assets/Shaders/MyShader.shader" --file updated.shader
|
|
161
|
+
echo "New shader code" | unity-mcp shader update "Assets/Shaders/MyShader.shader"
|
|
162
|
+
"""
|
|
163
|
+
config = get_config()
|
|
164
|
+
|
|
165
|
+
import os
|
|
166
|
+
name = os.path.splitext(os.path.basename(path))[0]
|
|
167
|
+
directory = os.path.dirname(path)
|
|
168
|
+
|
|
169
|
+
# Get contents from file, option, or stdin
|
|
170
|
+
if file_path:
|
|
171
|
+
with open(file_path, 'r') as f:
|
|
172
|
+
shader_contents = f.read()
|
|
173
|
+
elif contents:
|
|
174
|
+
shader_contents = contents
|
|
175
|
+
else:
|
|
176
|
+
import sys
|
|
177
|
+
if not sys.stdin.isatty():
|
|
178
|
+
shader_contents = sys.stdin.read()
|
|
179
|
+
else:
|
|
180
|
+
print_error(
|
|
181
|
+
"No shader contents provided. Use --contents, --file, or pipe via stdin.")
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
|
|
184
|
+
result = run_command("manage_shader", {
|
|
185
|
+
"action": "update",
|
|
186
|
+
"name": name,
|
|
187
|
+
"path": directory or "Assets/",
|
|
188
|
+
"contents": shader_contents,
|
|
189
|
+
}, config)
|
|
190
|
+
click.echo(format_output(result, config.format))
|
|
191
|
+
if result.get("success"):
|
|
192
|
+
print_success(f"Updated shader: {path}")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@shader.command("delete")
|
|
196
|
+
@click.argument("path")
|
|
197
|
+
@click.option(
|
|
198
|
+
"--force", "-f",
|
|
199
|
+
is_flag=True,
|
|
200
|
+
help="Skip confirmation prompt."
|
|
201
|
+
)
|
|
202
|
+
@handle_unity_errors
|
|
203
|
+
def delete_shader(path: str, force: bool):
|
|
204
|
+
"""Delete a shader.
|
|
205
|
+
|
|
206
|
+
\\b
|
|
207
|
+
Examples:
|
|
208
|
+
unity-mcp shader delete "Assets/Shaders/OldShader.shader"
|
|
209
|
+
unity-mcp shader delete "Assets/Shaders/OldShader.shader" --force
|
|
210
|
+
"""
|
|
211
|
+
config = get_config()
|
|
212
|
+
|
|
213
|
+
confirm_destructive_action("Delete", "shader", path, force)
|
|
214
|
+
|
|
215
|
+
import os
|
|
216
|
+
name = os.path.splitext(os.path.basename(path))[0]
|
|
217
|
+
directory = os.path.dirname(path)
|
|
218
|
+
|
|
219
|
+
result = run_command("manage_shader", {
|
|
220
|
+
"action": "delete",
|
|
221
|
+
"name": name,
|
|
222
|
+
"path": directory or "Assets/",
|
|
223
|
+
}, config)
|
|
224
|
+
click.echo(format_output(result, config.format))
|
|
225
|
+
if result.get("success"):
|
|
226
|
+
print_success(f"Deleted shader: {path}")
|