mcpywrap 0.4.2__tar.gz → 0.4.4__tar.gz

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 (270) hide show
  1. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/CHANGELOG.md +15 -0
  2. {mcpywrap-0.4.2/mcpywrap.egg-info → mcpywrap-0.4.4}/PKG-INFO +1 -1
  3. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/game-backends.md +1 -1
  4. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/git-dependencies.md +3 -3
  5. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/__init__.py +1 -1
  6. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/code_libraries.py +11 -7
  7. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/connect_cmd.py +11 -2
  8. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/sync_cmd.py +2 -6
  9. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/install.py +1 -1
  10. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/macos.py +30 -6
  11. mcpywrap-0.4.4/mcpywrap/engines/macos_network.py +47 -0
  12. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/tui.py +4 -2
  13. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/windows.py +22 -16
  14. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/git_cache.py +8 -1
  15. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/git_projects.py +14 -44
  16. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/bridge_payload/Injector.exe +0 -0
  17. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/bridge_payload/Loader.dll +0 -0
  18. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/bridge_payload/McpyMcsAuth.dll +0 -0
  19. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/bridge_payload/manifest.json +3 -3
  20. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_debug.py +4 -2
  21. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/session_worker.py +2 -2
  22. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/sessions.py +1 -1
  23. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/window_capture/manifest.json +1 -1
  24. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/window_capture/mcpy-window-capture.exe +0 -0
  25. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/minecraft/template/mod_template.py +2 -0
  26. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/source_files.py +4 -1
  27. {mcpywrap-0.4.2 → mcpywrap-0.4.4/mcpywrap.egg-info}/PKG-INFO +1 -1
  28. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap.egg-info/SOURCES.txt +2 -0
  29. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/pyproject.toml +1 -1
  30. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/SKILL.md +5 -2
  31. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/macos-local.md +26 -1
  32. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/project-dependencies.md +2 -1
  33. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_code_libraries.py +20 -0
  34. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_engine_backends.py +22 -0
  35. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_git_projects.py +105 -69
  36. mcpywrap-0.4.4/tests/test_macos_network.py +120 -0
  37. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_qt_sessions.py +5 -1
  38. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/LICENSE +0 -0
  39. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/MANIFEST.in +0 -0
  40. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/README.md +0 -0
  41. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/THIRD_PARTY_NOTICES.md +0 -0
  42. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/code-libraries.md +0 -0
  43. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/development.md +0 -0
  44. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/engine-discovery.md +0 -0
  45. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/input-sequence.md +0 -0
  46. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/launcher-client-python.md +0 -0
  47. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/local-dependencies.md +0 -0
  48. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/macos-resource-reload.md +0 -0
  49. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/mcs-auth.md +0 -0
  50. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/qt6-development.md +0 -0
  51. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/qumod.md +0 -0
  52. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/release-0.4.0.md +0 -0
  53. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/release-readiness-0.4.0.md +0 -0
  54. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/releasing.md +0 -0
  55. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/runtime-debug.md +0 -0
  56. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/runtime-input.md +0 -0
  57. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/runtime-player.md +0 -0
  58. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/runtime-ui.md +0 -0
  59. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/docs/shared-log-ui.md +0 -0
  60. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/__main__.py +0 -0
  61. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/AddonsPack.py +0 -0
  62. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/MapPack.py +0 -0
  63. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/__init__.py +0 -0
  64. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/assembly.py +0 -0
  65. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/dependency_manager.py +0 -0
  66. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/file_merge.py +0 -0
  67. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/project_builder.py +0 -0
  68. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/builders/watcher.py +0 -0
  69. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/cli.py +0 -0
  70. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/command_context.py +0 -0
  71. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/__init__.py +0 -0
  72. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/add_cmd.py +0 -0
  73. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/build_cmd.py +0 -0
  74. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/default_cmd.py +0 -0
  75. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/dependency_prompt.py +0 -0
  76. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/desktop_cmd.py +0 -0
  77. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/dev_cmd.py +0 -0
  78. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/doctor_cmd.py +0 -0
  79. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/edit_cmd.py +0 -0
  80. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/engine_cmd.py +0 -0
  81. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/init_cmd.py +0 -0
  82. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/mod_cmd.py +0 -0
  83. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/modsdk_cmd.py +0 -0
  84. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/package_cmd.py +0 -0
  85. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/publish_cmd.py +0 -0
  86. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/record_cmd.py +0 -0
  87. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/remove_cmd.py +0 -0
  88. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/run_cmd.py +0 -0
  89. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/runtime_cmd.py +0 -0
  90. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/runtime_input_cmd.py +0 -0
  91. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/runtime_player_cmd.py +0 -0
  92. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/runtime_ui_cmd.py +0 -0
  93. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/serve_cmd.py +0 -0
  94. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/session_cmd.py +0 -0
  95. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/commands/ui_cmd.py +0 -0
  96. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/config.py +0 -0
  97. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/custom_packaging.py +0 -0
  98. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/dependencies.py +0 -0
  99. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/__init__.py +0 -0
  100. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/apk_profile.py +0 -0
  101. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/backend.py +0 -0
  102. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/host.py +0 -0
  103. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/official.py +0 -0
  104. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/engines/preferences.py +0 -0
  105. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/framework_presets.py +0 -0
  106. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/frameworks.py +0 -0
  107. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/input_executor.py +0 -0
  108. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/input_plan.py +0 -0
  109. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/SimpleMonitor.py +0 -0
  110. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/__init__.py +0 -0
  111. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/auth_interaction.py +0 -0
  112. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/bridge_assets.py +0 -0
  113. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/bridge_payload/publisher.cer +0 -0
  114. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/diagnostics.py +0 -0
  115. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/discovery.py +0 -0
  116. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/editor.py +0 -0
  117. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/file_logs.py +0 -0
  118. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/game.py +0 -0
  119. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/host_input.py +0 -0
  120. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/hot_reload.py +0 -0
  121. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/input_sequence.py +0 -0
  122. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/launcher_python.py +0 -0
  123. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/log_colorizer.py +0 -0
  124. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/log_protocol.py +0 -0
  125. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/mcs.py +0 -0
  126. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/mcs_auth.py +0 -0
  127. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/network.py +0 -0
  128. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/private_logs.py +0 -0
  129. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/processes.py +0 -0
  130. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/recording_worker.py +0 -0
  131. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/recordings.py +0 -0
  132. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_cppconfig.py +0 -0
  133. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_input_payload.py +0 -0
  134. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_key_timeline_payload.py +0 -0
  135. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_player_payload.py +0 -0
  136. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_ui.py +0 -0
  137. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_ui_outline.py +0 -0
  138. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/runtime_ui_payload.py +0 -0
  139. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/session_capabilities.py +0 -0
  140. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/studio_server.py +0 -0
  141. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/studio_server_ui.py +0 -0
  142. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/symlink_helper_global.py +0 -0
  143. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/symlink_helper_map.py +0 -0
  144. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/symlinks.py +0 -0
  145. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/unified_input.py +0 -0
  146. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/mcstudio/window.py +0 -0
  147. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/minecraft/__init__.py +0 -0
  148. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/minecraft/addons.py +0 -0
  149. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/minecraft/level_dat.py +0 -0
  150. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/minecraft/map.py +0 -0
  151. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/minecraft/netease_modsdk.py +0 -0
  152. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/minecraft/template/generate_mod_files.py +0 -0
  153. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/project_dependencies.py +0 -0
  154. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/project_init.py +0 -0
  155. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/recording_io.py +0 -0
  156. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/remote/__init__.py +0 -0
  157. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/remote/client.py +0 -0
  158. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/remote/http_server.py +0 -0
  159. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/remote/service.py +0 -0
  160. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/timeline.py +0 -0
  161. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/__init__.py +0 -0
  162. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/engine_setup.py +0 -0
  163. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/log_view.py +0 -0
  164. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/log_window.py +0 -0
  165. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/project_ui.py +0 -0
  166. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/run_session.py +0 -0
  167. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/session_controller.py +0 -0
  168. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/session_tools.py +0 -0
  169. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/session_views.py +0 -0
  170. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/ui/world_config.py +0 -0
  171. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/utils/__init__.py +0 -0
  172. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/utils/pip_error_parser.py +0 -0
  173. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/utils/print_guide.py +0 -0
  174. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/utils/project_setup.py +0 -0
  175. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap/utils/utils.py +0 -0
  176. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap.egg-info/dependency_links.txt +0 -0
  177. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap.egg-info/entry_points.txt +0 -0
  178. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap.egg-info/requires.txt +0 -0
  179. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/mcpywrap.egg-info/top_level.txt +0 -0
  180. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/mcs_auth/Bridge.cs +0 -0
  181. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/mcs_auth/Injector.cs +0 -0
  182. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/mcs_auth/Loader.c +0 -0
  183. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/mcs_auth/publisher.cer +0 -0
  184. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/mcs_auth/publisher.json +0 -0
  185. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/runtime_input/README.md +0 -0
  186. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/runtime_input/launcher-monotonic.patch +0 -0
  187. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/runtime_input/steady_clock.h +0 -0
  188. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/window_capture/video_capture.h +0 -0
  189. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/native/window_capture/window_capture.cpp +0 -0
  190. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/scripts/build_all_native.py +0 -0
  191. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/scripts/build_mcs_auth_bridge.py +0 -0
  192. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/scripts/build_window_capture.py +0 -0
  193. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/scripts/check_mcs_auth_bridge.py +0 -0
  194. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/scripts/check_release.py +0 -0
  195. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/scripts/check_window_capture.py +0 -0
  196. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/setup.cfg +0 -0
  197. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/agents/openai.yaml +0 -0
  198. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/input-sequence.md +0 -0
  199. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/input-writing.md +0 -0
  200. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/instance-world-settings.md +0 -0
  201. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/local-development.md +0 -0
  202. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/remote-testing.md +0 -0
  203. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/runtime-input.md +0 -0
  204. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/runtime-key-timeline.md +0 -0
  205. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/runtime-player.md +0 -0
  206. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/runtime-ui.md +0 -0
  207. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/references/troubleshooting.md +0 -0
  208. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/scripts/bootstrap.ps1 +0 -0
  209. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/scripts/bootstrap.py +0 -0
  210. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/scripts/game_window.py +0 -0
  211. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/skills/mcpywrap/scripts/smoke.py +0 -0
  212. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/README.md +0 -0
  213. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/client_probe.py +0 -0
  214. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/input_probe.py +0 -0
  215. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/key_timeline_probe.py +0 -0
  216. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/movement.json +0 -0
  217. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/run.py +0 -0
  218. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/server_fixture.py +0 -0
  219. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual/runtime_controls/support.py +0 -0
  220. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_dependency_boundaries.py +0 -0
  221. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_input_window.py +0 -0
  222. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_integration.py +0 -0
  223. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_native_recording.py +0 -0
  224. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_remote_recording.py +0 -0
  225. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_runtime_input.py +0 -0
  226. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_runtime_input_unified.py +0 -0
  227. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_runtime_key_timeline.py +0 -0
  228. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_runtime_player.py +0 -0
  229. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/manual_runtime_ui.py +0 -0
  230. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_auth_interaction.py +0 -0
  231. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_automation.py +0 -0
  232. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_build_replace_retry.py +0 -0
  233. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_dependency_boundaries.py +0 -0
  234. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_engine_compatibility.py +0 -0
  235. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_engine_discovery.py +0 -0
  236. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_engine_install.py +0 -0
  237. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_game_window_skill.py +0 -0
  238. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_host_input.py +0 -0
  239. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_input_sequence.py +0 -0
  240. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_instance_cppconfig.py +0 -0
  241. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_launch_encoding.py +0 -0
  242. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_launcher_python.py +0 -0
  243. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_local_auth_session.py +0 -0
  244. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_local_dependencies.py +0 -0
  245. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_manual_runtime_controls.py +0 -0
  246. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_mcs_auth.py +0 -0
  247. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_mouse.py +0 -0
  248. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_native_input_sequence.py +0 -0
  249. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_native_preferences.py +0 -0
  250. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_native_recording.py +0 -0
  251. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_network.py +0 -0
  252. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_network_sessions.py +0 -0
  253. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_pack_uuid_conflicts.py +0 -0
  254. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_package.py +0 -0
  255. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_qumod_workflow.py +0 -0
  256. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_recordings.py +0 -0
  257. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_remote.py +0 -0
  258. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_remote_scripts.py +0 -0
  259. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_resource_reload.py +0 -0
  260. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_runtime_debug.py +0 -0
  261. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_runtime_input.py +0 -0
  262. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_runtime_key_timeline.py +0 -0
  263. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_runtime_player.py +0 -0
  264. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_runtime_ui.py +0 -0
  265. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_runtime_ui_outline.py +0 -0
  266. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_session_capabilities.py +0 -0
  267. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_shared_logs.py +0 -0
  268. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_skill_workflows.py +0 -0
  269. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_unified_skill_contract.py +0 -0
  270. {mcpywrap-0.4.2 → mcpywrap-0.4.4}/tests/test_window_focus.py +0 -0
@@ -1,5 +1,20 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.4.4
4
+
5
+ - 修复 Windows 与 macOS 之间同步 Git 依赖时的源码校验失败:获取源码时禁用宿主 Git 的换行、编码与过滤转换,统一保留固定提交中的文件字节。
6
+ - `mcpy sync` 按固定提交直接重新计算 lock 摘要,不改变依赖版本,不再创建锁备份或执行专门迁移;未被修改的旧安装副本自动重建,缓存损坏与手动修改仍会报错。
7
+ - Git 项目依赖和旧代码库使用独立的新源码缓存,首次同步重新获取固定提交,后续可离线复用。补充跨 Git 配置、旧 Windows 缓存、二进制文件与重复同步稳定性的回归测试。
8
+ - Windows 启动时增加原生 HUD 布局日志识别,避免 Python 启动日志异常导致世界就绪状态无法确认。
9
+ - 修复 Mod 模板窗口关闭后未释放对象、在新版 PySide6 下退出进程时可能崩溃的问题。
10
+
11
+ ## 0.4.3
12
+
13
+ - macOS 新增无认证服务器连接,复用 `mcpy connect` 与项目 `[tool.mcpywrap.server]` 的 `run` 入口、cppconfig、日志、偏好和退出机制,不装配本地 Mod 或创建本地世界实例。
14
+ - 配套 macOS 运行包 0.4.3 使用现有引擎 Python 入口进服,无新增游戏二进制补丁;首次安装自动取得配套运行包,旧包给出明确升级命令,保留已有世界绑定。
15
+ - 网络会话仅开放客户端 Python;在 worker 中拒绝服务端 Python 和本地 Mod 热更。macOS 初次连接超时会提示失败并回收游戏,进入 HUD 后记录连接验证结果。
16
+ - Skill 补充离线服务器条件、旧运行包升级、状态验证及客户端调试边界。已在 macOS 26.6.2 / APK 3.10.100.299889 上通过本机 Nemisys + SynapseAPI + Nukkit 验证 connect、项目 run、Python 与失败清理;不包含 MCS/MPay 登录、外部 HTTP 资源包或新版统一输入的验收。
17
+
3
18
  ## 0.4.2
4
19
 
5
20
  - 新增统一 `runtime input capabilities/observe/run/status/cancel/stop` 与 `mcpy.input`。键盘组合、独立边沿、玩家与单节点 UI 操作共用 JSON steps、动作注册表、排时和清理;单次 key 快捷命令只生成同一份计划。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcpywrap
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: A wrapper for Minecraft China Edition Addons management
5
5
  Author-email: boybook <boybook@easecation.net>
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ WindowsBackend 调用原有 MC Studio 发现、cppconfig 和 Safaia 流程,保
19
19
 
20
20
  macOS 首次 mcpy run 自动从内置发布源安装原生运行包和官方 APK,进度在 stderr;也可提前执行 mcpy engine install。--catalog / --apk 是高级覆盖选项。交互式 run 显示共用调试小窗,ui 打开完整管理页;--no-gui / --detach / --json / --non-interactive 抑制调试小窗;Windows 保持手动安装 MC Studio。默认资源目录 ~/Library/Application Support/mcpy,可设置 MCPY_ENGINE_HOME、MCPY_RUNTIME_CATALOG。
21
21
 
22
- macOS 当前只支持离线 Addon 世界,不支持 Map、服务器连接、MCS 登录或 Windows 桌面截图/键鼠/录像;需要这些能力时显式使用 Windows 远程端。最低 macOS 13.0,实际游戏只在 26.6.2 验证。运行包尚为 ad-hoc 签名、公证待完成。
22
+ macOS 支持离线 Addon 世界;无认证服务器连接需 CLI 0.4.3+ 与运行包 0.4.3+ 配套,运行包须声明 network_connect_protocol=1(旧 0.4.0 运行包需要显式升级)。connect 与配置了 server 的 run 共用 cppconfig、会话、日志及退出;网络会话仅支持客户端调试,不装配本地 Mod。macOS 不支持 Map、MCS 登录或 Windows 桌面截图/键鼠/录像;需要这些能力时显式使用 Windows 远程端。最低 macOS 13.0,实际游戏只在 26.6.2 验证。运行包尚为 ad-hoc 签名、公证待完成。
23
23
 
24
24
  图形界面的会话操作集中在 ui/session_controller.py;主界面和小窗仅使用共用 SessionControls/PythonConsole 与 LogView。小窗不再另起 Studio TCP 接收器,详见 [共用日志界面](shared-log-ui.md)。
25
25
 
@@ -76,11 +76,11 @@ Python的project.dependencies继续表示工具Python依赖,不变成游戏代
76
76
 
77
77
  两端提交并使用同一份 `mcpy-git.lock.json`,无需维护平台分支。锁定的是 Git 提交、源码和依赖关系;本机安装目录位于 `.mcpy/`,不提交到 Git。当前依赖都是游戏源码,没有需要分别锁定的 Windows/macOS 原生包。
78
78
 
79
- v2 锁使用相同的大小写敏感路径排序;源码由 Git archive 提取,保留仓库内的原始字节,工具生成的文件统一使用 LF。切换操作系统不会改变依赖摘要。新机器或克隆项目后运行一次 `mcpy sync`,随后正常 `mcpy run`。
79
+ v2 锁使用相同的大小写敏感路径排序;源码由 Git archive 提取,禁用宿主 Git 的换行、编码、ident 和 filter 转换及全局 attributes,保留提交中的文件字节(包括二进制和原本的 CRLF)。工具生成的文件统一使用 LF。新机器或克隆项目后运行一次 `mcpy sync`,随后正常 `mcpy run`。
80
80
 
81
- 普通 `mcpy sync` 会验证旧 v1 锁的源码,再自动更新跨平台格式,不再需要迁移参数。旧锁备份于 `.mcpy/lock-backups/`;需要重建的旧平台安装副本保留于 `.mcpy/registration-backups/`。提交新锁后,团队两端应使用支持 v2 的新版 mcpy。旧源码校验失败、或已安装文件出现无法解释的修改时仍会报错,不自动丢弃用户修改。
81
+ `mcpy sync` 依据声明中固定的完整 Git 提交重新计算源码和安装内容摘要,直接写回锁文件;不更新依赖版本、不创建备份或执行专门的锁迁移。旧 v1/v2 锁都可通过一次 sync 刷新。新快照使用独立的 `git-archive-v2` 缓存,首次同步会重新获取固定提交,之后可离线复用;旧缓存不参与新摘要计算。已安装副本与旧锁一致时可重建,手动改动或缓存损坏仍会报错。`build/package/run` 继续严格验证锁文件,不隐式重算。
82
82
 
83
- 终端只显示同步进度、结果和必要操作;`mcpy sync --json` 保留备份位置、完整摘要等诊断信息,供脚本和 AI 使用。`--migrate-windows-lock` 暂留作旧脚本兼容参数。
83
+ 终端只显示同步进度、结果和必要操作;`mcpy sync --json` 返回结构化结果。`--migrate-windows-lock` 和 JSON 中始终为空的 `git_lock_migrations` 仅保留旧脚本兼容。团队各设备应使用包含此修复的版本,并提交 sync 更新后的锁文件。
84
84
 
85
85
  ## 验证范围
86
86
 
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """mcpywrap - 我的世界中国版 Addon 开发依赖、构建与游戏测试工具。"""
3
3
 
4
- __version__ = '0.4.2'
4
+ __version__ = '0.4.4'
@@ -64,9 +64,6 @@ def prepare_sync(root, config=None):
64
64
  for entry in entries:
65
65
  key = identity(entry)
66
66
  installed = cache / key
67
- if installed.is_dir() and key in expected and expected[key] in (digest(installed), windows_checkout_digest(installed, crlf=False)):
68
- lock['libraries'].append({'declaration': entry, 'sha256': digest(installed)})
69
- continue
70
67
  # 临时仓库不检出文件、不运行依赖代码、hooks 或安装脚本。
71
68
  with tempfile.TemporaryDirectory(prefix='fetch-', dir=cache) as temporary:
72
69
  temp = Path(temporary)
@@ -83,12 +80,19 @@ def prepare_sync(root, config=None):
83
80
  (content / ('MCPY_UPSTREAM_' + file.name)).write_bytes(file.read_bytes())
84
81
  validate_game_files(root, [content])
85
82
  value = digest(content)
86
- if key in expected and expected[key] not in (value, windows_checkout_digest(content, crlf=False)):
87
- raise DependencyError('固定提交的代码库内容与锁文件摘要不符')
88
83
  if installed.exists():
89
- # 回退到旧提交:用重新获取的内容验证旧缓存,而不是逐文件覆盖。
84
+ # Rebuild unchanged old installations from canonical source.
90
85
  if digest(installed) != value:
91
- raise DependencyError(f'代码库缓存已损坏,请移走后重新 sync: {installed}')
86
+ if key not in expected or expected[key] not in (
87
+ digest(installed), windows_checkout_digest(installed, crlf=False)):
88
+ raise DependencyError(f'代码库缓存已损坏,请移走后重新 sync: {installed}')
89
+ old = temp / 'old'
90
+ os.replace(installed, old)
91
+ try:
92
+ os.replace(content, installed)
93
+ except BaseException:
94
+ os.replace(old, installed)
95
+ raise
92
96
  else:
93
97
  os.replace(content, installed)
94
98
  lock['libraries'].append({'declaration': entry, 'sha256': value})
@@ -22,5 +22,14 @@ def connect_cmd(host, port, mcs_auth=False, detach=False, request_id=None, **eng
22
22
  if request_id:
23
23
  raise click.UsageError('--request-id 仅用于远程启动')
24
24
  from ..engines.backend import get_backend
25
- return get_backend().connect(ServerTarget(host, port, 'mcs' if mcs_auth else 'none'),
26
- engine_overrides=engine_overrides, detach=detach)
25
+ from ..engines.host import EngineError
26
+ backend = get_backend()
27
+ target = ServerTarget(host, port, 'mcs' if mcs_auth else 'none')
28
+ try:
29
+ return backend.connect(target, engine_overrides=engine_overrides, detach=detach)
30
+ except EngineError as error:
31
+ if error.code != 'setup_required' or not backend.managed_install: raise
32
+ from .engine_cmd import perform_install
33
+ click.echo('首次运行:自动下载并准备本地游戏环境…', err=True)
34
+ perform_install()
35
+ return backend.connect(target, engine_overrides=engine_overrides, detach=detach)
@@ -19,8 +19,6 @@ def sync_cmd(install, migrate_windows_lock=False):
19
19
  result = sync_project(project_dir(), install, migrate_windows_lock=migrate_windows_lock)
20
20
  if json_output():
21
21
  return result
22
- if result['git_lock_migrations']:
23
- click.echo('已更新跨平台依赖记录,旧文件已备份。')
24
22
  count = result['git_projects'] + result['code_libraries']
25
23
  click.secho('完成 · %d 个源码依赖已就绪 · %.1fs' % (count, time.monotonic()-started), fg='green')
26
24
  if install:
@@ -38,7 +36,6 @@ def sync_project(path, install=False, migrate_windows_lock=False):
38
36
  from ..builders.dependency_manager import DependencyManager
39
37
  visited, active = set(), set()
40
38
  git_count = 0
41
- migrations = []
42
39
  def sync_sources(directory):
43
40
  nonlocal git_count
44
41
  key = canonical_path(directory)
@@ -57,8 +54,7 @@ def sync_project(path, install=False, migrate_windows_lock=False):
57
54
  if target and 'mcpywrap' in read_project(target).get('tool', {}):
58
55
  sync_sources(target)
59
56
  if git_declarations(directory, source_config) or Path(directory, GIT_LOCK_FILE).exists():
60
- git_count += sync_projects(directory, source_config, migrate_windows_lock=migrate_windows_lock,
61
- migration_report=migrations)
57
+ git_count += sync_projects(directory, source_config)
62
58
  active.remove(key)
63
59
  visited.add(key)
64
60
  sync_sources(path)
@@ -78,4 +74,4 @@ def sync_project(path, install=False, migrate_windows_lock=False):
78
74
  if proc.returncode:
79
75
  raise click.ClickException(proc.stderr or proc.stdout or '项目安装失败')
80
76
  return {'project': str(path), 'installed': install, 'code_libraries': library_count,
81
- 'git_projects': git_count, 'git_lock_migrations': migrations}
77
+ 'git_projects': git_count, 'git_lock_migrations': []}
@@ -24,7 +24,7 @@ from .host import EngineError, describe, require_macos
24
24
 
25
25
  CATALOG_ENV = 'MCPY_RUNTIME_CATALOG'
26
26
  DEFAULT_CATALOG = ('https://github.com/EaseCation/mcpelauncher-manifest/releases/download/'
27
- 'mcpy-runtime-v0.4.0/catalog.json')
27
+ 'mcpy-runtime-v0.4.3/catalog.json')
28
28
  # Public constant from the official ApkDownload component, verified 2026-10-05.
29
29
  # https://mcdev.webapp.163.com/static/js/4.05713153ba17aea6db53.js
30
30
  CDN_CONSTANT = 'mEE7Cot48r9j2AvEL2N6jpXEc'
@@ -65,7 +65,8 @@ def pinned_runtime(item):
65
65
  install.verify_game(game, plan['profile'])
66
66
  compatibility = install.preflight_runtime(app, game, plan['profile'], meta, plan['runtime_id'])
67
67
  return {'runtime': str(app), 'game': str(game), 'profile': plan['profile'], 'runtime_id': plan['runtime_id'],
68
- 'compat_report': compatibility, 'cppconfig_protocol': meta.get('cppconfig_protocol')}
68
+ 'compat_report': compatibility, 'cppconfig_protocol': meta.get('cppconfig_protocol'),
69
+ 'network_connect_protocol': meta.get('network_connect_protocol')}
69
70
 
70
71
 
71
72
  def run(project, *, new=False, listing=False, delete=None, clean_all=False, force=False,
@@ -85,9 +86,15 @@ def run(project, *, new=False, listing=False, delete=None, clean_all=False, forc
85
86
  raise EngineError('MC Studio 身份、EXE 和下载目录参数仅适用于 Windows。', 'unsupported_option',
86
87
  'macOS 当前使用开发者 APK 的离线模式;资源由 mcpy engine 管理。')
87
88
  config = read_project(project)
88
- if config.get('tool', {}).get('mcpywrap', {}).get('server'):
89
- raise EngineError('macOS 本地后端当前只支持离线世界。', 'unsupported_feature',
90
- '连接服务器请配置 --remote <Windows 服务地址>。')
89
+ if not (listing or delete or clean_all):
90
+ from ..mcstudio.network import configured_target, prepare_project
91
+ target = configured_target(config)
92
+ if target is not None:
93
+ if new or instance_prefix or world_config is not None:
94
+ raise click.UsageError('网络模式不支持 --new 或本地世界实例 ID')
95
+ packs = prepare_project(project, config)
96
+ from .macos_network import connect
97
+ return connect(target, project_dir=project, packs=packs, engine_overrides=overrides, detach=detach)
91
98
  if config.get('tool', {}).get('mcpywrap', {}).get('project_type', 'addon') != 'addon':
92
99
  raise EngineError('macOS 首版支持 Addon 测试,Map 项目尚未接入。', 'unsupported_feature',
93
100
  '地图项目请在 Windows 本机运行。')
@@ -192,6 +199,13 @@ def run(project, *, new=False, listing=False, delete=None, clean_all=False, forc
192
199
 
193
200
  def launch_session(data):
194
201
  plan = data['launch']
202
+ if data.get('mode') == 'network':
203
+ from types import SimpleNamespace
204
+ from ..mcstudio.network import ServerTarget, unauthenticated_config
205
+ config = unauthenticated_config(
206
+ SimpleNamespace(version=plan['engine_version'], download_dir=plan['game']),
207
+ ServerTarget(**data['network']['target']))
208
+ install.write_json(Path(plan['cppconfig']), config)
195
209
  app = Path(plan['runtime'])
196
210
  helper = app/'Contents/Resources/launcher/run_netease_dev.py'
197
211
  if not helper.is_file():
@@ -228,7 +242,7 @@ class MacOSBackend(GameBackend):
228
242
  label = 'macOS · Apple Silicon · Metal'
229
243
  capabilities = ('local-worlds', 'macos-local-worlds', 'engine-install', 'macos-engine-install',
230
244
  'status', 'logs', 'stop', 'py', 'runtime', 'runtime-ui', 'runtime-player',
231
- 'client-python-requests', 'project-ui', 'reload', 'watch')
245
+ 'client-python-requests', 'project-ui', 'reload', 'watch', 'network-sessions')
232
246
  managed_install = True
233
247
  setup_description = ('在 Apple Silicon Mac 上运行本地 Addon 测试。\n'
234
248
  '首次运行会自动下载原生启动器和网易开发者版资源,并在本机完成安装。\n'
@@ -250,6 +264,10 @@ class MacOSBackend(GameBackend):
250
264
  options.pop('no_gui', None)
251
265
  return run(project, **options)
252
266
 
267
+ def connect(self, target, **options):
268
+ from .macos_network import connect
269
+ return connect(target, **options)
270
+
253
271
  def watch_directory(self, project, data):
254
272
  return Path(data['config_path']).parent/'assembled'
255
273
 
@@ -380,7 +398,9 @@ else:
380
398
  request_id = 'startup-' + str(index)
381
399
  result = channel._rpc('result', {'request_id': request_id})
382
400
  if result.get('request_id') == request_id and result.get('state') in ('failed', 'cancelled'):
383
- raise EngineError('世界启动步骤失败:' + str(result.get('error', result['state'])), 'world_start_failed')
401
+ raise EngineError(('服务器连接未完成:' if data.get('mode') == 'network' else '世界启动步骤失败:')
402
+ + str(result.get('error', result['state'])),
403
+ 'connection_failed' if data.get('mode') == 'network' else 'world_start_failed')
384
404
  except (OSError, ConnectionError):
385
405
  pass
386
406
  try:
@@ -389,9 +409,13 @@ else:
389
409
  stream.seek(max(0, stream.tell() - 65536))
390
410
  if b'"top_screen":"hud_screen"' in stream.read():
391
411
  data['world_ready'] = True
412
+ if data.get('mode') == 'network': data['connection_verified'] = True
392
413
  return True
393
414
  except OSError:
394
415
  pass
416
+ if data.get('mode') == 'network' and time.time() - data['created_at'] > 90:
417
+ raise EngineError('90 秒内未进入服务器;请检查地址、端口、协议版本以及服务器是否允许无认证的中国版客户端。',
418
+ 'connection_timeout')
395
419
  return False
396
420
 
397
421
  def handoff(self, data):
@@ -0,0 +1,47 @@
1
+ """Anonymous server launch using the shared cppconfig and session lifecycle."""
2
+ from dataclasses import asdict
3
+ from pathlib import Path
4
+ import uuid
5
+
6
+ import click
7
+ from . import install
8
+ from .host import EngineError, require_macos
9
+
10
+
11
+ def connect(target, *, project_dir=None, packs=(), engine_overrides=None, detach=False):
12
+ from ..command_context import project_dir as current_project, json_output
13
+ from ..mcstudio import sessions
14
+ from ..remote.service import directory_lock
15
+ from .macos import ensure_runtime, pinned_runtime
16
+ require_macos()
17
+ if json_output() and not detach:
18
+ raise click.UsageError('网络运行 --json 需要 --detach;使用 status/logs/stop 管理返回的会话')
19
+ overrides = engine_overrides or {}
20
+ if target.auth != 'none' or overrides.get('game_executable_path') or overrides.get('mcs_download_path'):
21
+ raise EngineError('macOS 服务器连接仅支持无认证模式;MC Studio 身份和 EXE 参数仅适用于 Windows。',
22
+ 'unsupported_option')
23
+ root = Path(project_dir or current_project()).resolve()
24
+ with directory_lock(install.home()/'install-lock'):
25
+ state = pinned_runtime({'installation': ensure_runtime()})
26
+ if state.get('network_connect_protocol') != 1:
27
+ raise EngineError('此 macOS 运行包尚不支持连接服务器。', 'runtime_incompatible',
28
+ '请执行 mcpy engine install --catalog ' + install.DEFAULT_CATALOG + ',再重试;已有世界保留原绑定。')
29
+ version = state['profile']['apk']['version']
30
+ requested = overrides.get('engine_version')
31
+ if requested and requested != version:
32
+ raise EngineError('当前资源版本为 ' + version, 'engine_version_mismatch',
33
+ '请使用匹配的资源,或省略 --engine-version。')
34
+ session = uuid.uuid4().hex
35
+ directory = sessions.session_path(root, session)
36
+ plan = {'runtime': state['runtime'], 'game': state['game'],
37
+ 'data': str(directory/'native/data'), 'cache': str(directory/'native/cache'),
38
+ 'engine_version': version, 'compat_report': state.get('compat_report'),
39
+ 'cppconfig': str(directory/'runtime.cppconfig')}
40
+ data = sessions.start(root, backend='macos-arm64', session_id=session, launch=plan,
41
+ network={'target': asdict(target), 'engine': {'version': version}})
42
+ result = sessions.handoff(data)
43
+ if detach:
44
+ return result
45
+ click.echo(f'连接 {target.host}:{target.port}(无认证);不装配本地 Mod。')
46
+ from .tui import watch_session
47
+ return watch_session(root, session)
@@ -19,10 +19,12 @@ def watch_session(project, session):
19
19
  backend = get_backend(data.get('backend', 'windows'))
20
20
  table = Table.grid(padding=(0, 2))
21
21
  table.add_row('平台', backend.label)
22
- table.add_row('状态', '世界已就绪' if data.get('world_ready') else '引擎已启动,正在准备世界…')
22
+ network = data.get('mode') == 'network'
23
+ table.add_row('状态', ('已进入服务器' if data.get('connection_verified') else '正在连接服务器…')
24
+ if network else ('世界已就绪' if data.get('world_ready') else '引擎已启动,正在准备世界…'))
23
25
  table.add_row('会话', session)
24
26
  table.add_row('操作', 'Ctrl+C 保存退出;其他终端可使用 logs / runtime py')
25
- live.update(Panel(table, title='mcpy 本地测试'))
27
+ live.update(Panel(table, title='mcpy 服务器测试' if network else 'mcpy 本地测试'))
26
28
  time.sleep(.25)
27
29
  except KeyboardInterrupt:
28
30
  console.print('正在保存世界并关闭窗口…')
@@ -13,28 +13,34 @@ class WindowsBackend(GameBackend):
13
13
  def debug_channel(self, data, write_log):
14
14
  from ..mcstudio.runtime_debug import SafaiaChannel
15
15
  self._world_ready = data.get('mode') != 'local'
16
- self._log_offset, self._log_pending = 0, b''
16
+ self._ready_log_cursors = {}
17
17
  if data.get('mode') == 'local': data['world_ready'] = False
18
18
  return SafaiaChannel(write_log, ready=lambda: self._world_ready)
19
19
 
20
20
  def refresh(self, data, channel):
21
21
  if self._world_ready: return False
22
22
  from pathlib import Path
23
- path = Path(data['log_path'])
24
- try:
25
- with path.open('rb') as stream:
26
- stream.seek(self._log_offset)
27
- chunk = stream.read(65536)
28
- self._log_offset = stream.tell()
29
- except FileNotFoundError:
30
- return False
31
- text = self._log_pending + chunk
32
- # This engine log is emitted after client/server Python system creation.
33
- if b'OnHandlePushScreen hud_screen' in text:
34
- self._world_ready = data['world_ready'] = True
35
- self._log_pending = b''
36
- return True
37
- self._log_pending = text[-64:]
23
+ import re
24
+ for name in ('log_path', 'engine_log_path'):
25
+ if not data.get(name): continue
26
+ path = Path(data[name])
27
+ offset, pending = self._ready_log_cursors.get(name, (0, b''))
28
+ try:
29
+ with path.open('rb') as stream:
30
+ stream.seek(offset)
31
+ chunk = stream.read(65536)
32
+ offset = stream.tell()
33
+ except FileNotFoundError:
34
+ continue
35
+ text = pending + chunk
36
+ # Both markers require a created gameplay HUD. The native layout
37
+ # marker also works if the Python launch logging handler raises.
38
+ hud = (b'OnHandlePushScreen hud_screen' in text if name == 'log_path' else
39
+ re.search(rb"\bINFO GUI [^\r\n]*\] Layout main thread: [^\r\n]*controls completed [^\r\n]*for screen 'hud\.hud_screen'\.", text))
40
+ self._ready_log_cursors[name] = (offset, text[-1024:])
41
+ if hud:
42
+ self._world_ready = data['world_ready'] = True
43
+ return True
38
44
  return False
39
45
 
40
46
  def reload_restriction(self, data, kind):
@@ -89,7 +89,9 @@ def fetch_snapshot(url, ref):
89
89
  raise DependencyError('无效Git引用')
90
90
  origin = hashlib.sha256(url.encode('utf-8')).hexdigest()
91
91
  base = cache_root()
92
- snapshots = base / 'git' / origin
92
+ # Old archives may contain bytes converted by the host's Git settings.
93
+ # Keep them separate so even a verified old cache cannot reintroduce CRLF.
94
+ snapshots = base / 'git-archive-v2' / origin
93
95
  snapshots.mkdir(parents=True, exist_ok=True)
94
96
  with _source_lock(base / 'locks' / (origin + '.lock')):
95
97
  if re.fullmatch('[0-9a-f]{40}', ref) and (snapshots / ref).exists():
@@ -99,6 +101,11 @@ def fetch_snapshot(url, ref):
99
101
  repo, package = stage / 'repo', stage / 'snapshot'
100
102
  content = package / 'content'
101
103
  _git('init', '--bare', repo)
104
+ # Archive applies checkout conversions too. Preserve blob bytes,
105
+ # including binary/intentional CRLF files, on every host.
106
+ (repo / 'info').mkdir(exist_ok=True)
107
+ (repo / 'info' / 'attributes').write_bytes(
108
+ b'* -text -eol -filter -ident -working-tree-encoding\n')
102
109
  _git('-C', repo, 'fetch', '--depth=1', '--no-tags', url, ref)
103
110
  commit = _git('-C', repo, 'rev-parse', 'FETCH_HEAD^{commit}').decode().strip()
104
111
  if not re.fullmatch('[0-9a-f]{40}', commit) or (re.fullmatch('[0-9a-f]{40}', ref) and commit != ref):
@@ -108,11 +108,10 @@ def prepare_graph(root, config=None, migrate_windows_lock=False):
108
108
  root = Path(root).resolve()
109
109
  config = read_project(root) if config is None else config
110
110
  roots = declarations(root, config)
111
- expected, legacy = {}, False
111
+ expected = {}
112
112
  if (root / LOCK_FILE).exists():
113
113
  try:
114
114
  prior = json.loads((root / LOCK_FILE).read_text('utf-8'))
115
- legacy = prior['version'] == 1
116
115
  expected = {record['id']: record for record in prior['nodes']}
117
116
  except (ValueError, KeyError, TypeError) as exc:
118
117
  raise DependencyError('无法读取依赖锁文件 mcpy-git.lock.json。\n请从版本控制恢复该文件后运行 mcpy sync。') from exc
@@ -120,7 +119,7 @@ def prepare_graph(root, config=None, migrate_windows_lock=False):
120
119
  raise DependencyError('此项目需要较新版本的 mcpy,请先升级 mcpywrap。')
121
120
  registered = long_path(root / '.mcpy/git-projects')
122
121
  registered.mkdir(parents=True, exist_ok=True)
123
- nodes, active, migrations = {}, [], []
122
+ nodes, active = {}, []
124
123
  def visit(entry, supplied_source=None):
125
124
  source, commit = supplied_source or fetch_source(root, entry['git'], entry['rev'])
126
125
  project, payload, project_config, kind, target = _layout(source, entry)
@@ -138,11 +137,8 @@ def prepare_graph(root, config=None, migrate_windows_lock=False):
138
137
  active.append(key)
139
138
  source_hash = digest(source)
140
139
  previous = expected.get(key)
141
- migrating = legacy and previous is not None
142
- if previous and previous['source_sha256'] != source_hash:
143
- if not migrating or previous['source_sha256'] not in (
144
- windows_checkout_digest(source), windows_checkout_digest(source, crlf=False)):
145
- raise DependencyError(f"依赖 {entry['name']} 的源码校验失败。\n请保留 mcpy-git.lock.json,并重新获取该依赖的固定版本。")
140
+ # sync derives checksums from the pinned commit's verified snapshot.
141
+ # Old host-dependent checksums are outputs, not version constraints.
146
142
  children = []
147
143
  for child in declarations(project, project_config):
148
144
  children.append(visit(child))
@@ -197,65 +193,39 @@ def prepare_graph(root, config=None, migrate_windows_lock=False):
197
193
  from .dependencies import validate_game_files
198
194
  validate_game_files(stage, addon_directories(stage).values())
199
195
  content_hash = digest(stage)
200
- if previous and previous['sha256'] != content_hash and not migrating:
201
- raise DependencyError(f"依赖 {entry['name']} 的安装内容与锁文件不一致。\n请确认团队使用相同版本的 mcpywrap;不要删除锁文件。")
202
196
  final = registered / key
203
197
  if final.exists():
204
198
  if digest(final) != content_hash:
205
- # Old generated files may contain Windows line endings. Only
206
- # replace a verified old registration; preserve it for recovery.
207
- if not migrating or previous['sha256'] not in (
199
+ # Only replace an unchanged installation of the old lock.
200
+ if not previous or previous['sha256'] not in (
208
201
  digest(final), windows_checkout_digest(final, crlf=False)):
209
202
  raise DependencyError(f"依赖 {entry['name']} 的本地文件已修改。\n请将 {final} 移到项目外备份,再运行 mcpy sync。")
210
- backups = root/'.mcpy/registration-backups'
211
- backups.mkdir(parents=True, exist_ok=True)
212
- backup = Path(tempfile.mkdtemp(prefix=key[:12]+'-', dir=backups))/'project'
213
- os.replace(final, backup)
203
+ old = Path(temporary) / 'old'
204
+ os.replace(final, old)
214
205
  try:
215
206
  os.replace(stage, final)
216
207
  except BaseException:
217
- os.replace(backup, final)
208
+ os.replace(old, final)
218
209
  raise
219
210
  else:
220
211
  os.replace(stage, final)
221
212
  record = {'id': key, 'source': identity, 'source_sha256': source_hash, 'sha256': content_hash,
222
213
  'kind': kind, 'target': target, 'children': list(dict.fromkeys(children))}
223
- if migrating:
224
- migrations.append({'node': key, 'name': entry['name'], 'git': entry['git'], 'rev': commit,
225
- 'old_source_sha256': previous['source_sha256'], 'source_sha256': source_hash,
226
- 'old_registered_sha256': previous['sha256'], 'registered_sha256': content_hash})
227
214
  nodes[key] = record
228
215
  active.pop()
229
216
  return key
230
217
  root_ids = [visit(entry) for entry in roots]
231
- lock = {'version': 2, 'declarations': roots, 'roots': root_ids, 'nodes': list(nodes.values())}
232
- if migrations:
233
- lock['_lock_migrations'] = migrations
234
- return lock
218
+ return {'version': 2, 'declarations': roots, 'roots': root_ids, 'nodes': list(nodes.values())}
235
219
 
236
220
 
237
221
  def write_lock(root, lock):
238
- data = dict(lock)
239
- migrations = data.pop('_lock_migrations', [])
240
- report = []
241
- if migrations and (Path(root)/LOCK_FILE).exists():
242
- old = (Path(root)/LOCK_FILE).read_bytes()
243
- backup = Path(root)/'.mcpy/lock-backups'/(hashlib.sha256(old).hexdigest()+'.json')
244
- backup.parent.mkdir(parents=True, exist_ok=True)
245
- if backup.exists() and backup.read_bytes() != old:
246
- raise DependencyError('无法备份旧依赖记录,原锁文件未修改。')
247
- backup.write_bytes(old)
248
- report.append({'project': str(root), 'backup': str(backup), 'nodes': migrations})
249
- _write_json(Path(root) / LOCK_FILE, data)
250
- return report
222
+ _write_json(Path(root) / LOCK_FILE, lock)
251
223
 
252
224
 
253
225
  def sync_projects(root, config=None, migrate_windows_lock=False, migration_report=None):
254
- # Kept for older callers; recognized legacy locks now migrate automatically.
255
- lock = prepare_graph(root, config, migrate_windows_lock=migrate_windows_lock)
256
- report = write_lock(root, lock)
257
- if migration_report is not None:
258
- migration_report.extend(report)
226
+ # Legacy arguments are retained for callers; sync simply recalculates the lock.
227
+ lock = prepare_graph(root, config)
228
+ write_lock(root, lock)
259
229
  return len(lock['nodes'])
260
230
 
261
231
 
@@ -11,9 +11,9 @@
11
11
  "Loader.c": "f2f1f781ce82fc30c62a6ce22bb048d3b023f6ff500e6ec8228da28b4dbf1b4a"
12
12
  },
13
13
  "files": {
14
- "Injector.exe": "d63c7fcf4061bad6381c14a3f60c7ad78e99e71536dcc82c81fc9e259bea6c53",
15
- "Loader.dll": "4e5cdb48778866c66da75b9a560b9513624c1b8cf3b2c6b0e52c5c3145eac91a",
16
- "McpyMcsAuth.dll": "30a7a1bf74c7a049e145442baf94870072033d726d83449c7c8a1b9e29438c04",
14
+ "Injector.exe": "aa185d69f11e138df90f446c239d075ef6ab4e5c5e0457981a6c09f52e04b7b9",
15
+ "Loader.dll": "03956417da34f569dd6e794076f2252ff05cb286fd1a8dc6ce966dc78911330e",
16
+ "McpyMcsAuth.dll": "b1270f169319e8acebca7d49be966d54c769590d708157c3984e33f9a943c1d7",
17
17
  "publisher.cer": "e5aa2ac39df40804ed8b08fe8e43c4a2b8923f0d02a360708486b2db5b6303d7"
18
18
  }
19
19
  }
@@ -314,6 +314,8 @@ class _ControlHandler(socketserver.BaseRequestHandler):
314
314
  request = json.loads(recv_exact(self.request, size).decode('utf-8'))
315
315
  if request.get('token') != self.server.token:
316
316
  raise ValueError('会话控制凭据无效')
317
+ if self.server.mode == 'network' and (request.get('side', 'client') != 'client' or request.get('action') == 'reload'):
318
+ raise ValueError('网络会话仅支持客户端调试,不支持服务端 Python 或本地 Mod 热更')
317
319
  if request.get('action') == 'execute':
318
320
  options = {'condition': request['condition']} if request.get('condition') is not None else {}
319
321
  if options and not hasattr(self.server.channel, 'submit'):
@@ -357,8 +359,8 @@ class RuntimeControlServer(socketserver.ThreadingTCPServer):
357
359
  daemon_threads = True
358
360
  allow_reuse_address = True
359
361
 
360
- def __init__(self, channel, token, project=None, session=None):
361
- self.channel, self.token = channel, token
362
+ def __init__(self, channel, token, project=None, session=None, mode='local'):
363
+ self.channel, self.token, self.mode = channel, token, mode
362
364
  self.input_manager = None
363
365
  if project is not None and session is not None:
364
366
  from .host_input import HostInputManager
@@ -57,12 +57,12 @@ def run(project, session):
57
57
  debug_channel = backend.debug_channel(data, receiver._write)
58
58
  debug_channel.start(process.pid)
59
59
  token = uuid.uuid4().hex
60
- control_server = RuntimeControlServer(debug_channel, token, project, session)
60
+ control_server = RuntimeControlServer(debug_channel, token, project, session, mode=data.get('mode', 'local'))
61
61
  control_server.start()
62
62
  sessions.save(directory/'control.json', {'port': control_server.server_address[1],
63
63
  'token': token,
64
64
  'unified_input_worker': True,
65
- 'python_reload_sides': ['client', 'server'],
65
+ 'python_reload_sides': [] if data.get('mode') == 'network' else ['client', 'server'],
66
66
  'client_python_queue': callable(getattr(debug_channel, 'submit', None))})
67
67
  data.update(state='running', game=identity(process.pid))
68
68
  sessions.save(path, data)
@@ -114,7 +114,7 @@ def handoff(data):
114
114
  target = data['network']['target']
115
115
  result.update(host=target['host'], port=target['port'], identity_source=target['auth'],
116
116
  identity_provided=data['mcs_auth'], authenticated=False,
117
- connection_verified=False, addons_assembled=False,
117
+ connection_verified=data.get('connection_verified', False), addons_assembled=False,
118
118
  engine_version=data['network']['engine']['version'])
119
119
  return result
120
120
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "executable_sha256": "2a37e9282aac2a242169be0f5ac499aabf443aacff2c7aa92867d9570552089d",
2
+ "executable_sha256": "4f4f8b0a8eae4b8d409ae90e222886623c4b53ed66525d8267ffaadc1db65442",
3
3
  "source_sha256": "0c0b2a02bfecea68b824a486ac1d88cde6bd9fc8ee8e4c9a6939870bd8e710d1",
4
4
  "sources": {
5
5
  "video_capture.h": "8daee1b8c1c8273bc8d19d39f0559f82c30d7ff900894bcc2cc197d3881532d7",
@@ -144,6 +144,8 @@ def open_ui_crate_mod(behavior_pack_path, run_event_loop=True):
144
144
 
145
145
  # 主窗口
146
146
  window = QWidget()
147
+ # Closed embedded editors must release their signal closures before Qt exits.
148
+ window.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
147
149
  window.setWindowTitle("Minecraft Mod 创建工具")
148
150
  window.setMinimumWidth(900) # 增加窗口宽度以适应目录树
149
151
  window.setMinimumHeight(600) # 设置窗口最小高度
@@ -68,7 +68,10 @@ def _git(*args):
68
68
  return value[4:]
69
69
  return value
70
70
  try:
71
- result = subprocess.run(['git', '-c', 'core.hooksPath=', '-c', 'core.longpaths=true', *map(argument, args)],
71
+ result = subprocess.run(['git', '-c', 'core.hooksPath=', '-c', 'core.longpaths=true',
72
+ '-c', 'core.autocrlf=false', '-c', 'core.eol=lf',
73
+ '-c', 'core.attributesFile=' + os.devnull, *map(argument, args)],
74
+ env=dict(os.environ, GIT_ATTR_NOSYSTEM='1'),
72
75
  capture_output=True, timeout=180)
73
76
  except (OSError, subprocess.TimeoutExpired) as exc:
74
77
  raise DependencyError(f'无法获取代码库: {exc}') from exc