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:
2026-05-29 01:11:20 +08:00
parent e8693dad2a
commit dd3eb24d0f
488 changed files with 33927 additions and 0 deletions

View File

@@ -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)