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.
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/PKG-INFO +1 -1
- mcpywrap_core-0.6.2/mcpywrap_core/_process_identity.py +55 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/_script_worker.py +15 -12
- mcpywrap_core-0.6.2/mcpywrap_core/_version.py +1 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/api.py +22 -8
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/dependency_manager.py +4 -3
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/project_builder.py +3 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/cli.py +1 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/command_context.py +25 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/connect_cmd.py +7 -4
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/desktop_cmd.py +5 -1
- mcpywrap_core-0.6.2/mcpywrap_core/commands/host_cmd.py +68 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/modsdk_cmd.py +11 -3
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/run_cmd.py +10 -3
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/script_cmd.py +3 -15
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/session_cmd.py +46 -4
- mcpywrap_core-0.6.2/mcpywrap_core/commands/skill_cmd.py +34 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/backend.py +3 -2
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/install.py +1 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/macos.py +12 -4
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/macos_network.py +4 -2
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/windows.py +6 -4
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/git_cache.py +15 -9
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/host.py +17 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/host_assets.py +1 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/game.py +27 -3
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/host_input.py +4 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mcstudio/log_query.py +75 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/log_stream.py +9 -3
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/network.py +5 -4
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/processes.py +9 -6
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/screenshot.py +1 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/session_worker.py +3 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/sessions.py +5 -3
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/window.py +6 -1
- mcpywrap_core-0.6.2/mcpywrap_core/mcstudio/window_activation.py +56 -0
- mcpywrap_core-0.6.2/mcpywrap_core/minecraft/modsdk_versions.py +49 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/netease_modsdk.py +3 -32
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/__init__.py +2 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/client.py +232 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/intervention.py +41 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/options.py +35 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/preparation.py +173 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/profiles.py +116 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/receipts.py +85 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/routing.py +80 -0
- mcpywrap_core-0.6.2/mcpywrap_core/mobile_host/streams.py +32 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/build.py +9 -3
- mcpywrap_core-0.6.2/mcpywrap_core/operations/input_snapshot.py +37 -0
- mcpywrap_core-0.6.2/mcpywrap_core/operations/project.py +61 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/project_init.py +4 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/project_scripts.py +33 -4
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/client.py +16 -2
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/service.py +4 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/deployment_service.py +1 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/groups.py +10 -5
- mcpywrap_core-0.6.2/mcpywrap_core/runtime/window_policy.py +36 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/windows.py +6 -4
- mcpywrap_core-0.6.2/mcpywrap_core/skill/SKILL.md +60 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/agents/openai.yaml +4 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/CatalogRuntime/__init__.py +3 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/manifest.json +5 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/example.toml +2 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/prepare.py +53 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/schema.json +9 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/framework/pyproject.toml +18 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/.gitignore +6 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/behavior_pack/manifest.json +5 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/data/items.json +1 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/pyproject.toml +12 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/assets/consumer-prepare/game/resource_pack/manifest.json +5 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/application-host.md +88 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/capture.md +49 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/consumer-prepare-author.md +76 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/consumer-prepare.md +91 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/dual-clients.md +93 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/input-sequence.md +87 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/input-writing.md +213 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/instance-world-settings.md +52 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/local-development.md +86 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/macos-local.md +70 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/network-sessions.md +46 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/project-dependencies.md +131 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/project-scripts.md +104 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/project-workflow.md +50 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/remote-testing.md +52 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-debug.md +59 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-input.md +134 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-key-timeline.md +78 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-player.md +85 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/runtime-ui.md +79 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/script-example.md +44 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/setup.md +78 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/references/troubleshooting.md +83 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/bootstrap.ps1 +31 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/bootstrap.py +225 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/game_window.py +60 -0
- mcpywrap_core-0.6.2/mcpywrap_core/skill/scripts/smoke.py +238 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/PKG-INFO +1 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/SOURCES.txt +19 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/pyproject.toml +1 -1
- mcpywrap_core-0.6.2/setup.py +28 -0
- mcpywrap_core-0.6.0/mcpywrap_core/_version.py +0 -1
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/LICENSE +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/MANIFEST.in +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/README.md +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/THIRD_PARTY_NOTICES.md +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/__main__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/AddonsPack.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/MapPack.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/assembly.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/file_merge.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/builders/watcher.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/code_libraries.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/add_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/build_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/default_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/dependency_prompt.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/deployment_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/deployment_output.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/dev_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/doctor_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/edit_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/engine_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/init_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/mod_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/package_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/publish_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/record_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/remove_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_input_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_player_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/runtime_ui_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/serve_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/sync_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/commands/ui_cmd.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/config.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/consumer_prepare.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/core-contract.json +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/custom_packaging.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/dependencies.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/android_icon.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/apk_profile.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/host.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/macos_icons.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/official.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/preferences.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/engines/tui.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/framework_presets.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/frameworks.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/git_projects.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/input_executor.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/input_plan.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/SimpleMonitor.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/auth_interaction.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/bridge_assets.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/deployment_links.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/diagnostics.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/discovery.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/editor.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/file_logs.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/hot_reload.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/image.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/input_sequence.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/launcher_control.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/launcher_python.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/log_colorizer.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/log_protocol.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/mcs.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/mcs_auth.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/private_logs.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/recording_worker.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/recordings.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_cppconfig.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_debug.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_input_payload.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_key_timeline_payload.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_player_payload.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_ui.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_ui_outline.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/runtime_ui_payload.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/session_capabilities.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/studio_server.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/studio_server_ui.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/symlink_helper_global.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/symlink_helper_map.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/symlinks.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/mcstudio/unified_input.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/addons.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/level_dat.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/map.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/modsdk_environment.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/template/generate_mod_files.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/minecraft/template/mod_template.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/package.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/operations/sync.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/prepare_environment.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/project_dependencies.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/recording_io.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/http_server.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/remote/protocol.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/deployment.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/deployment_inspection.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/diagnostics.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/instances.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/native.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/occupancy.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/preparation.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/profiles.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/storage.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/targets.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/runtime/watch.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/source_files.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/timeline.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/application.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icns +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icon/Assets/01-cube.svg +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icon/Assets/02-prompt.svg +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.icon/icon.json +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/assets/mcpy.png +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/engine_setup.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/log_view.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/log_window.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/project_ui.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/run_session.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/session_controller.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/session_tools.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/session_views.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/ui/world_config.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/console.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/link_snapshot.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/locks.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/paths.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/pip_error_parser.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/print_guide.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/project_setup.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core/utils/utils.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/dependency_links.txt +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/requires.txt +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_core.egg-info/top_level.txt +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_host/__init__.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/mcpywrap_host/store.py +0 -0
- {mcpywrap_core-0.6.0 → mcpywrap_core-0.6.2}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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',
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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():
|