refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack
将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
@tool
|
||||
extends "res://addons/godot_mcp/commands/base_command.gd"
|
||||
|
||||
const NodeUtils := preload("res://addons/godot_mcp/utils/node_utils.gd")
|
||||
const PropertyParser := preload("res://addons/godot_mcp/utils/property_parser.gd")
|
||||
|
||||
|
||||
func get_commands() -> Dictionary:
|
||||
return {
|
||||
"get_scene_tree": _get_scene_tree,
|
||||
"get_scene_file_content": _get_scene_file_content,
|
||||
"create_scene": _create_scene,
|
||||
"open_scene": _open_scene,
|
||||
"delete_scene": _delete_scene,
|
||||
"add_scene_instance": _add_scene_instance,
|
||||
"play_scene": _play_scene,
|
||||
"stop_scene": _stop_scene,
|
||||
"save_scene": _save_scene,
|
||||
"get_scene_exports": _get_scene_exports,
|
||||
}
|
||||
|
||||
|
||||
func _get_scene_tree(params: Dictionary) -> Dictionary:
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var max_depth: int = optional_int(params, "max_depth", -1)
|
||||
var tree := NodeUtils.get_node_tree(root, max_depth)
|
||||
return success({"scene_path": root.scene_file_path, "tree": tree})
|
||||
|
||||
|
||||
func _get_scene_file_content(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var path: String = result[0]
|
||||
|
||||
if not FileAccess.file_exists(path):
|
||||
return error_not_found("Scene file '%s'" % path)
|
||||
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
return error_internal("Cannot read file: %s" % error_string(FileAccess.get_open_error()))
|
||||
|
||||
var content := file.get_as_text()
|
||||
file.close()
|
||||
return success({"path": path, "content": content, "size": content.length()})
|
||||
|
||||
|
||||
func _create_scene(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var path: String = result[0]
|
||||
|
||||
var guard := guard_offline_scene_save(path)
|
||||
if not guard.is_empty():
|
||||
return guard
|
||||
|
||||
var root_type: String = optional_string(params, "root_type", "Node2D")
|
||||
var root_name: String = optional_string(params, "root_name", "")
|
||||
|
||||
# Validate root type exists
|
||||
if not ClassDB.class_exists(root_type):
|
||||
return error_invalid_params("Unknown node type: %s" % root_type)
|
||||
|
||||
# Create the scene
|
||||
var root: Node = ClassDB.instantiate(root_type)
|
||||
if root_name.is_empty():
|
||||
root_name = path.get_file().get_basename()
|
||||
root.name = root_name
|
||||
|
||||
var scene := PackedScene.new()
|
||||
var err := scene.pack(root)
|
||||
root.queue_free()
|
||||
|
||||
if err != OK:
|
||||
return error_internal("Failed to pack scene: %s" % error_string(err))
|
||||
|
||||
# Ensure directory exists
|
||||
var dir_path := path.get_base_dir()
|
||||
if not DirAccess.dir_exists_absolute(dir_path):
|
||||
DirAccess.make_dir_recursive_absolute(dir_path)
|
||||
|
||||
err = ResourceSaver.save(scene, path)
|
||||
if err != OK:
|
||||
return error_internal("Failed to save scene: %s" % error_string(err))
|
||||
|
||||
# Refresh filesystem
|
||||
EditorInterface.get_resource_filesystem().scan()
|
||||
|
||||
return success({"path": path, "root_type": root_type, "root_name": root_name})
|
||||
|
||||
|
||||
func _open_scene(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var path: String = result[0]
|
||||
|
||||
if not FileAccess.file_exists(path):
|
||||
return error_not_found("Scene file '%s'" % path)
|
||||
|
||||
EditorInterface.open_scene_from_path(path)
|
||||
return success({"path": path, "opened": true})
|
||||
|
||||
|
||||
func _delete_scene(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var path: String = result[0]
|
||||
|
||||
if not FileAccess.file_exists(path):
|
||||
return error_not_found("Scene file '%s'" % path)
|
||||
|
||||
var err := DirAccess.remove_absolute(path)
|
||||
if err != OK:
|
||||
return error_internal("Failed to delete scene: %s" % error_string(err))
|
||||
|
||||
# Also remove .import file if exists
|
||||
var import_path := path + ".import"
|
||||
if FileAccess.file_exists(import_path):
|
||||
DirAccess.remove_absolute(import_path)
|
||||
|
||||
EditorInterface.get_resource_filesystem().scan()
|
||||
return success({"path": path, "deleted": true})
|
||||
|
||||
|
||||
func _add_scene_instance(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "scene_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var scene_path: String = result[0]
|
||||
|
||||
var parent_path: String = optional_string(params, "parent_path", ".")
|
||||
var instance_name: String = optional_string(params, "name", "")
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
if not FileAccess.file_exists(scene_path):
|
||||
return error_not_found("Scene file '%s'" % scene_path)
|
||||
|
||||
var parent := find_node_by_path(parent_path)
|
||||
if parent == null:
|
||||
return error_not_found("Parent node '%s'" % parent_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
var packed: PackedScene = load(scene_path)
|
||||
if packed == null:
|
||||
return error_internal("Failed to load scene: %s" % scene_path)
|
||||
|
||||
var instance := packed.instantiate()
|
||||
if not instance_name.is_empty():
|
||||
instance.name = instance_name
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Add scene instance")
|
||||
undo_redo.add_do_method(parent, "add_child", instance)
|
||||
undo_redo.add_do_method(instance, "set_owner", root)
|
||||
undo_redo.add_do_reference(instance)
|
||||
undo_redo.add_undo_method(parent, "remove_child", instance)
|
||||
undo_redo.commit_action()
|
||||
|
||||
NodeUtils.set_owner_recursive(instance, root)
|
||||
|
||||
return success({
|
||||
"node_path": str(root.get_path_to(instance)),
|
||||
"scene_path": scene_path,
|
||||
"name": instance.name,
|
||||
})
|
||||
|
||||
|
||||
func _play_scene(params: Dictionary) -> Dictionary:
|
||||
var mode: String = optional_string(params, "mode", "main") # "main", "current", or path
|
||||
|
||||
match mode:
|
||||
"main":
|
||||
EditorInterface.play_main_scene()
|
||||
"current":
|
||||
EditorInterface.play_current_scene()
|
||||
_:
|
||||
# Treat as scene path
|
||||
if not FileAccess.file_exists(mode):
|
||||
return error_not_found("Scene file '%s'" % mode)
|
||||
EditorInterface.play_custom_scene(mode)
|
||||
|
||||
return success({"playing": true, "mode": mode})
|
||||
|
||||
|
||||
func _stop_scene(_params: Dictionary) -> Dictionary:
|
||||
if not EditorInterface.is_playing_scene():
|
||||
return success({"stopped": false, "message": "No scene is currently playing"})
|
||||
|
||||
EditorInterface.stop_playing_scene()
|
||||
|
||||
# Clean up temp files
|
||||
_cleanup_screenshot_files()
|
||||
_cleanup_input_files()
|
||||
_cleanup_inspector_files()
|
||||
|
||||
return success({"stopped": true})
|
||||
|
||||
|
||||
func _save_scene(params: Dictionary) -> Dictionary:
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var path: String = optional_string(params, "path", "")
|
||||
if path.is_empty():
|
||||
path = root.scene_file_path
|
||||
|
||||
if path.is_empty():
|
||||
return error_invalid_params("No save path specified and scene has no existing path")
|
||||
|
||||
var normalized_path := normalize_project_path(path)
|
||||
if is_scene_path_open(normalized_path) and not is_active_scene_path(normalized_path):
|
||||
return error_conflict(
|
||||
"Refusing to save inactive open scene '%s' from the active editor scene" % normalized_path,
|
||||
{
|
||||
"path": normalized_path,
|
||||
"active_scene": normalize_project_path(root.scene_file_path),
|
||||
"open_scenes": get_open_scene_paths(),
|
||||
"suggestion": "Open the target scene tab before saving it, or close it before offline edits.",
|
||||
}
|
||||
)
|
||||
|
||||
var dir_path := normalized_path.get_base_dir()
|
||||
if not DirAccess.dir_exists_absolute(dir_path):
|
||||
DirAccess.make_dir_recursive_absolute(dir_path)
|
||||
|
||||
var err: int
|
||||
var save_method: String
|
||||
if root.scene_file_path.is_empty() or normalize_project_path(root.scene_file_path) != normalized_path:
|
||||
EditorInterface.save_scene_as(normalized_path)
|
||||
err = OK
|
||||
save_method = "EditorInterface.save_scene_as"
|
||||
else:
|
||||
err = EditorInterface.save_scene()
|
||||
save_method = "EditorInterface.save_scene"
|
||||
if err != OK:
|
||||
return error_internal("Failed to save scene via %s: %s" % [save_method, error_string(err)])
|
||||
|
||||
return success({"path": normalized_path, "saved": true, "method": save_method})
|
||||
|
||||
|
||||
func _get_scene_exports(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var path: String = result[0]
|
||||
|
||||
if not FileAccess.file_exists(path):
|
||||
return error_not_found("Scene file '%s'" % path)
|
||||
|
||||
var packed: PackedScene = load(path)
|
||||
if packed == null:
|
||||
return error_internal("Failed to load scene: %s" % path)
|
||||
|
||||
var instance: Node = packed.instantiate()
|
||||
if instance == null:
|
||||
return error_internal("Failed to instantiate scene: %s" % path)
|
||||
|
||||
var nodes_data: Array = []
|
||||
_collect_exports_recursive(instance, instance, nodes_data)
|
||||
|
||||
instance.queue_free()
|
||||
|
||||
return success({
|
||||
"path": path,
|
||||
"nodes": nodes_data,
|
||||
"count": nodes_data.size(),
|
||||
})
|
||||
|
||||
|
||||
func _collect_exports_recursive(node: Node, root: Node, nodes_data: Array) -> void:
|
||||
var script: Script = node.get_script()
|
||||
if script != null:
|
||||
var exports: Dictionary = {}
|
||||
for prop_info in script.get_script_property_list():
|
||||
var usage: int = prop_info["usage"]
|
||||
if (usage & PROPERTY_USAGE_EDITOR) and (usage & PROPERTY_USAGE_SCRIPT_VARIABLE):
|
||||
var prop_name: String = prop_info["name"]
|
||||
exports[prop_name] = {
|
||||
"value": PropertyParser.serialize_value(node.get(prop_name)),
|
||||
"type": prop_info["type"],
|
||||
"hint": prop_info.get("hint", 0),
|
||||
"hint_string": prop_info.get("hint_string", ""),
|
||||
}
|
||||
if not exports.is_empty():
|
||||
var node_path := "." if node == root else str(root.get_path_to(node))
|
||||
nodes_data.append({
|
||||
"node_path": node_path,
|
||||
"node_name": node.name,
|
||||
"node_type": node.get_class(),
|
||||
"script_path": script.resource_path,
|
||||
"exports": exports,
|
||||
})
|
||||
|
||||
for child in node.get_children():
|
||||
_collect_exports_recursive(child, root, nodes_data)
|
||||
|
||||
|
||||
func _cleanup_screenshot_files() -> void:
|
||||
var user_dir := get_game_user_dir()
|
||||
var request_path := user_dir + "/mcp_screenshot_request"
|
||||
var screenshot_path := user_dir + "/mcp_screenshot.png"
|
||||
if FileAccess.file_exists(request_path):
|
||||
DirAccess.remove_absolute(request_path)
|
||||
if FileAccess.file_exists(screenshot_path):
|
||||
DirAccess.remove_absolute(screenshot_path)
|
||||
|
||||
|
||||
func _cleanup_input_files() -> void:
|
||||
var user_dir := get_game_user_dir()
|
||||
var commands_path := user_dir + "/mcp_input_commands"
|
||||
if FileAccess.file_exists(commands_path):
|
||||
DirAccess.remove_absolute(commands_path)
|
||||
|
||||
|
||||
func _cleanup_inspector_files() -> void:
|
||||
var user_dir := get_game_user_dir()
|
||||
var request_path := user_dir + "/mcp_game_request"
|
||||
var response_path := user_dir + "/mcp_game_response"
|
||||
if FileAccess.file_exists(request_path):
|
||||
DirAccess.remove_absolute(request_path)
|
||||
if FileAccess.file_exists(response_path):
|
||||
DirAccess.remove_absolute(response_path)
|
||||
Reference in New Issue
Block a user