mcpforunityserver 9.3.0b20260128055651__py3-none-any.whl → 9.3.0b20260129121506__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.
Files changed (61) hide show
  1. cli/commands/animation.py +6 -9
  2. cli/commands/asset.py +50 -80
  3. cli/commands/audio.py +14 -22
  4. cli/commands/batch.py +20 -33
  5. cli/commands/code.py +63 -70
  6. cli/commands/component.py +33 -55
  7. cli/commands/editor.py +122 -188
  8. cli/commands/gameobject.py +60 -83
  9. cli/commands/instance.py +28 -36
  10. cli/commands/lighting.py +54 -59
  11. cli/commands/material.py +39 -68
  12. cli/commands/prefab.py +63 -81
  13. cli/commands/scene.py +30 -54
  14. cli/commands/script.py +32 -50
  15. cli/commands/shader.py +43 -55
  16. cli/commands/texture.py +53 -51
  17. cli/commands/tool.py +24 -27
  18. cli/commands/ui.py +125 -130
  19. cli/commands/vfx.py +84 -138
  20. cli/utils/confirmation.py +37 -0
  21. cli/utils/connection.py +32 -2
  22. cli/utils/constants.py +23 -0
  23. cli/utils/parsers.py +112 -0
  24. core/config.py +0 -4
  25. core/telemetry.py +20 -2
  26. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/METADATA +21 -1
  27. mcpforunityserver-9.3.0b20260129121506.dist-info/RECORD +103 -0
  28. services/resources/active_tool.py +1 -1
  29. services/resources/custom_tools.py +1 -1
  30. services/resources/editor_state.py +1 -1
  31. services/resources/gameobject.py +4 -4
  32. services/resources/layers.py +1 -1
  33. services/resources/menu_items.py +1 -1
  34. services/resources/prefab.py +3 -3
  35. services/resources/prefab_stage.py +1 -1
  36. services/resources/project_info.py +1 -1
  37. services/resources/selection.py +1 -1
  38. services/resources/tags.py +1 -1
  39. services/resources/tests.py +40 -8
  40. services/resources/unity_instances.py +1 -1
  41. services/resources/windows.py +1 -1
  42. services/tools/__init__.py +3 -1
  43. services/tools/find_gameobjects.py +32 -11
  44. services/tools/manage_gameobject.py +11 -66
  45. services/tools/manage_material.py +4 -37
  46. services/tools/manage_prefabs.py +51 -7
  47. services/tools/manage_script.py +1 -1
  48. services/tools/manage_texture.py +10 -96
  49. services/tools/run_tests.py +67 -4
  50. services/tools/utils.py +217 -0
  51. transport/models.py +1 -0
  52. transport/plugin_hub.py +2 -1
  53. transport/plugin_registry.py +3 -0
  54. transport/unity_transport.py +0 -51
  55. utils/focus_nudge.py +291 -23
  56. mcpforunityserver-9.3.0b20260128055651.dist-info/RECORD +0 -101
  57. utils/reload_sentinel.py +0 -9
  58. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/WHEEL +0 -0
  59. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/entry_points.txt +0 -0
  60. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/licenses/LICENSE +0 -0
  61. {mcpforunityserver-9.3.0b20260128055651.dist-info → mcpforunityserver-9.3.0b20260129121506.dist-info}/top_level.txt +0 -0
cli/commands/script.py CHANGED
@@ -7,7 +7,9 @@ from typing import Optional, Any
7
7
 
8
8
  from cli.utils.config import get_config
9
9
  from cli.utils.output import format_output, print_error, print_success
10
- from cli.utils.connection import run_command, UnityConnectionError
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
11
13
 
12
14
 
13
15
  @click.group()
@@ -41,6 +43,7 @@ def script():
41
43
  default=None,
42
44
  help="Full script contents (overrides template)."
43
45
  )
46
+ @handle_unity_errors
44
47
  def create(name: str, path: str, script_type: str, namespace: Optional[str], contents: Optional[str]):
45
48
  """Create a new C# script.
46
49
 
@@ -65,14 +68,10 @@ def create(name: str, path: str, script_type: str, namespace: Optional[str], con
65
68
  if contents:
66
69
  params["contents"] = contents
67
70
 
68
- try:
69
- result = run_command("manage_script", params, config)
70
- click.echo(format_output(result, config.format))
71
- if result.get("success"):
72
- print_success(f"Created script: {name}.cs")
73
- except UnityConnectionError as e:
74
- print_error(str(e))
75
- sys.exit(1)
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")
76
75
 
77
76
 
78
77
  @script.command("read")
@@ -89,6 +88,7 @@ def create(name: str, path: str, script_type: str, namespace: Optional[str], con
89
88
  type=int,
90
89
  help="Number of lines to read."
91
90
  )
91
+ @handle_unity_errors
92
92
  def read(path: str, start_line: Optional[int], line_count: Optional[int]):
93
93
  """Read a C# script file.
94
94
 
@@ -115,20 +115,16 @@ def read(path: str, start_line: Optional[int], line_count: Optional[int]):
115
115
  if line_count:
116
116
  params["lineCount"] = line_count
117
117
 
118
- try:
119
- result = run_command("manage_script", params, config)
120
- # For read, just output the content directly
121
- if result.get("success") and result.get("data"):
122
- data = result.get("data", {})
123
- if isinstance(data, dict) and "contents" in data:
124
- click.echo(data["contents"])
125
- else:
126
- click.echo(format_output(result, config.format))
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"])
127
124
  else:
128
125
  click.echo(format_output(result, config.format))
129
- except UnityConnectionError as e:
130
- print_error(str(e))
131
- sys.exit(1)
126
+ else:
127
+ click.echo(format_output(result, config.format))
132
128
 
133
129
 
134
130
  @script.command("delete")
@@ -138,6 +134,7 @@ def read(path: str, start_line: Optional[int], line_count: Optional[int]):
138
134
  is_flag=True,
139
135
  help="Skip confirmation prompt."
140
136
  )
137
+ @handle_unity_errors
141
138
  def delete(path: str, force: bool):
142
139
  """Delete a C# script.
143
140
 
@@ -147,8 +144,7 @@ def delete(path: str, force: bool):
147
144
  """
148
145
  config = get_config()
149
146
 
150
- if not force:
151
- click.confirm(f"Delete script '{path}'?", abort=True)
147
+ confirm_destructive_action("Delete", "script", path, force)
152
148
 
153
149
  parts = path.rsplit("/", 1)
154
150
  filename = parts[-1]
@@ -161,14 +157,10 @@ def delete(path: str, force: bool):
161
157
  "path": directory,
162
158
  }
163
159
 
164
- try:
165
- result = run_command("manage_script", params, config)
166
- click.echo(format_output(result, config.format))
167
- if result.get("success"):
168
- print_success(f"Deleted: {path}")
169
- except UnityConnectionError as e:
170
- print_error(str(e))
171
- sys.exit(1)
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}")
172
164
 
173
165
 
174
166
  @script.command("edit")
@@ -178,6 +170,7 @@ def delete(path: str, force: bool):
178
170
  required=True,
179
171
  help='Edits as JSON array of {startLine, startCol, endLine, endCol, newText}.'
180
172
  )
173
+ @handle_unity_errors
181
174
  def edit(path: str, edits: str):
182
175
  """Apply text edits to a script.
183
176
 
@@ -187,25 +180,17 @@ def edit(path: str, edits: str):
187
180
  """
188
181
  config = get_config()
189
182
 
190
- try:
191
- edits_list = json.loads(edits)
192
- except json.JSONDecodeError as e:
193
- print_error(f"Invalid JSON for edits: {e}")
194
- sys.exit(1)
183
+ edits_list = parse_json_list_or_exit(edits, "edits")
195
184
 
196
185
  params: dict[str, Any] = {
197
186
  "uri": path,
198
187
  "edits": edits_list,
199
188
  }
200
189
 
201
- try:
202
- result = run_command("apply_text_edits", params, config)
203
- click.echo(format_output(result, config.format))
204
- if result.get("success"):
205
- print_success(f"Applied edits to: {path}")
206
- except UnityConnectionError as e:
207
- print_error(str(e))
208
- sys.exit(1)
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}")
209
194
 
210
195
 
211
196
  @script.command("validate")
@@ -216,6 +201,7 @@ def edit(path: str, edits: str):
216
201
  default="basic",
217
202
  help="Validation level."
218
203
  )
204
+ @handle_unity_errors
219
205
  def validate(path: str, level: str):
220
206
  """Validate a C# script for errors.
221
207
 
@@ -232,9 +218,5 @@ def validate(path: str, level: str):
232
218
  "include_diagnostics": True,
233
219
  }
234
220
 
235
- try:
236
- result = run_command("validate_script", params, config)
237
- click.echo(format_output(result, config.format))
238
- except UnityConnectionError as e:
239
- print_error(str(e))
240
- sys.exit(1)
221
+ result = run_command("validate_script", params, config)
222
+ click.echo(format_output(result, config.format))
cli/commands/shader.py CHANGED
@@ -6,7 +6,8 @@ from typing import Optional
6
6
 
7
7
  from cli.utils.config import get_config
8
8
  from cli.utils.output import format_output, print_error, print_success
9
- from cli.utils.connection import run_command, UnityConnectionError
9
+ from cli.utils.connection import run_command, handle_unity_errors
10
+ from cli.utils.confirmation import confirm_destructive_action
10
11
 
11
12
 
12
13
  @click.group()
@@ -17,6 +18,7 @@ def shader():
17
18
 
18
19
  @shader.command("read")
19
20
  @click.argument("path")
21
+ @handle_unity_errors
20
22
  def read_shader(path: str):
21
23
  """Read a shader file.
22
24
 
@@ -31,21 +33,17 @@ def read_shader(path: str):
31
33
  name = os.path.splitext(os.path.basename(path))[0]
32
34
  directory = os.path.dirname(path)
33
35
 
34
- try:
35
- result = run_command("manage_shader", {
36
- "action": "read",
37
- "name": name,
38
- "path": directory or "Assets/",
39
- }, config)
36
+ result = run_command("manage_shader", {
37
+ "action": "read",
38
+ "name": name,
39
+ "path": directory or "Assets/",
40
+ }, config)
40
41
 
41
- # If successful, display the contents nicely
42
- if result.get("success") and result.get("data", {}).get("contents"):
43
- click.echo(result["data"]["contents"])
44
- else:
45
- click.echo(format_output(result, config.format))
46
- except UnityConnectionError as e:
47
- print_error(str(e))
48
- sys.exit(1)
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))
49
47
 
50
48
 
51
49
  @shader.command("create")
@@ -67,6 +65,7 @@ def read_shader(path: str):
67
65
  type=click.Path(exists=True),
68
66
  help="Read shader code from file."
69
67
  )
68
+ @handle_unity_errors
70
69
  def create_shader(name: str, path: str, contents: Optional[str], file_path: Optional[str]):
71
70
  """Create a new shader.
72
71
 
@@ -127,19 +126,15 @@ def create_shader(name: str, path: str, contents: Optional[str], file_path: Opti
127
126
  }}
128
127
  '''
129
128
 
130
- try:
131
- result = run_command("manage_shader", {
132
- "action": "create",
133
- "name": name,
134
- "path": path,
135
- "contents": shader_contents,
136
- }, config)
137
- click.echo(format_output(result, config.format))
138
- if result.get("success"):
139
- print_success(f"Created shader: {path}/{name}.shader")
140
- except UnityConnectionError as e:
141
- print_error(str(e))
142
- sys.exit(1)
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")
143
138
 
144
139
 
145
140
  @shader.command("update")
@@ -156,6 +151,7 @@ def create_shader(name: str, path: str, contents: Optional[str], file_path: Opti
156
151
  type=click.Path(exists=True),
157
152
  help="Read shader code from file."
158
153
  )
154
+ @handle_unity_errors
159
155
  def update_shader(path: str, contents: Optional[str], file_path: Optional[str]):
160
156
  """Update an existing shader.
161
157
 
@@ -185,19 +181,15 @@ def update_shader(path: str, contents: Optional[str], file_path: Optional[str]):
185
181
  "No shader contents provided. Use --contents, --file, or pipe via stdin.")
186
182
  sys.exit(1)
187
183
 
188
- try:
189
- result = run_command("manage_shader", {
190
- "action": "update",
191
- "name": name,
192
- "path": directory or "Assets/",
193
- "contents": shader_contents,
194
- }, config)
195
- click.echo(format_output(result, config.format))
196
- if result.get("success"):
197
- print_success(f"Updated shader: {path}")
198
- except UnityConnectionError as e:
199
- print_error(str(e))
200
- sys.exit(1)
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}")
201
193
 
202
194
 
203
195
  @shader.command("delete")
@@ -207,6 +199,7 @@ def update_shader(path: str, contents: Optional[str], file_path: Optional[str]):
207
199
  is_flag=True,
208
200
  help="Skip confirmation prompt."
209
201
  )
202
+ @handle_unity_errors
210
203
  def delete_shader(path: str, force: bool):
211
204
  """Delete a shader.
212
205
 
@@ -217,22 +210,17 @@ def delete_shader(path: str, force: bool):
217
210
  """
218
211
  config = get_config()
219
212
 
220
- if not force:
221
- click.confirm(f"Delete shader '{path}'?", abort=True)
213
+ confirm_destructive_action("Delete", "shader", path, force)
222
214
 
223
215
  import os
224
216
  name = os.path.splitext(os.path.basename(path))[0]
225
217
  directory = os.path.dirname(path)
226
218
 
227
- try:
228
- result = run_command("manage_shader", {
229
- "action": "delete",
230
- "name": name,
231
- "path": directory or "Assets/",
232
- }, config)
233
- click.echo(format_output(result, config.format))
234
- if result.get("success"):
235
- print_success(f"Deleted shader: {path}")
236
- except UnityConnectionError as e:
237
- print_error(str(e))
238
- sys.exit(1)
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}")
cli/commands/texture.py CHANGED
@@ -1,28 +1,13 @@
1
1
  """Texture CLI commands."""
2
2
 
3
3
  import sys
4
- import json
5
4
  import click
6
5
  from typing import Optional, Any
7
6
 
8
7
  from cli.utils.config import get_config
9
8
  from cli.utils.output import format_output, print_error, print_success
10
- from cli.utils.connection import run_command, UnityConnectionError
11
-
12
-
13
- def try_parse_json(value: str, context: str) -> Any:
14
- """Try to parse JSON, with fallback for single quotes and Python bools."""
15
- try:
16
- return json.loads(value)
17
- except json.JSONDecodeError:
18
- # Try to fix common shell quoting issues (single quotes, Python bools)
19
- try:
20
- fixed = value.replace("'", '"').replace(
21
- "True", "true").replace("False", "false")
22
- return json.loads(fixed)
23
- except json.JSONDecodeError as e:
24
- print_error(f"Invalid JSON for {context}: {e}")
25
- sys.exit(1)
9
+ from cli.utils.connection import run_command, handle_unity_errors
10
+ from cli.utils.parsers import parse_json_or_exit as try_parse_json
26
11
 
27
12
 
28
13
  _TEXTURE_TYPES = {
@@ -125,6 +110,22 @@ def _normalize_color(value: Any, context: str) -> list[int]:
125
110
  return _parse_hex_color(value)
126
111
  value = try_parse_json(value, context)
127
112
 
113
+ # Handle dict with r/g/b keys (e.g., {"r": 1, "g": 0, "b": 0} or {"r": 1, "g": 0, "b": 0, "a": 1})
114
+ if isinstance(value, dict):
115
+ if all(k in value for k in ("r", "g", "b")):
116
+ try:
117
+ color = [value["r"], value["g"], value["b"]]
118
+ if "a" in value:
119
+ color.append(value["a"])
120
+ else:
121
+ color.append(1.0 if _is_normalized_color(color) else 255)
122
+ if _is_normalized_color(color):
123
+ return [int(round(float(c) * 255)) for c in color]
124
+ return [int(c) for c in color]
125
+ except (TypeError, ValueError):
126
+ raise ValueError(f"{context} dict values must be numeric, got {value}")
127
+ raise ValueError(f"{context} dict must have 'r', 'g', 'b' keys, got {list(value.keys())}")
128
+
128
129
  if isinstance(value, (list, tuple)):
129
130
  if len(value) == 3:
130
131
  value = list(value) + [1.0 if _is_normalized_color(value) else 255]
@@ -339,6 +340,7 @@ def texture():
339
340
  ]), help="Pattern type")
340
341
  @click.option("--palette", help="Color palette for pattern (JSON array of colors)")
341
342
  @click.option("--import-settings", help="TextureImporter settings (JSON)")
343
+ @handle_unity_errors
342
344
  def create(path: str, width: int, height: int, image_path: Optional[str], color: Optional[str],
343
345
  pattern: Optional[str], palette: Optional[str], import_settings: Optional[str]):
344
346
  """Create a new procedural texture.
@@ -402,14 +404,10 @@ def create(path: str, width: int, height: int, image_path: Optional[str], color:
402
404
  if image_path:
403
405
  params["imagePath"] = image_path
404
406
 
405
- try:
406
- result = run_command("manage_texture", params, config)
407
- click.echo(format_output(result, config.format))
408
- if result.get("success"):
409
- print_success(f"Created texture: {path}")
410
- except UnityConnectionError as e:
411
- print_error(str(e))
412
- sys.exit(1)
407
+ result = run_command("manage_texture", params, config)
408
+ click.echo(format_output(result, config.format))
409
+ if result.get("success"):
410
+ print_success(f"Created texture: {path}")
413
411
 
414
412
 
415
413
  @texture.command("sprite")
@@ -423,6 +421,7 @@ def create(path: str, width: int, height: int, image_path: Optional[str], color:
423
421
  ]), help="Pattern type (defaults to checkerboard if no color specified)")
424
422
  @click.option("--ppu", default=100.0, help="Pixels Per Unit")
425
423
  @click.option("--pivot", help="Pivot as [x,y] (default: [0.5, 0.5])")
424
+ @handle_unity_errors
426
425
  def sprite(path: str, width: int, height: int, image_path: Optional[str], color: Optional[str], pattern: Optional[str], ppu: float, pivot: Optional[str]):
427
426
  """Quickly create a sprite texture.
428
427
 
@@ -476,19 +475,16 @@ def sprite(path: str, width: int, height: int, image_path: Optional[str], color:
476
475
  if image_path:
477
476
  params["imagePath"] = image_path
478
477
 
479
- try:
480
- result = run_command("manage_texture", params, config)
481
- click.echo(format_output(result, config.format))
482
- if result.get("success"):
483
- print_success(f"Created sprite: {path}")
484
- except UnityConnectionError as e:
485
- print_error(str(e))
486
- sys.exit(1)
478
+ result = run_command("manage_texture", params, config)
479
+ click.echo(format_output(result, config.format))
480
+ if result.get("success"):
481
+ print_success(f"Created sprite: {path}")
487
482
 
488
483
 
489
484
  @texture.command("modify")
490
485
  @click.argument("path")
491
486
  @click.option("--set-pixels", required=True, help="Modification args as JSON")
487
+ @handle_unity_errors
492
488
  def modify(path: str, set_pixels: str):
493
489
  """Modify an existing texture.
494
490
 
@@ -510,29 +506,35 @@ def modify(path: str, set_pixels: str):
510
506
  print_error(str(e))
511
507
  sys.exit(1)
512
508
 
513
- try:
514
- result = run_command("manage_texture", params, config)
515
- click.echo(format_output(result, config.format))
516
- if result.get("success"):
517
- print_success(f"Modified texture: {path}")
518
- except UnityConnectionError as e:
519
- print_error(str(e))
520
- sys.exit(1)
509
+ result = run_command("manage_texture", params, config)
510
+ click.echo(format_output(result, config.format))
511
+ if result.get("success"):
512
+ print_success(f"Modified texture: {path}")
521
513
 
522
514
 
523
515
  @texture.command("delete")
524
516
  @click.argument("path")
525
- def delete(path: str):
517
+ @click.option(
518
+ "--force", "-f",
519
+ is_flag=True,
520
+ help="Skip confirmation prompt."
521
+ )
522
+ @handle_unity_errors
523
+ def delete(path: str, force: bool):
526
524
  """Delete a texture.
525
+
526
+ \\b
527
+ Examples:
528
+ unity-mcp texture delete "Assets/Textures/Old.png"
529
+ unity-mcp texture delete "Assets/Textures/Old.png" --force
527
530
  """
531
+ from cli.utils.confirmation import confirm_destructive_action
528
532
  config = get_config()
529
533
 
530
- try:
531
- result = run_command("manage_texture", {
532
- "action": "delete", "path": path}, config)
533
- click.echo(format_output(result, config.format))
534
- if result.get("success"):
535
- print_success(f"Deleted texture: {path}")
536
- except UnityConnectionError as e:
537
- print_error(str(e))
538
- sys.exit(1)
534
+ confirm_destructive_action("Delete", "texture", path, force)
535
+
536
+ result = run_command("manage_texture", {
537
+ "action": "delete", "path": path}, config)
538
+ click.echo(format_output(result, config.format))
539
+ if result.get("success"):
540
+ print_success(f"Deleted texture: {path}")
cli/commands/tool.py CHANGED
@@ -1,40 +1,35 @@
1
1
  """Tool CLI commands for listing custom tools."""
2
2
 
3
- import sys
4
3
  import click
5
4
 
6
5
  from cli.utils.config import get_config
7
6
  from cli.utils.output import format_output, print_error
8
- from cli.utils.connection import run_list_custom_tools, UnityConnectionError
7
+ from cli.utils.connection import run_list_custom_tools, handle_unity_errors
9
8
 
10
9
 
11
10
  def _list_custom_tools() -> None:
12
11
  config = get_config()
13
- try:
14
- result = run_list_custom_tools(config)
15
- if config.format != "text":
16
- click.echo(format_output(result, config.format))
17
- return
18
-
19
- if not isinstance(result, dict) or not result.get("success", True):
20
- click.echo(format_output(result, config.format))
21
- return
22
-
23
- tools = result.get("tools")
24
- if tools is None:
25
- data = result.get("data", {})
26
- tools = data.get("tools") if isinstance(data, dict) else None
27
- if not isinstance(tools, list):
28
- click.echo(format_output(result, config.format))
29
- return
30
-
31
- click.echo(f"Custom tools ({len(tools)}):")
32
- for i, tool in enumerate(tools):
33
- name = tool.get("name") if isinstance(tool, dict) else str(tool)
34
- click.echo(f" [{i}] {name}")
35
- except UnityConnectionError as e:
36
- print_error(str(e))
37
- sys.exit(1)
12
+ result = run_list_custom_tools(config)
13
+ if config.format != "text":
14
+ click.echo(format_output(result, config.format))
15
+ return
16
+
17
+ if not isinstance(result, dict) or not result.get("success", True):
18
+ click.echo(format_output(result, config.format))
19
+ return
20
+
21
+ tools = result.get("tools")
22
+ if tools is None:
23
+ data = result.get("data", {})
24
+ tools = data.get("tools") if isinstance(data, dict) else None
25
+ if not isinstance(tools, list):
26
+ click.echo(format_output(result, config.format))
27
+ return
28
+
29
+ click.echo(f"Custom tools ({len(tools)}):")
30
+ for i, t in enumerate(tools):
31
+ name = t.get("name") if isinstance(t, dict) else str(t)
32
+ click.echo(f" [{i}] {name}")
38
33
 
39
34
 
40
35
  @click.group("tool")
@@ -44,6 +39,7 @@ def tool():
44
39
 
45
40
 
46
41
  @tool.command("list")
42
+ @handle_unity_errors
47
43
  def list_tools():
48
44
  """List custom tools registered for the active Unity project."""
49
45
  _list_custom_tools()
@@ -56,6 +52,7 @@ def custom_tool():
56
52
 
57
53
 
58
54
  @custom_tool.command("list")
55
+ @handle_unity_errors
59
56
  def list_custom_tools():
60
57
  """List custom tools registered for the active Unity project."""
61
58
  _list_custom_tools()