mcpywrap-core 0.6.0__tar.gz → 0.6.2__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 (255) hide show
  1. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/PKG-INFO +1 -1
  2. mcpywrap_core-0.6.2/mcpywrap_core/_process_identity.py +55 -0
  3. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/_script_worker.py +15 -12
  4. mcpywrap_core-0.6.2/mcpywrap_core/_version.py +1 -0
  5. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/api.py +22 -8
  6. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/dependency_manager.py +4 -3
  7. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/project_builder.py +3 -1
  8. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/cli.py +1 -1
  9. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/command_context.py +25 -0
  10. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/connect_cmd.py +7 -4
  11. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/desktop_cmd.py +5 -1
  12. mcpywrap_core-0.6.2/mcpywrap_core/commands/host_cmd.py +68 -0
  13. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/modsdk_cmd.py +11 -3
  14. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/run_cmd.py +10 -3
  15. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/script_cmd.py +3 -15
  16. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/session_cmd.py +46 -4
  17. mcpywrap_core-0.6.2/mcpywrap_core/commands/skill_cmd.py +34 -0
  18. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/backend.py +3 -2
  19. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/install.py +1 -1
  20. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/macos.py +12 -4
  21. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/macos_network.py +4 -2
  22. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/windows.py +6 -4
  23. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/git_cache.py +15 -9
  24. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/host.py +17 -0
  25. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/host_assets.py +1 -1
  26. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/game.py +27 -3
  27. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/host_input.py +4 -0
  28. mcpywrap_core-0.6.2/mcpywrap_core/mcstudio/log_query.py +75 -0
  29. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/log_stream.py +9 -3
  30. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/network.py +5 -4
  31. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/processes.py +9 -6
  32. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/screenshot.py +1 -0
  33. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/session_worker.py +3 -0
  34. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/sessions.py +5 -3
  35. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/window.py +6 -1
  36. mcpywrap_core-0.6.2/mcpywrap_core/mcstudio/window_activation.py +56 -0
  37. mcpywrap_core-0.6.2/mcpywrap_core/minecraft/modsdk_versions.py +49 -0
  38. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/netease_modsdk.py +3 -32
  39. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/__init__.py +2 -0
  40. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/client.py +232 -0
  41. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/intervention.py +41 -0
  42. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/options.py +35 -0
  43. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/preparation.py +173 -0
  44. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/profiles.py +116 -0
  45. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/receipts.py +85 -0
  46. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/routing.py +80 -0
  47. mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/streams.py +32 -0
  48. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/build.py +9 -3
  49. mcpywrap_core-0.6.2/mcpywrap_core/operations/input_snapshot.py +37 -0
  50. mcpywrap_core-0.6.2/mcpywrap_core/operations/project.py +61 -0
  51. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/project_init.py +4 -1
  52. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/project_scripts.py +33 -4
  53. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/client.py +16 -2
  54. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/service.py +4 -1
  55. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/deployment_service.py +1 -0
  56. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/groups.py +10 -5
  57. mcpywrap_core-0.6.2/mcpywrap_core/runtime/window_policy.py +36 -0
  58. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/windows.py +6 -4
  59. mcpywrap_core-0.6.2/mcpywrap_core/skill/SKILL.md +60 -0
  60. mcpywrap_core-0.6.2/mcpywrap_core/skill/agents/openai.yaml +4 -0
  61. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/CatalogRuntime/__init__.py +3 -0
  62. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/manifest.json +5 -0
  63. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/example.toml +2 -0
  64. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/prepare.py +53 -0
  65. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/schema.json +9 -0
  66. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/pyproject.toml +18 -0
  67. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/.gitignore +6 -0
  68. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/behavior_pack/manifest.json +5 -0
  69. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/data/items.json +1 -0
  70. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/pyproject.toml +12 -0
  71. mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/resource_pack/manifest.json +5 -0
  72. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/application-host.md +88 -0
  73. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/capture.md +49 -0
  74. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/consumer-prepare-author.md +76 -0
  75. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/consumer-prepare.md +91 -0
  76. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/dual-clients.md +93 -0
  77. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/input-sequence.md +87 -0
  78. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/input-writing.md +213 -0
  79. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/instance-world-settings.md +52 -0
  80. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/local-development.md +86 -0
  81. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/macos-local.md +70 -0
  82. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/network-sessions.md +46 -0
  83. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/project-dependencies.md +131 -0
  84. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/project-scripts.md +104 -0
  85. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/project-workflow.md +50 -0
  86. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/remote-testing.md +52 -0
  87. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-debug.md +59 -0
  88. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-input.md +134 -0
  89. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-key-timeline.md +78 -0
  90. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-player.md +85 -0
  91. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-ui.md +79 -0
  92. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/script-example.md +44 -0
  93. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/setup.md +78 -0
  94. mcpywrap_core-0.6.2/mcpywrap_core/skill/references/troubleshooting.md +83 -0
  95. mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/bootstrap.ps1 +31 -0
  96. mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/bootstrap.py +225 -0
  97. mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/game_window.py +60 -0
  98. mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/smoke.py +238 -0
  99. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/PKG-INFO +1 -1
  100. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/SOURCES.txt +19 -0
  101. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/pyproject.toml +1 -1
  102. mcpywrap_core-0.6.2/setup.py +28 -0
  103. mcpywrap_core-0.6.0/mcpywrap_core/_version.py +0 -1
  104. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/LICENSE +0 -0
  105. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/MANIFEST.in +0 -0
  106. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/README.md +0 -0
  107. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/THIRD_PARTY_NOTICES.md +0 -0
  108. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/__init__.py +0 -0
  109. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/__main__.py +0 -0
  110. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/AddonsPack.py +0 -0
  111. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/MapPack.py +0 -0
  112. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/__init__.py +0 -0
  113. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/assembly.py +0 -0
  114. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/file_merge.py +0 -0
  115. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/watcher.py +0 -0
  116. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/code_libraries.py +0 -0
  117. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/__init__.py +0 -0
  118. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/add_cmd.py +0 -0
  119. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/build_cmd.py +0 -0
  120. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/default_cmd.py +0 -0
  121. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/dependency_prompt.py +0 -0
  122. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/deployment_cmd.py +0 -0
  123. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/deployment_output.py +0 -0
  124. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/dev_cmd.py +0 -0
  125. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/doctor_cmd.py +0 -0
  126. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/edit_cmd.py +0 -0
  127. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/engine_cmd.py +0 -0
  128. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/init_cmd.py +0 -0
  129. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/mod_cmd.py +0 -0
  130. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/package_cmd.py +0 -0
  131. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/publish_cmd.py +0 -0
  132. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/record_cmd.py +0 -0
  133. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/remove_cmd.py +0 -0
  134. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_cmd.py +0 -0
  135. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_input_cmd.py +0 -0
  136. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_player_cmd.py +0 -0
  137. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_ui_cmd.py +0 -0
  138. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/serve_cmd.py +0 -0
  139. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/sync_cmd.py +0 -0
  140. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/ui_cmd.py +0 -0
  141. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/config.py +0 -0
  142. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/consumer_prepare.py +0 -0
  143. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/core-contract.json +0 -0
  144. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/custom_packaging.py +0 -0
  145. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/dependencies.py +0 -0
  146. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/__init__.py +0 -0
  147. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/android_icon.py +0 -0
  148. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/apk_profile.py +0 -0
  149. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/host.py +0 -0
  150. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/macos_icons.py +0 -0
  151. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/official.py +0 -0
  152. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/preferences.py +0 -0
  153. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/tui.py +0 -0
  154. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/framework_presets.py +0 -0
  155. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/frameworks.py +0 -0
  156. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/git_projects.py +0 -0
  157. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/input_executor.py +0 -0
  158. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/input_plan.py +0 -0
  159. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/SimpleMonitor.py +0 -0
  160. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/__init__.py +0 -0
  161. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/auth_interaction.py +0 -0
  162. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/bridge_assets.py +0 -0
  163. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/deployment_links.py +0 -0
  164. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/diagnostics.py +0 -0
  165. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/discovery.py +0 -0
  166. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/editor.py +0 -0
  167. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/file_logs.py +0 -0
  168. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/hot_reload.py +0 -0
  169. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/image.py +0 -0
  170. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/input_sequence.py +0 -0
  171. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/launcher_control.py +0 -0
  172. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/launcher_python.py +0 -0
  173. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/log_colorizer.py +0 -0
  174. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/log_protocol.py +0 -0
  175. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/mcs.py +0 -0
  176. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/mcs_auth.py +0 -0
  177. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/private_logs.py +0 -0
  178. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/recording_worker.py +0 -0
  179. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/recordings.py +0 -0
  180. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_cppconfig.py +0 -0
  181. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_debug.py +0 -0
  182. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_input_payload.py +0 -0
  183. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_key_timeline_payload.py +0 -0
  184. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_player_payload.py +0 -0
  185. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_ui.py +0 -0
  186. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_ui_outline.py +0 -0
  187. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_ui_payload.py +0 -0
  188. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/session_capabilities.py +0 -0
  189. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/studio_server.py +0 -0
  190. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/studio_server_ui.py +0 -0
  191. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/symlink_helper_global.py +0 -0
  192. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/symlink_helper_map.py +0 -0
  193. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/symlinks.py +0 -0
  194. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/unified_input.py +0 -0
  195. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/__init__.py +0 -0
  196. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/addons.py +0 -0
  197. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/level_dat.py +0 -0
  198. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/map.py +0 -0
  199. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/modsdk_environment.py +0 -0
  200. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/template/generate_mod_files.py +0 -0
  201. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/template/mod_template.py +0 -0
  202. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/__init__.py +0 -0
  203. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/package.py +0 -0
  204. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/sync.py +0 -0
  205. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/prepare_environment.py +0 -0
  206. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/project_dependencies.py +0 -0
  207. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/recording_io.py +0 -0
  208. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/__init__.py +0 -0
  209. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/http_server.py +0 -0
  210. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/protocol.py +0 -0
  211. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/__init__.py +0 -0
  212. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/deployment.py +0 -0
  213. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/deployment_inspection.py +0 -0
  214. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/diagnostics.py +0 -0
  215. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/instances.py +0 -0
  216. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/native.py +0 -0
  217. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/occupancy.py +0 -0
  218. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/preparation.py +0 -0
  219. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/profiles.py +0 -0
  220. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/storage.py +0 -0
  221. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/targets.py +0 -0
  222. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/watch.py +0 -0
  223. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/source_files.py +0 -0
  224. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/timeline.py +0 -0
  225. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/__init__.py +0 -0
  226. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/application.py +0 -0
  227. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icns +0 -0
  228. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icon/Assets/01-cube.svg +0 -0
  229. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icon/Assets/02-prompt.svg +0 -0
  230. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icon/icon.json +0 -0
  231. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.png +0 -0
  232. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/engine_setup.py +0 -0
  233. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/log_view.py +0 -0
  234. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/log_window.py +0 -0
  235. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/project_ui.py +0 -0
  236. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/run_session.py +0 -0
  237. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/session_controller.py +0 -0
  238. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/session_tools.py +0 -0
  239. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/session_views.py +0 -0
  240. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/world_config.py +0 -0
  241. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/__init__.py +0 -0
  242. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/console.py +0 -0
  243. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/link_snapshot.py +0 -0
  244. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/locks.py +0 -0
  245. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/paths.py +0 -0
  246. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/pip_error_parser.py +0 -0
  247. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/print_guide.py +0 -0
  248. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/project_setup.py +0 -0
  249. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/utils.py +0 -0
  250. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/dependency_links.txt +0 -0
  251. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/requires.txt +0 -0
  252. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/top_level.txt +0 -0
  253. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_host/__init__.py +0 -0
  254. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_host/store.py +0 -0
  255. {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcpywrap-core
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Shared mcpy project dependency, build and runtime logic
5
5
  Author-email: boybook <boybook@easecation.net>
6
6
  License-Expression: MIT
@@ -0,0 +1,55 @@
1
+ """Process identity without requiring system-wide boot statistics on restricted Linux."""
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+ import psutil
6
+
7
+
8
+ def proc_stat(pid):
9
+ # comm may contain spaces and parentheses; numeric fields start after its final ')'.
10
+ raw = Path('/proc/%d/stat' % pid).read_text().rsplit(')', 1)[1].split()
11
+ return {'pid': pid, 'state': raw[0], 'parent': int(raw[1]), 'group': int(raw[2]), 'start_ticks': int(raw[19])}
12
+
13
+
14
+ def identity(pid):
15
+ process = psutil.Process(pid)
16
+ try:
17
+ created = process.create_time()
18
+ except psutil.AccessDenied:
19
+ if not sys.platform.startswith('linux'):
20
+ raise
21
+ # Android and other restricted procfs mounts expose our per-process stat,
22
+ # but not /proc/stat (which psutil needs to convert ticks to wall-clock time).
23
+ stat = proc_stat(pid)
24
+ return {'pid': pid, 'start_ticks': stat['start_ticks'], 'executable': process.exe()}
25
+ return {'pid': pid, 'created_at': created, 'executable': process.exe()}
26
+
27
+
28
+ def alive(record):
29
+ try:
30
+ if 'start_ticks' in record:
31
+ stat = proc_stat(record['pid'])
32
+ return stat['start_ticks'] == record['start_ticks'] and stat['state'] != 'Z'
33
+ process = psutil.Process(record['pid'])
34
+ return abs(process.create_time()-record['created_at']) < .01 and process.status() != psutil.STATUS_ZOMBIE
35
+ except (psutil.NoSuchProcess, FileNotFoundError, ProcessLookupError):
36
+ return False
37
+
38
+
39
+ def proc_members(group, known):
40
+ """Inspect readable per-process records, never require visibility of other UIDs."""
41
+ rows = {}
42
+ for directory in Path('/proc').iterdir():
43
+ if not directory.name.isdigit(): continue
44
+ try: rows[int(directory.name)] = proc_stat(int(directory.name))
45
+ except (OSError, ValueError, IndexError): continue
46
+ owned = {record['pid'] for record in known.values() if alive(record)}
47
+ found = {pid for pid, row in rows.items() if row['group'] == group and row['state'] != 'Z'}
48
+ while True:
49
+ descendants = {pid for pid, row in rows.items() if row['parent'] in owned | found and row['state'] != 'Z'}
50
+ if descendants <= found: break
51
+ found |= descendants
52
+ for pid in found:
53
+ try: known[pid] = identity(pid)
54
+ except (psutil.NoSuchProcess, OSError): pass
55
+ return [record for record in known.values() if alive(record)]
@@ -10,17 +10,11 @@ import time
10
10
  import psutil
11
11
 
12
12
 
13
- def identity(pid):
14
- p = psutil.Process(pid)
15
- return {'pid': pid, 'created_at': p.create_time(), 'executable': p.exe()}
16
-
17
-
18
- def alive(record):
19
- try:
20
- p = psutil.Process(record['pid'])
21
- return abs(p.create_time() - record['created_at']) < .01 and p.status() != psutil.STATUS_ZOMBIE
22
- except psutil.NoSuchProcess:
23
- return False
13
+ # Also works when this supervisor is executed directly by absolute filename.
14
+ try:
15
+ from ._process_identity import identity, alive, proc_members
16
+ except ImportError:
17
+ from _process_identity import identity, alive, proc_members
24
18
 
25
19
 
26
20
  def save(path, data):
@@ -149,6 +143,8 @@ class WindowsJob:
149
143
 
150
144
 
151
145
  def members(process, known):
146
+ if any('start_ticks' in record for record in known.values()):
147
+ return proc_members(process.pid, known)
152
148
  try:
153
149
  for p in psutil.Process(process.pid).children(recursive=True):
154
150
  known[p.pid] = identity(p.pid)
@@ -229,7 +225,14 @@ def main():
229
225
  try:
230
226
  child = identity(process.pid)
231
227
  except psutil.ZombieProcess:
232
- child = {'pid': process.pid, 'created_at': psutil.Process(process.pid).create_time(), 'executable': command[0]}
228
+ try:
229
+ child = {'pid': process.pid, 'created_at': psutil.Process(process.pid).create_time(), 'executable': command[0]}
230
+ except psutil.AccessDenied:
231
+ try:
232
+ from ._process_identity import proc_stat
233
+ except ImportError:
234
+ from _process_identity import proc_stat
235
+ child = {'pid': process.pid, 'start_ticks': proc_stat(process.pid)['start_ticks'], 'executable': command[0]}
233
236
  state.update(child=child, group=process.pid if os.name != 'nt' else None, state='running')
234
237
  known[process.pid] = child
235
238
  save(path, state)
@@ -0,0 +1 @@
1
+ __version__ = '0.6.2'
@@ -1,11 +1,13 @@
1
1
  """Versioned engineering API. All frontends call the same operation functions."""
2
2
  from pathlib import Path
3
+ import click
3
4
 
4
5
  from . import __version__
5
6
  from .host import HOST_API, HostServices, host_scope, current_host
6
7
 
7
8
  CORE_API = 1
8
- OPERATIONS = ('resolve', 'build', 'package', 'sync')
9
+ from .operations.project import OPTIONS
10
+ OPERATIONS = ('resolve', 'build', 'package', 'sync', *OPTIONS)
9
11
 
10
12
 
11
13
  class Core:
@@ -20,16 +22,28 @@ class Core:
20
22
  """Execute or raise the original structured error; never invoke another core."""
21
23
  if operation not in OPERATIONS:
22
24
  raise ValueError('未知工程操作:' + str(operation))
23
- allowed = {'resolve': set(), 'build': {'merge'}, 'package': {'merge'},
25
+ allowed = OPTIONS[operation] if operation in OPTIONS else {'resolve': set(), 'build': {'merge'}, 'package': {'merge'},
24
26
  'sync': {'install', 'migrate_windows_lock'}}[operation]
25
- if set(options) - allowed or any(type(value) is not bool for value in options.values()):
27
+ if set(options) - allowed or (operation not in OPTIONS and any(type(value) is not bool for value in options.values())):
26
28
  raise ValueError('无效的工程操作参数')
29
+ if operation in OPTIONS:
30
+ for name,value in options.items():
31
+ valid = (type(value) is bool if name=='effective' else
32
+ isinstance(value,(list,tuple)) and all(isinstance(item,str) for item in value) if name=='args' else
33
+ value is None or isinstance(value,str))
34
+ if not valid: raise ValueError('无效的工程操作参数:'+name)
35
+ if operation=='script.run' and not options.get('name'):
36
+ raise ValueError('运行项目脚本需要 name')
27
37
  project = Path(project).expanduser().resolve()
28
- if not project.is_dir() or not (project/'pyproject.toml').is_file():
38
+ if not project.is_dir() or (operation != 'init' and not (project/'pyproject.toml').is_file()):
29
39
  raise ValueError('未找到项目 pyproject.toml:' + str(project))
30
40
  from .command_context import project_scope
31
41
  with host_scope(self.host), project_scope(project) as context:
32
- if operation == 'resolve':
42
+ if operation in OPTIONS:
43
+ from .operations.project import execute
44
+ context.non_interactive = True
45
+ result = execute(operation, project, options)
46
+ elif operation == 'resolve':
33
47
  from .dependencies import DependencyService
34
48
  manager = DependencyService(project).resolve()
35
49
  result = {'project': str(project), 'dependencies': [
@@ -60,10 +74,10 @@ class Core:
60
74
  if request.get('operation') == 'describe':
61
75
  return {'ok': True, 'result': self.describe()}
62
76
  options = request.get('options', {})
63
- if not isinstance(options, dict) or not isinstance(request.get('project'), str):
77
+ if not isinstance(options, dict) or not all(isinstance(key,str) for key in options) or not isinstance(request.get('project'), str):
64
78
  raise ValueError('无效的工程请求')
65
79
  result = self.execute(request.get('operation'), request['project'], **options)
66
80
  return {'ok': True, 'result': result, 'core': self.describe()}
67
- except (ValueError, OSError) as error:
68
- return {'ok': False, 'error': str(error), 'code': getattr(error, 'code', 'operation_failed'),
81
+ except (ValueError, OSError, click.ClickException) as error:
82
+ return {'ok': False, 'error': error.format_message() if isinstance(error,click.ClickException) else str(error), 'code': getattr(error, 'code', 'operation_failed'),
69
83
  'hint': getattr(error, 'hint', None), 'details': getattr(error, 'details', {})}
@@ -3,6 +3,7 @@ import hashlib
3
3
  import json
4
4
  import re
5
5
  from importlib import metadata
6
+ from ..host import distribution, marker_environment
6
7
  from functools import cached_property
7
8
  from pathlib import Path
8
9
  from urllib.parse import urlsplit
@@ -73,7 +74,7 @@ class DependencyManager:
73
74
 
74
75
  def find_dependency_path(self, package_name):
75
76
  try:
76
- dist = metadata.distribution(Requirement(package_name).name)
77
+ dist = distribution(Requirement(package_name).name)
77
78
  except (metadata.PackageNotFoundError, InvalidRequirement):
78
79
  return None
79
80
  direct = dist.read_text('direct_url.json')
@@ -118,11 +119,11 @@ class DependencyManager:
118
119
  requirement = Requirement(entry.value)
119
120
  except InvalidRequirement as exc:
120
121
  raise DependencyError(f'无效的 Python 包声明: {entry.value}') from exc
121
- if requirement.marker and not requirement.marker.evaluate():
122
+ if requirement.marker and not requirement.marker.evaluate(environment=marker_environment()):
122
123
  self._status(source, entry, 'inactive', '当前工具 Python 环境不满足环境标记,未启用;不代表游戏兼容性。')
123
124
  continue
124
125
  try:
125
- dist = metadata.distribution(requirement.name)
126
+ dist = distribution(requirement.name)
126
127
  except metadata.PackageNotFoundError:
127
128
  self._missing(source, entry.value, '尚未安装')
128
129
  continue
@@ -93,7 +93,9 @@ class AddonProjectBuilder:
93
93
  staged_addon = AddonsPack(self.project_name, stage)
94
94
  for kind in ('behavior', 'resource'):
95
95
  setattr(staged_addon, kind + '_pack_dir', str(stage / Path(getattr(self.target_addon, kind + '_pack_dir')).name))
96
- assemble_addon(self.packs, staged_addon)
96
+ from ..operations.input_snapshot import stable_inputs
97
+ with stable_inputs(self.dependency_manager):
98
+ assemble_addon(self.packs, staged_addon)
97
99
  if target.exists():
98
100
  _replace_with_retry(target, backup)
99
101
  try:
@@ -7,7 +7,7 @@ from .command_context import OperationGroup, configure_context
7
7
 
8
8
  COMMANDS = {
9
9
  name: (name + '_cmd', name + '_cmd') for name in
10
- ('modsdk', 'init', 'add', 'remove', 'build', 'dev', 'package', 'publish',
10
+ ('host', 'skill', 'modsdk', 'init', 'add', 'remove', 'build', 'dev', 'package', 'publish',
11
11
  'mod', 'run', 'connect', 'edit', 'ui', 'doctor', 'engine', 'sync', 'serve', 'record', 'script', 'deployment')
12
12
  }
13
13
  COMMANDS.update({name: ('session_cmd', name + '_cmd') for name in ('status', 'logs', 'stop', 'restart')})
@@ -86,6 +86,16 @@ def require_project():
86
86
  raise click.ClickException('未找到 pyproject.toml;请先执行 mcpy init --type addon 或 --type map')
87
87
 
88
88
 
89
+ def restart_handoff(result, previous):
90
+ if not isinstance(result,dict) or not previous: return result
91
+ current=result.get('session')
92
+ if current and current!=previous:
93
+ result=dict(result,previous_session=previous,session_changed=True)
94
+ message='会话已更换;后续日志、截图和调试请使用 --session '+current+'。旧会话记录仍保留。'
95
+ result['hint']=((result.get('hint') or '')+' '+message).strip()
96
+ return result
97
+
98
+
89
99
  class OperationCommand(click.Command):
90
100
  def __init__(self, *args, local_only=False, **kwargs):
91
101
  self.local_only = local_only
@@ -94,10 +104,25 @@ class OperationCommand(click.Command):
94
104
  help='输出单个 JSON 结果'))
95
105
 
96
106
  def invoke(self, ctx):
107
+ try:
108
+ result=self._invoke_operation(ctx)
109
+ except (ValueError,OSError) as error:
110
+ if ctx.info_name=='restart' and getattr(error,'details',None):
111
+ error.details=restart_handoff(error.details,ctx.params.get('session'))
112
+ if error.details.get('session_changed'):
113
+ error.hint=((getattr(error,'hint',None) or '')+' '+error.details['hint']).strip()
114
+ raise
115
+ return restart_handoff(result,ctx.params.get('session')) if ctx.info_name=='restart' else result
116
+
117
+ def _invoke_operation(self, ctx):
97
118
  ctx.params.pop('json_output', None)
98
119
  if os.environ.get('_MCPY_SCRIPT_CHAIN') and ctx.info_name in ('run', 'build', 'package', 'dev', 'restart', 'edit', 'sync', 'add', 'remove'):
99
120
  from .project_scripts import reject_recursion
100
121
  reject_recursion()
122
+ from .mobile_host.routing import route
123
+ routed, result = route(ctx)
124
+ if routed:
125
+ return result
101
126
  if self.local_only or (ctx.parent and ctx.parent.info_name == 'engine'):
102
127
  return super().invoke(ctx)
103
128
  from .remote.client import routed_command
@@ -4,17 +4,19 @@ import click
4
4
  from ..command_context import OperationCommand
5
5
  from ..mcstudio.discovery import engine_options
6
6
  from ..mcstudio.network import ServerTarget
7
+ from ..runtime.window_policy import window_options, resolve_background
7
8
 
8
9
 
9
10
  @click.command(cls=OperationCommand)
10
11
  @engine_options
12
+ @window_options
11
13
  @click.argument('host')
12
14
  @click.option('--port', type=click.IntRange(1, 65535), default=19132, show_default=True)
13
15
  @click.option('--mcs-auth', is_flag=True, help='本次连接使用已登录的 MC Studio 身份')
14
16
  @click.option('--detach', is_flag=True, help='后台连接并返回游戏会话;JSON 必须指定')
15
17
  @click.option('--clients', type=click.IntRange(1, 2), default=1, show_default=True)
16
18
  @click.option('--request-id', help='远程启动的 32 位十六进制请求 ID,用于找回或重试同一请求')
17
- def connect_cmd(host, port, mcs_auth=False, detach=False, request_id=None, clients=1, **engine_overrides):
19
+ def connect_cmd(host, port, mcs_auth=False, detach=False, request_id=None, clients=1, background=None, **engine_overrides):
18
20
  """尝试连接服务器;前台采集日志,Ctrl+C 结束本次游戏。
19
21
 
20
22
  默认不读取登录身份;--mcs-auth 要求 MCS 已登录及本机桥接可用。
@@ -26,21 +28,22 @@ def connect_cmd(host, port, mcs_auth=False, detach=False, request_id=None, clien
26
28
  from ..engines.host import EngineError
27
29
  backend = get_backend()
28
30
  target = ServerTarget(host, port, 'mcs' if mcs_auth else 'none')
31
+ background = resolve_background(background, detach=detach)
29
32
  if clients == 2:
30
33
  from ..runtime import groups
31
34
  from ..command_context import project_dir, json_output
32
35
  if json_output() and not detach:
33
36
  raise click.UsageError('connect --json 需要 --detach')
34
- result = groups.start_network(project_dir(), backend, target, engine_overrides)
37
+ result = groups.start_network(project_dir(), backend, target, engine_overrides, background=background)
35
38
  if detach or result.get('ok') is False:
36
39
  return result
37
40
  from ..engines.tui import watch_group
38
41
  return watch_group(project_dir(), result)
39
42
  try:
40
- return backend.connect(target, engine_overrides=engine_overrides, detach=detach)
43
+ return backend.connect(target, engine_overrides=engine_overrides, detach=detach, background=background)
41
44
  except EngineError as error:
42
45
  if error.code != 'setup_required' or not backend.managed_install: raise
43
46
  from .engine_cmd import perform_install
44
47
  click.echo('首次运行:自动下载并准备本地游戏环境…', err=True)
45
48
  perform_install()
46
- return backend.connect(target, engine_overrides=engine_overrides, detach=detach)
49
+ return backend.connect(target, engine_overrides=engine_overrides, detach=detach, background=background)
@@ -6,6 +6,10 @@ from ..command_context import OperationCommand, project_dir
6
6
 
7
7
 
8
8
  def output_path(value):
9
+ if value is None:
10
+ import datetime
11
+ import uuid
12
+ value = project_dir() / 'captures' / ('game-'+datetime.datetime.now().strftime('%Y%m%d-%H%M%S')+'-'+uuid.uuid4().hex[:8]+'.png')
9
13
  path = Path(value).expanduser().resolve()
10
14
  if path.suffix.lower() != '.png' or path.exists():
11
15
  raise ValueError('输出必须是尚不存在的 .png 文件')
@@ -27,7 +31,7 @@ class KeyCommand(OperationCommand):
27
31
 
28
32
  @click.command(cls=OperationCommand)
29
33
  @click.option('--session', required=True)
30
- @click.option('--output', required=True, help='本机 PNG 路径,不覆盖已有文件')
34
+ @click.option('--output', help='本机 PNG 路径,默认保存到作品 captures/,不覆盖已有文件')
31
35
  @click.option('--background-only', is_flag=True, help='仅后台捕获;失败不激活游戏或回退到前台')
32
36
  def screenshot_cmd(session, output, background_only=False):
33
37
  """保存游戏 PNG;macOS 由 Launcher 按需读帧,远程模式下载 Windows 截图。"""
@@ -0,0 +1,68 @@
1
+ """Pair a standard mcpy installation with a local application host."""
2
+ from pathlib import Path
3
+ import click
4
+ from ..command_context import OperationCommand, project_dir
5
+ from ..mobile_host import profiles
6
+ from ..mobile_host.client import Client, pair
7
+
8
+ @click.group()
9
+ def host_cmd():
10
+ """管理本机 App 连接和共享工作区。"""
11
+
12
+ @host_cmd.command('pair', cls=OperationCommand, local_only=True)
13
+ @click.option('--url', default='http://127.0.0.1:17653', show_default=True)
14
+ @click.option('--code', required=True, help='App 中短时有效的配对码')
15
+ @click.option('--workspace', required=True, type=click.Path(exists=True, file_okay=False))
16
+ @click.option('--label', default='mcpy CLI')
17
+ def pair_cmd(url, code, workspace, label):
18
+ return pair(url, code, workspace, label)
19
+
20
+ @host_cmd.command('status', cls=OperationCommand, local_only=True)
21
+ @click.option("--operation",help="查询原任务,避免重复发送修改操作")
22
+ @click.option('--recent', is_flag=True, help='列出本客户端保存的请求 ID(App 离线也可用)')
23
+ @click.option('--binding-only',is_flag=True,help='只读当前工作区的本地绑定,不访问任何宿主')
24
+ def status_cmd(operation, recent, binding_only=False):
25
+ records=profiles.read()
26
+ selected=profiles.select(project_dir())
27
+ if binding_only:
28
+ if operation or recent: raise click.UsageError('--binding-only 不与 --operation 或 --recent 同用')
29
+ return dict(paired=selected is not None,bound_for_project=selected is not None,
30
+ **({'endpoint':selected['endpoint'],'workspace':selected['workspace']} if selected else {}))
31
+ record = selected or (records[0] if len(records)==1 else None)
32
+ if not record: return {'paired': False, 'hint': '在 App 的开发工具页面开始连接'}
33
+ if recent:
34
+ from ..mobile_host.receipts import Receipts
35
+ return {'operations': Receipts(record).recent()}
36
+ if operation:
37
+ if len(operation)!=64 or any(c not in '0123456789abcdef' for c in operation): raise ValueError('无效操作 ID')
38
+ return Client(record).request('GET','/operations/'+operation)
39
+ from ..remote.protocol import RemoteError
40
+ binding = dict(paired=True, bound_for_project=profiles.select(project_dir()) is not None,
41
+ endpoint=record['endpoint'], workspace=record['workspace'])
42
+ try:
43
+ return dict(Client(record).capabilities(), **binding)
44
+ except RemoteError as error:
45
+ error.details = dict(getattr(error, 'details', {}) or {}, **binding)
46
+ raise
47
+
48
+ @host_cmd.command('disconnect', cls=OperationCommand, local_only=True)
49
+ @click.option('--offline', is_flag=True, help='App 不可达时仅删除本地连接;仍需在 App 撤销授权')
50
+ def disconnect_cmd(offline):
51
+ records = profiles.read()
52
+ record = profiles.select(project_dir()) or (records[0] if len(records)==1 else None)
53
+ if not record:
54
+ if records: raise ValueError('有多个连接,请用 --project 指定要断开的工作区')
55
+ return {'paired': False}
56
+ if not offline: Client(record).request('POST', '/clients/revoke', {})
57
+ profiles.unbind(record['workspace'])
58
+ return {'paired': False, 'revoked': not offline}
59
+
60
+
61
+ @host_cmd.command('resume', cls=OperationCommand, local_only=True)
62
+ @click.option('--operation', required=True, help='原操作 ID;仅恢复等待和提交已有准备结果')
63
+ def resume_cmd(operation):
64
+ records = profiles.read()
65
+ record = profiles.select(project_dir()) or (records[0] if len(records) == 1 else None)
66
+ if not record:
67
+ raise ValueError('请先选择已配对的工作区')
68
+ return Client(record).resume(operation)
@@ -6,14 +6,18 @@ from ..minecraft.netease_modsdk import get_available_versions, download_and_inst
6
6
 
7
7
  @click.command(cls=OperationCommand)
8
8
  @click.option('--list', 'list_versions', is_flag=True)
9
+ @click.option('--offline', is_flag=True, help='仅查询缓存的版本目录')
10
+ @click.option('--refresh', is_flag=True, help='联网刷新版本目录,整体最多等待 12 秒')
9
11
  @click.option('--version', help='安装指定版本')
10
12
  @click.option('--latest', is_flag=True, help='显式选择最新版本')
11
13
  @click.option('--inspect', 'inspect_only', is_flag=True, help='只读检查目标 Python 与已安装补全库,无网络')
12
14
  @click.option('--python', 'python_path', type=click.Path(dir_okay=False), help='开发环境 Python 可执行文件绝对路径,不解析虚拟环境链接')
13
15
  @click.option('--expected-environment', help='安装前后核验 --inspect 返回的环境 identity')
14
- def modsdk_cmd(list_versions, version, latest, inspect_only, python_path, expected_environment):
16
+ def modsdk_cmd(list_versions, version, latest, inspect_only, python_path, expected_environment, offline=False, refresh=False):
15
17
  """管理开发环境 ModSDK 补全库;--python 定向安装,不安装到游戏。"""
16
18
  from ..minecraft.modsdk_environment import inspect_environment, install_environment
19
+ if offline and refresh: raise click.UsageError('--offline 与 --refresh 不能同时使用')
20
+ if (offline or refresh) and not list_versions: raise click.UsageError('--offline/--refresh 只用于 --list')
17
21
  if inspect_only:
18
22
  if list_versions or version or latest or expected_environment:
19
23
  raise click.UsageError('--inspect 不能与安装/版本列表选项同时使用')
@@ -27,11 +31,15 @@ def modsdk_cmd(list_versions, version, latest, inspect_only, python_path, expect
27
31
  raise click.UsageError('--list、--version、--latest 不能同时使用')
28
32
  if not any((list_versions, version, latest)) and non_interactive():
29
33
  raise click.UsageError('请指定 --list、--version 或 --latest')
34
+ if list_versions:
35
+ from ..minecraft.modsdk_versions import catalog
36
+ result = catalog(offline=offline,refresh=refresh)
37
+ if not result['versions']:
38
+ return dict(result, ok=False, code='sdk_catalog_unavailable', error='没有可用的 SDK 版本目录')
39
+ return result
30
40
  versions = get_available_versions()
31
41
  if not versions:
32
42
  raise click.ClickException('无法获取 ModSDK 版本,请检查网络')
33
- if list_versions:
34
- return {'versions': versions}
35
43
  if not version:
36
44
  version = versions[-1] if latest else click.prompt('SDK 版本', type=click.Choice(versions), default=versions[-1])
37
45
  if python_path:
@@ -2,8 +2,10 @@
2
2
  import click
3
3
  from ..command_context import OperationCommand, project_dir as current_project, non_interactive, require_project
4
4
  from ..config import config_exists
5
+ from ..mobile_host.options import HostOption
5
6
  from ..mcstudio.discovery import engine_options
6
7
  from ..mcstudio.runtime_cppconfig import creation_settings, RULE_LABELS
8
+ from ..runtime.window_policy import window_options, resolve_background
7
9
 
8
10
  WORLD_CHOICES = {
9
11
  'world_type': {'infinite': 1, 'flat': 2},
@@ -55,6 +57,7 @@ def take_world_options(arguments, cppconfig=None):
55
57
  @click.command(cls=OperationCommand)
56
58
  @world_options
57
59
  @engine_options
60
+ @window_options
58
61
  @click.option("--no-gui", is_flag=True, help="不显示调试小窗;适用于 AI、脚本或纯终端操作")
59
62
  @click.option("--detach", is_flag=True, help="后台运行并返回游戏会话")
60
63
  @click.option('--mcs-auth', is_flag=True, help='本次单人测试或网络连接使用已登录的 MC Studio 身份')
@@ -67,8 +70,9 @@ def take_world_options(arguments, cppconfig=None):
67
70
  @click.option('--clean-all', is_flag=True, help='清空所有游戏实例')
68
71
  @click.option('--clients', type=click.IntRange(1, 2), default=1, show_default=True,
69
72
  help='本机玩家客户端数量;本地双客户端需在客户端 2 手动点击目标局域网卡片加入')
73
+ @click.option('--launch-timeout', cls=HostOption, type=click.IntRange(5,120), default=None, help='App 宿主进程/画中画确认等待秒数;不含世界加载')
70
74
  @click.argument('instance_prefix', required=False)
71
- def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach, mcs_auth=False, cppconfig=None, clients=1, **engine_overrides):
75
+ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach, mcs_auth=False, cppconfig=None, clients=1, background=None, launch_timeout=None, **engine_overrides):
72
76
  """游戏实例运行与管理
73
77
 
74
78
  可直接运行 'mcpy run' 启动最新实例,或使用选项管理实例。
@@ -78,6 +82,7 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
78
82
  不装配本地 Mod,不保证服务器接受连接。网络模式前台采集日志,
79
83
  Ctrl+C 结束本次网络游戏;--detach 返回可查询、操作和停止的会话。
80
84
  """
85
+ if launch_timeout is not None: raise click.UsageError('当前执行目标不支持 --launch-timeout')
81
86
  base_dir = str(current_project())
82
87
  from ..command_context import json_output
83
88
  if clients == 2 and json_output() and not detach:
@@ -107,7 +112,8 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
107
112
  from ..ui.run_session import show_session_window
108
113
  options = dict(new=new, listing=list, delete=delete, force=force, clean_all=clean_all,
109
114
  instance_prefix=instance_prefix, no_gui=True, detach=detach or show_debug,
110
- mcs_auth=mcs_auth, overrides=engine_overrides, world_config=world_config)
115
+ mcs_auth=mcs_auth, overrides=engine_overrides, world_config=world_config,
116
+ background=resolve_background(background, no_gui=no_gui, detach=detach))
111
117
  def perform():
112
118
  if clients == 1:
113
119
  return backend.run(base_dir, **options)
@@ -119,7 +125,8 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
119
125
  raise click.UsageError('网络联调不支持世界创建参数')
120
126
  if mcs_auth:
121
127
  raise click.UsageError('双客户端进服暂不支持双账号认证')
122
- return groups.start_network(base_dir, backend, target, engine_overrides)
128
+ return groups.start_network(base_dir, backend, target, engine_overrides,
129
+ background=options['background'])
123
130
  return groups.start_local(base_dir, backend, options)
124
131
  recovered = False
125
132
  try:
@@ -1,7 +1,7 @@
1
1
  """Explicit local project scripts, separate from game run and runtime Python."""
2
2
  import click
3
3
  from ..command_context import OperationCommand, project_dir, require_project
4
- from ..project_scripts import configuration, operation
4
+ from ..project_scripts import operation
5
5
 
6
6
 
7
7
  @click.group()
@@ -14,21 +14,9 @@ def script_cmd():
14
14
  def list_cmd(effective=False):
15
15
  """只读列出脚本与生命周期绑定。"""
16
16
  require_project()
17
- scripts, hooks = configuration(project_dir())
18
- result = {'scripts': [{'name': name, 'command': spec['command'], 'timeout': spec['timeout'],
19
- 'hooks': [event for event, names in hooks.items() if name in names]}
20
- for name, spec in scripts.items()]}
21
17
  from ..command_context import json_output
22
- if json_output() and not effective:
23
- return result
24
- from ..consumer_prepare import PreparationPlan, EVENTS
25
- plan = PreparationPlan(project_dir(), inspect_only=True)
26
- result.update(plan.inspect())
27
- result['execution_order'] = {event: [{'kind': 'root', 'name': name} for name in hooks.get(event, [])] +
28
- [{'kind': 'consumer_prepare', 'provider': p['id'], 'name': p['name']}
29
- for p in plan.enabled] for event in EVENTS}
30
- result['execution_order']['before_archive'] = [
31
- {'kind': 'root', 'name': name} for name in hooks.get('before_archive', [])]
18
+ from ..project_scripts import describe_scripts
19
+ result = describe_scripts(project_dir(), effective=effective or not json_output())
32
20
  if json_output():
33
21
  return result
34
22
  for script in result['scripts']:
@@ -2,6 +2,7 @@
2
2
  import click
3
3
  from ..command_context import OperationCommand, project_dir, json_output
4
4
  from ..mcstudio import sessions
5
+ from ..mobile_host.options import HostOption, LogSource
5
6
 
6
7
 
7
8
  @click.command(cls=OperationCommand, name='status')
@@ -26,21 +27,42 @@ def status_cmd(session, list_sessions, group=None):
26
27
  @click.option('--session')
27
28
  @click.option('--group')
28
29
  @click.option('--tail', type=click.IntRange(0, 10000), default=100, show_default=True)
29
- @click.option('--source', type=click.Choice(['game', 'engine', 'worker', 'all']), default='game', show_default=True)
30
+ @click.option('--source', type=LogSource(), default='game', show_default=True)
30
31
  @click.option('--follow', is_flag=True, help='增量跟随日志,结束订阅不停止游戏(仅本机)')
31
32
  @click.option('--format', 'output_format', type=click.Choice(['text', 'jsonl']), default='text', show_default=True)
32
- def logs_cmd(session, tail, source, group=None, follow=False, output_format='text'):
33
+ @click.option('--filter', 'contains', type=click.STRING, help='区分大小写的字面文本匹配,先过滤再取 tail')
34
+ @click.option('--summary', is_flag=True, help='统计扫描范围内完全相同的消息,不推断错误等级')
35
+ @click.option('--since-ms', cls=HostOption, type=click.IntRange(min=0), help='起始 Unix 毫秒时间(需要宿主提供带时间戳日志)')
36
+ @click.option('--until-ms', cls=HostOption, type=click.IntRange(min=0), help='截止 Unix 毫秒时间(需要宿主提供带时间戳日志)')
37
+ def logs_cmd(session, tail, source, group=None, follow=False, output_format='text', contains=None, summary=False, since_ms=None, until_ms=None):
33
38
  """读取日志快照,或使用 --follow 持续接收增量日志。"""
34
39
  target(session, group)
40
+ if source in ('client','server') or since_ms is not None or until_ms is not None:
41
+ raise click.UsageError('本地文本日志未提供可靠端侧/时间戳;这些筛选需支持结构化日志的宿主')
42
+ if summary and follow:
43
+ raise click.UsageError('--summary 用于有限日志快照,不与 --follow 同用')
35
44
  if follow:
36
45
  if json_output():
37
46
  raise click.UsageError('--follow 是持续流;使用 --format jsonl,不加 --json')
38
47
  from ..mcstudio.log_stream import follow as events
39
48
  import json
49
+ from ..mcstudio.log_query import StreamFilter
50
+ filters = {}
40
51
  from contextlib import closing
41
52
  try:
42
- with closing(events(project_dir(), session=session, group=group, source=source, tail=tail)) as stream:
53
+ with closing(events(project_dir(), session=session, group=group, source=source, tail=tail, **({"contains":contains} if contains is not None else {}))) as stream:
43
54
  for item in stream:
55
+ if item.get('event') == 'reset': filters.pop((item.get('session'),item.get('source')),None)
56
+ if item.get('event') == 'log' and contains is not None:
57
+ key=(item.get('session'),item.get('source'))
58
+ item=dict(item,text=filters.setdefault(key,StreamFilter(contains)).feed(item['text']))
59
+ if not item['text']: continue
60
+ if item.get('event') == 'end':
61
+ for (sid,name), selection in filters.items():
62
+ remaining=selection.feed('',final=True)
63
+ if remaining:
64
+ if output_format=='jsonl': click.echo(json.dumps({'event':'log','session':sid,'source':name,'text':remaining},ensure_ascii=True))
65
+ else: click.echo('['+sid[:8]+' '+name+'] '+remaining,nl=False)
44
66
  if output_format == 'jsonl':
45
67
  click.echo(json.dumps(item, ensure_ascii=True))
46
68
  elif item['event'] == 'log':
@@ -48,6 +70,24 @@ def logs_cmd(session, tail, source, group=None, follow=False, output_format='tex
48
70
  except KeyboardInterrupt:
49
71
  pass
50
72
  return
73
+ if contains is not None or summary:
74
+ if output_format != 'text' or tail == 0: raise click.UsageError('日志快照需要 --tail 1..10000 和 --format text')
75
+ from ..mcstudio.log_query import snapshot
76
+ if group:
77
+ from ..runtime.groups import read
78
+ targets=[member['session'] for member in read(project_dir(),group)['members'] if member.get('session')]
79
+ else: targets=[session]
80
+ streams=[]
81
+ for sid in targets:
82
+ directory=sessions.session_path(project_dir(),sid)
83
+ if not (directory/'session.json').is_file(): raise ValueError('会话不存在')
84
+ for name in (('game','engine','worker') if source=='all' else (source,)):
85
+ streams.append(dict(snapshot(directory/(name+'.log'),contains=contains,tail=tail,summary=summary),session=sid,source=name))
86
+ if json_output():
87
+ return streams[0] if len(streams)==1 else {'streams':streams}
88
+ from ..mcstudio.log_query import show_snapshot
89
+ show_snapshot({'streams':streams},summary)
90
+ return
51
91
  if source == 'all' or output_format != 'text' or tail == 0:
52
92
  raise click.UsageError('--source all、--format jsonl 和 --tail 0 仅用于 --follow')
53
93
  if group:
@@ -85,8 +125,10 @@ def target(session, group):
85
125
  @click.command(cls=OperationCommand, name='restart')
86
126
  @click.option('--session')
87
127
  @click.option('--group')
88
- def restart_cmd(session, group):
128
+ @click.option('--launch-timeout', cls=HostOption, type=click.IntRange(5,120), default=None, help='App 宿主启动确认等待秒数')
129
+ def restart_cmd(session, group, launch_timeout=None):
89
130
  """重试指定成员,或重新运行整个联调组。"""
131
+ if launch_timeout is not None: raise click.UsageError('当前执行目标不支持 --launch-timeout')
90
132
  from ..runtime import groups
91
133
  from ..command_context import remote_url
92
134
  if remote_url():