mobile-debug-mcp 0.34.0 → 0.35.0
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.
- package/.agents/skills/mcp-builder/SKILL.md +44 -64
- package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
- package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
- package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
- package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
- package/.agents/skills/test-authoring/SKILL.md +7 -6
- package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
- package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
- package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
- package/.github/copilot-instructions.md +14 -36
- package/.github/workflows/ci.yml +4 -4
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +156 -129
- package/agent-plugin/.claude-plugin/plugin.json +2 -2
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/.mcp.json +1 -1
- package/agent-plugin/README.md +16 -6
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +27 -2
- package/dist/contracts/execution-controls.js +24 -0
- package/dist/contracts/selector.js +8 -0
- package/dist/contracts/target.js +15 -0
- package/dist/contracts/ui.js +1 -0
- package/dist/debugger/android.js +2 -1
- package/dist/debugger/diagnostics.js +54 -0
- package/dist/debugger/protocol.js +33 -0
- package/dist/debugger/sessions.js +104 -62
- package/dist/debugger/worker.js +53 -42
- package/dist/interact/adjustment/adjustment-context.js +41 -0
- package/dist/interact/adjustment/adjustment-gestures.js +55 -0
- package/dist/interact/adjustment/adjustment-plan.js +18 -0
- package/dist/interact/adjustment/adjustment-resolution.js +127 -0
- package/dist/interact/adjustment/adjustment-result.js +19 -0
- package/dist/interact/adjustment/adjustment-semantic.js +64 -0
- package/dist/interact/adjustment/adjustment-verification.js +35 -0
- package/dist/interact/adjustment/adjustment.js +52 -0
- package/dist/interact/adjustment/control-geometry.js +87 -0
- package/dist/interact/adjustment/control-values.js +136 -0
- package/dist/interact/adjustment/slider-labels.js +84 -0
- package/dist/interact/assertions/screen-assertion.js +37 -0
- package/dist/interact/assertions/state-assertion-context.js +35 -0
- package/dist/interact/assertions/state-assertion.js +76 -0
- package/dist/interact/assertions/state-comparison.js +141 -0
- package/dist/interact/assertions/visibility-assertion.js +34 -0
- package/dist/interact/conditional-action.js +12 -7
- package/dist/interact/index.js +65 -3090
- package/dist/interact/input-service.js +1 -0
- package/dist/interact/interaction-trace.js +21 -0
- package/dist/interact/model.js +1 -0
- package/dist/interact/observation.js +1 -0
- package/dist/interact/targeting/candidate-ancestor.js +58 -0
- package/dist/interact/targeting/candidate-result.js +23 -0
- package/dist/interact/targeting/candidate-scoring.js +50 -0
- package/dist/interact/targeting/element-helpers.js +216 -0
- package/dist/interact/targeting/find-element.js +65 -0
- package/dist/interact/targeting/nearby-control.js +56 -0
- package/dist/interact/targeting/resolved-elements.js +21 -0
- package/dist/interact/targeting/tap-target.js +17 -0
- package/dist/interact/targeting/target-scope.js +104 -0
- package/dist/interact/waits/legacy-log-wait.js +38 -0
- package/dist/interact/waits/legacy-wait-observations.js +103 -0
- package/dist/interact/waits/legacy-wait-state.js +34 -0
- package/dist/interact/waits/legacy-wait.js +48 -0
- package/dist/interact/waits/screen-wait.js +49 -0
- package/dist/interact/waits/ui-change-helpers.js +157 -0
- package/dist/interact/waits/ui-change-state.js +18 -0
- package/dist/interact/waits/ui-change-wait.js +77 -0
- package/dist/interact/waits/ui-wait-condition.js +56 -0
- package/dist/interact/waits/ui-wait-state.js +38 -0
- package/dist/interact/waits/ui-wait.js +106 -0
- package/dist/manage/android.js +55 -51
- package/dist/manage/index.js +38 -75
- package/dist/manage/ios/build-types.js +1 -0
- package/dist/manage/ios/build.js +210 -0
- package/dist/manage/ios/project-discovery.js +29 -0
- package/dist/manage/ios/scheme.js +18 -0
- package/dist/manage/ios.js +42 -256
- package/dist/observe/android/context.js +1 -0
- package/dist/observe/android/current-screen.js +55 -0
- package/dist/observe/android/fingerprint.js +13 -0
- package/dist/observe/android/log-stream.js +132 -0
- package/dist/observe/android/logs.js +107 -0
- package/dist/observe/android/screenshot.js +70 -0
- package/dist/observe/android/ui-tree.js +107 -0
- package/dist/observe/android.js +16 -520
- package/dist/observe/evidence.js +114 -75
- package/dist/observe/index.js +16 -263
- package/dist/observe/ios/context.js +16 -0
- package/dist/observe/ios/fingerprint.js +12 -0
- package/dist/observe/ios/log-stream.js +105 -0
- package/dist/observe/ios/logs.js +168 -0
- package/dist/observe/ios/screenshot.js +22 -0
- package/dist/observe/ios/ui-elements.js +241 -0
- package/dist/observe/ios/ui-tree.js +137 -0
- package/dist/observe/ios.js +17 -715
- package/dist/observe/logs/model.js +1 -0
- package/dist/{network → observe/network}/index.js +2 -2
- package/dist/observe/readiness-service.js +4 -2
- package/dist/observe/readiness.js +29 -10
- package/dist/observe/screenshot-encoding.js +41 -0
- package/dist/observe/snapshot-collection.js +79 -0
- package/dist/observe/snapshot-logs.js +30 -0
- package/dist/observe/snapshot-metadata.js +43 -31
- package/dist/observe/snapshot-semantic.js +115 -0
- package/dist/observe/system-trace.js +22 -13
- package/dist/runtime/action-results.js +270 -0
- package/dist/runtime/clock.js +4 -0
- package/dist/server/common.js +1 -256
- package/dist/server/definitions/assert.js +162 -0
- package/dist/server/definitions/diagnostic.js +118 -0
- package/dist/server/definitions/input.js +274 -0
- package/dist/server/definitions/manage.js +194 -0
- package/dist/server/definitions/observe.js +149 -0
- package/dist/server/definitions/target.js +138 -0
- package/dist/server/definitions/wait.js +105 -0
- package/dist/server/handlers/action-execution.js +163 -0
- package/dist/server/handlers/correlation.js +23 -0
- package/dist/server/{debugger-handlers.js → handlers/debugger-handlers.js} +22 -12
- package/dist/server/handlers/debugger-response.js +9 -0
- package/dist/server/handlers/diagnostic.js +69 -0
- package/dist/server/handlers/handler-types.js +1 -0
- package/dist/server/handlers/input.js +154 -0
- package/dist/server/handlers/interact.js +113 -0
- package/dist/server/handlers/journey-assertions.js +35 -0
- package/dist/server/handlers/journey.js +147 -0
- package/dist/server/handlers/manage.js +119 -0
- package/dist/server/handlers/observe.js +95 -0
- package/dist/server/handlers/payload.js +4 -0
- package/dist/server/handlers/tool-handlers.js +160 -0
- package/dist/server/schemas/condition-schema.js +2 -0
- package/dist/server/{debugger-schema.js → schemas/debugger-schema.js} +11 -6
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
- package/dist/server/tool-definitions.js +58 -1135
- package/dist/server-core.js +2 -2
- package/dist/system/android-status.js +101 -0
- package/dist/system/android.js +37 -27
- package/dist/system/index.js +13 -439
- package/dist/system/ios-status.js +132 -0
- package/dist/system/ios.js +11 -8
- package/dist/system/model.js +1 -0
- package/dist/system/probes.js +105 -0
- package/dist/system/status-summary.js +43 -0
- package/dist/system/unsupported-status.js +52 -0
- package/dist/utils/android/commands.js +89 -0
- package/dist/utils/android/gradle.js +139 -0
- package/dist/utils/android/logs.js +103 -0
- package/dist/utils/android/ui-tree.js +233 -0
- package/dist/utils/android/utils.js +6 -564
- package/dist/utils/cli/idb/idb-helper.js +4 -6
- package/dist/utils/cli/idb/install-idb.js +13 -9
- package/dist/utils/cli/ios/preflight-ios.js +42 -38
- package/dist/utils/ios/utils.js +45 -46
- package/dist/utils/java.js +65 -49
- package/dist/utils/resolve-device.js +4 -2
- package/dist/utils/ui/index.js +26 -26
- package/docs/CHANGELOG.md +44 -0
- package/docs/README.md +38 -0
- package/docs/benchmarks/README.md +18 -0
- package/docs/benchmarks/interaction-latency/README.md +14 -0
- package/docs/benchmarks/interaction-latency/final.md +4 -1
- package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
- package/docs/benchmarks/ui-tree-performance/README.md +24 -16
- package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
- package/docs/code-structure.md +118 -0
- package/docs/specs/001-state-verification/spec.md +16 -14
- package/docs/specs/002-richer-element-identity/spec.md +12 -10
- package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
- package/docs/specs/004-action-verification-routing/spec.md +3 -3
- package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
- package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
- package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
- package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
- package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
- package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
- package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
- package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
- package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
- package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
- package/docs/specs/014-actionability-resolution/spec.md +20 -18
- package/docs/specs/015-adjustable-control-support/spec.md +17 -15
- package/docs/specs/016-environment-tools/spec.md +20 -20
- package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
- package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
- package/docs/specs/020-conditional-actions/intent.md +5 -3
- package/docs/specs/020-conditional-actions/plan.md +8 -9
- package/docs/specs/020-conditional-actions/spec.md +4 -2
- package/docs/specs/021-app-readiness-signals/intent.md +4 -2
- package/docs/specs/021-app-readiness-signals/plan.md +7 -6
- package/docs/specs/021-app-readiness-signals/spec.md +3 -1
- package/docs/specs/022-correlated-evidence/intent.md +4 -4
- package/docs/specs/022-correlated-evidence/plan.md +10 -11
- package/docs/specs/022-correlated-evidence/spec.md +3 -1
- package/docs/specs/023-android-system-traces/intent.md +3 -2
- package/docs/specs/023-android-system-traces/plan.md +5 -7
- package/docs/specs/023-android-system-traces/spec.md +3 -1
- package/docs/specs/024-android-heap-dump-capture/intent.md +3 -1
- package/docs/specs/025-android-runtime-debugger/intent.md +4 -3
- package/docs/specs/025-android-runtime-debugger/plan.md +3 -1
- package/docs/specs/025-android-runtime-debugger/spec.md +4 -3
- package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
- package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
- package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
- package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
- package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
- package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
- package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
- package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
- package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
- package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
- package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
- package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
- package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
- package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
- package/docs/specs/030-android-coroutine-validation/report.md +240 -0
- package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
- package/docs/tools/TOOLS.md +31 -18
- package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
- package/docs/tools/debugger.md +76 -0
- package/docs/tools/interact.md +42 -20
- package/docs/tools/manage.md +25 -44
- package/docs/tools/observe.md +55 -23
- package/docs/tools/system-traces.md +9 -1
- package/docs/tools/system.md +12 -5
- package/package.json +11 -8
- package/scripts/architecture-graph.mjs +69 -0
- package/scripts/check-architecture.mjs +47 -0
- package/scripts/code-quality-baseline.json +1 -0
- package/scripts/code-quality.mjs +161 -0
- package/src/contracts/execution-controls.ts +26 -0
- package/src/contracts/selector.ts +17 -0
- package/src/contracts/target.ts +19 -0
- package/src/contracts/ui.ts +94 -0
- package/src/debugger/android.ts +2 -1
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +6 -1
- package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +314 -155
- package/src/debugger/worker.ts +51 -38
- package/src/interact/adjustment/adjustment-context.ts +83 -0
- package/src/interact/adjustment/adjustment-gestures.ts +48 -0
- package/src/interact/adjustment/adjustment-plan.ts +23 -0
- package/src/interact/adjustment/adjustment-resolution.ts +104 -0
- package/src/interact/adjustment/adjustment-result.ts +34 -0
- package/src/interact/adjustment/adjustment-semantic.ts +53 -0
- package/src/interact/adjustment/adjustment-verification.ts +60 -0
- package/src/interact/adjustment/adjustment.ts +49 -0
- package/src/interact/adjustment/control-geometry.ts +120 -0
- package/src/interact/adjustment/control-values.ts +141 -0
- package/src/interact/adjustment/slider-labels.ts +84 -0
- package/src/interact/assertions/screen-assertion.ts +39 -0
- package/src/interact/assertions/state-assertion-context.ts +48 -0
- package/src/interact/assertions/state-assertion.ts +64 -0
- package/src/interact/assertions/state-comparison.ts +168 -0
- package/src/interact/assertions/visibility-assertion.ts +41 -0
- package/src/interact/conditional-action.ts +18 -12
- package/src/interact/index.ts +76 -3492
- package/src/interact/input-service.ts +11 -0
- package/src/interact/interaction-trace.ts +36 -0
- package/src/interact/model.ts +109 -0
- package/src/interact/observation.ts +7 -0
- package/src/interact/targeting/candidate-ancestor.ts +49 -0
- package/src/interact/targeting/candidate-result.ts +24 -0
- package/src/interact/targeting/candidate-scoring.ts +42 -0
- package/src/interact/targeting/element-helpers.ts +243 -0
- package/src/interact/targeting/find-element.ts +47 -0
- package/src/interact/targeting/nearby-control.ts +46 -0
- package/src/interact/targeting/resolved-elements.ts +20 -0
- package/src/interact/targeting/tap-target.ts +22 -0
- package/src/interact/targeting/target-scope.ts +121 -0
- package/src/interact/waits/legacy-log-wait.ts +30 -0
- package/src/interact/waits/legacy-wait-observations.ts +55 -0
- package/src/interact/waits/legacy-wait-state.ts +48 -0
- package/src/interact/waits/legacy-wait.ts +34 -0
- package/src/interact/waits/screen-wait.ts +37 -0
- package/src/interact/waits/ui-change-helpers.ts +184 -0
- package/src/interact/waits/ui-change-state.ts +26 -0
- package/src/interact/waits/ui-change-wait.ts +59 -0
- package/src/interact/waits/ui-wait-condition.ts +52 -0
- package/src/interact/waits/ui-wait-state.ts +55 -0
- package/src/interact/waits/ui-wait.ts +84 -0
- package/src/manage/android.ts +56 -50
- package/src/manage/index.ts +63 -93
- package/src/manage/ios/build-types.ts +14 -0
- package/src/manage/ios/build.ts +230 -0
- package/src/manage/ios/project-discovery.ts +26 -0
- package/src/manage/ios/scheme.ts +16 -0
- package/src/manage/ios.ts +65 -298
- package/src/observe/android/context.ts +1 -0
- package/src/observe/android/current-screen.ts +63 -0
- package/src/observe/android/fingerprint.ts +13 -0
- package/src/observe/android/log-stream.ts +127 -0
- package/src/observe/android/logs.ts +110 -0
- package/src/observe/android/screenshot.ts +79 -0
- package/src/observe/android/ui-tree.ts +113 -0
- package/src/observe/android.ts +17 -531
- package/src/observe/evidence.ts +122 -70
- package/src/observe/index.ts +24 -286
- package/src/observe/ios/context.ts +22 -0
- package/src/observe/ios/fingerprint.ts +12 -0
- package/src/observe/ios/log-stream.ts +103 -0
- package/src/observe/ios/logs.ts +179 -0
- package/src/observe/ios/screenshot.ts +26 -0
- package/src/observe/ios/ui-elements.ts +266 -0
- package/src/observe/ios/ui-tree.ts +143 -0
- package/src/observe/ios.ts +18 -749
- package/src/observe/logs/model.ts +11 -0
- package/src/{network → observe/network}/index.ts +2 -2
- package/src/observe/readiness-service.ts +5 -3
- package/src/observe/readiness.ts +39 -18
- package/src/observe/screenshot-encoding.ts +32 -0
- package/src/observe/snapshot-collection.ts +69 -0
- package/src/observe/snapshot-logs.ts +26 -0
- package/src/observe/snapshot-metadata.ts +51 -31
- package/src/observe/snapshot-semantic.ts +153 -0
- package/src/observe/system-trace.ts +24 -15
- package/src/runtime/action-results.ts +376 -0
- package/src/runtime/clock.ts +9 -0
- package/src/server/common.ts +9 -359
- package/src/server/definitions/assert.ts +164 -0
- package/src/server/definitions/diagnostic.ts +120 -0
- package/src/server/definitions/input.ts +276 -0
- package/src/server/definitions/manage.ts +196 -0
- package/src/server/definitions/observe.ts +151 -0
- package/src/server/definitions/target.ts +140 -0
- package/src/server/definitions/wait.ts +107 -0
- package/src/server/handlers/action-execution.ts +164 -0
- package/src/server/handlers/correlation.ts +20 -0
- package/src/server/handlers/debugger-handlers.ts +39 -0
- package/src/server/handlers/debugger-response.ts +7 -0
- package/src/server/handlers/diagnostic.ts +79 -0
- package/src/server/handlers/handler-types.ts +7 -0
- package/src/server/handlers/input.ts +173 -0
- package/src/server/handlers/interact.ts +135 -0
- package/src/server/handlers/journey-assertions.ts +39 -0
- package/src/server/handlers/journey.ts +132 -0
- package/src/server/handlers/manage.ts +141 -0
- package/src/server/handlers/observe.ts +116 -0
- package/src/server/handlers/payload.ts +36 -0
- package/src/server/handlers/tool-handlers.ts +159 -0
- package/src/server/schemas/condition-schema.ts +2 -0
- package/src/server/{debugger-schema.ts → schemas/debugger-schema.ts} +13 -7
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
- package/src/server/{system-trace-schema.ts → schemas/system-trace-schema.ts} +3 -3
- package/src/server/tool-definitions.ts +67 -1143
- package/src/server-core.ts +2 -2
- package/src/server.ts +1 -1
- package/src/system/android-status.ts +124 -0
- package/src/system/android.ts +33 -21
- package/src/system/index.ts +21 -525
- package/src/system/ios-status.ts +147 -0
- package/src/system/ios.ts +14 -10
- package/src/system/model.ts +41 -0
- package/src/system/probes.ts +114 -0
- package/src/system/status-summary.ts +47 -0
- package/src/system/unsupported-status.ts +55 -0
- package/src/types.ts +16 -106
- package/src/utils/android/commands.ts +81 -0
- package/src/utils/android/gradle.ts +132 -0
- package/src/utils/android/logs.ts +103 -0
- package/src/utils/android/ui-tree.ts +239 -0
- package/src/utils/android/utils.ts +16 -556
- package/src/utils/cli/idb/check-idb.ts +4 -4
- package/src/utils/cli/idb/idb-helper.ts +6 -8
- package/src/utils/cli/idb/install-idb.ts +15 -9
- package/src/utils/cli/ios/preflight-ios.ts +39 -31
- package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
- package/src/utils/ios/utils.ts +57 -61
- package/src/utils/java.ts +62 -43
- package/src/utils/resolve-device.ts +9 -6
- package/src/utils/ui/index.ts +28 -28
- package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
- package/test/device/automated/debugger/coroutine-support.ts +140 -0
- package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
- package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
- package/test/device/automated/debugger/runtime.android.test.ts +19 -3
- package/test/device/automated/interact/interaction.android.test.ts +56 -0
- package/test/device/automated/observe/system-trace.smoke.ts +1 -1
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
- package/test/fixtures/contracts/tool-definitions.json +3536 -0
- package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
- package/test/fixtures/coroutine-app/README.md +48 -0
- package/test/fixtures/coroutine-app/build.ts +35 -0
- package/test/fixtures/debugger-app/MainActivity.java +21 -0
- package/test/fixtures/debugger-app/MainActivity.kt +32 -0
- package/test/fixtures/debugger-app/README.md +52 -0
- package/test/fixtures/debugger-app/build.ts +25 -15
- package/test/fixtures/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -0
- package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
- package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
- package/test/unit/debugger/diagnostics.test.ts +80 -0
- package/test/unit/debugger/guards.test.ts +4 -0
- package/test/unit/debugger/preparation.test.ts +32 -0
- package/test/unit/debugger/protocol.test.ts +15 -0
- package/test/unit/debugger/sessions.test.ts +1 -1
- package/test/unit/interact/adjustment_service.test.ts +42 -0
- package/test/unit/interact/adjustment_verification.test.ts +35 -0
- package/test/unit/interact/clock_dependencies.test.ts +63 -0
- package/test/unit/interact/element_helpers.test.ts +19 -0
- package/test/unit/interact/legacy_wait_service.test.ts +17 -0
- package/test/unit/interact/state_comparison.test.ts +20 -0
- package/test/unit/interact/target_scope.test.ts +35 -0
- package/test/unit/network/get_network_activity.test.ts +1 -1
- package/test/unit/observe/screenshot-encoding.test.ts +30 -0
- package/test/unit/observe/snapshot-logs.test.ts +14 -0
- package/test/unit/quality/architecture.test.ts +60 -0
- package/test/unit/quality/code-quality.test.ts +58 -0
- package/test/unit/quality/interaction_boundaries.test.ts +24 -0
- package/test/unit/server/conditional_actions.test.ts +1 -1
- package/test/unit/server/conditional_schema.test.ts +2 -2
- package/test/unit/server/debugger-response.test.ts +63 -0
- package/test/unit/server/tool_definitions_parity.test.ts +8 -0
- package/dist/server/tool-handlers.js +0 -1000
- package/docs/ROADMAP.md +0 -646
- package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
- package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
- package/docs/specs/baseline-spec-v0.md +0 -312
- package/docs/specs/mcp-tooling-spec-v1.md +0 -446
- package/src/server/debugger-handlers.ts +0 -29
- package/src/server/tool-handlers.ts +0 -1008
- /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
- /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
- /package/dist/server/{system-trace-schema.js → schemas/system-trace-schema.js} +0 -0
- /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
- /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { getIdbCmd, getXcrunCmd } from '../utils/ios/utils.js';
|
|
2
|
+
import { probeIdb } from '../utils/cli/idb/idb-helper.js';
|
|
3
|
+
import { probeCommand, makeFailure, makeToolEntry, describeXcrunDiscovery, describeIdbDiscovery } from './probes.js';
|
|
4
|
+
export function buildIOSPlatform(ios, hostSupported, hostOs) {
|
|
5
|
+
const platformUnsupported = !hostSupported || hostOs === 'linux' || hostOs === 'windows';
|
|
6
|
+
if (platformUnsupported) {
|
|
7
|
+
return {
|
|
8
|
+
status: 'unsupported',
|
|
9
|
+
tools: {},
|
|
10
|
+
providers: {},
|
|
11
|
+
capabilities: {},
|
|
12
|
+
devices: [],
|
|
13
|
+
failures: [
|
|
14
|
+
makeFailure('PLATFORM_UNAVAILABLE', 'ios', 'iOS tooling is only supported on macOS.', {
|
|
15
|
+
tool: null,
|
|
16
|
+
searchedLocations: [],
|
|
17
|
+
selectedProvider: null,
|
|
18
|
+
rawVersion: null,
|
|
19
|
+
remediation: ['Run Mobile Debug MCP on macOS for iOS toolchain discovery.']
|
|
20
|
+
})
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const { xcrunCmd, xcrunProbe, idbProbe, xcrunTool, idbTool, idbCompanionTool, lldbTool } = iosTools(ios, hostSupported, hostOs);
|
|
25
|
+
const simctlAvailable = !!ios.iosAvailable;
|
|
26
|
+
const idbAvailable = !!idbProbe.ok;
|
|
27
|
+
const bootedDevices = ios.iosDevices > 0
|
|
28
|
+
? Array.from({ length: ios.iosDevices }, (_, index) => ({
|
|
29
|
+
id: `ios-simulator-${index + 1}`,
|
|
30
|
+
state: 'booted'
|
|
31
|
+
}))
|
|
32
|
+
: [];
|
|
33
|
+
const { platformUnavailableFailure, deviceFailure } = iosFailures(platformUnsupported, simctlAvailable, bootedDevices.length, xcrunCmd, xcrunProbe.raw);
|
|
34
|
+
const selectedInstallProvider = idbAvailable ? 'idb' : simctlAvailable ? 'simctl' : null;
|
|
35
|
+
const selectedDebugProvider = simctlAvailable ? 'LLDB' : null;
|
|
36
|
+
const selectedDiscoveryProvider = idbAvailable ? 'idb' : simctlAvailable ? 'simctl' : null;
|
|
37
|
+
const status = platformUnsupported
|
|
38
|
+
? 'unsupported'
|
|
39
|
+
: !simctlAvailable
|
|
40
|
+
? 'unavailable'
|
|
41
|
+
: !bootedDevices.length
|
|
42
|
+
? 'partially_ready'
|
|
43
|
+
: 'ready';
|
|
44
|
+
const iosFailure = platformUnsupported
|
|
45
|
+
? makeFailure('PLATFORM_UNAVAILABLE', 'ios', 'iOS tooling is only supported on macOS.', {
|
|
46
|
+
tool: null,
|
|
47
|
+
searchedLocations: [],
|
|
48
|
+
selectedProvider: null,
|
|
49
|
+
rawVersion: null,
|
|
50
|
+
remediation: ['Run Mobile Debug MCP on macOS for iOS toolchain discovery.']
|
|
51
|
+
})
|
|
52
|
+
: platformUnavailableFailure ?? deviceFailure ?? null;
|
|
53
|
+
const failureList = [iosFailure].filter((value) => value !== null);
|
|
54
|
+
return {
|
|
55
|
+
status,
|
|
56
|
+
tools: {
|
|
57
|
+
xcrun: xcrunTool,
|
|
58
|
+
idb: idbTool,
|
|
59
|
+
idb_companion: idbCompanionTool,
|
|
60
|
+
lldb: lldbTool
|
|
61
|
+
},
|
|
62
|
+
providers: {
|
|
63
|
+
idb: { available: idbAvailable, selected: selectedInstallProvider === 'idb', tool: 'idb' },
|
|
64
|
+
simctl: { available: simctlAvailable, selected: selectedInstallProvider === 'simctl', tool: 'xcrun' },
|
|
65
|
+
lldb: { available: simctlAvailable, selected: selectedDebugProvider === 'LLDB', tool: 'xcrun' }
|
|
66
|
+
},
|
|
67
|
+
capabilities: iosCapabilities(selectedDiscoveryProvider, selectedInstallProvider, selectedDebugProvider, simctlAvailable, bootedDevices.length, deviceFailure, iosFailure),
|
|
68
|
+
devices: bootedDevices,
|
|
69
|
+
failures: failureList
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function iosTools(ios, hostSupported, hostOs) {
|
|
73
|
+
const xcrunCmd = getXcrunCmd();
|
|
74
|
+
const xcrunProbe = ios.iosAvailable ? probeCommand(xcrunCmd, ['--version']) : { ok: false, raw: null, parsedVersion: null };
|
|
75
|
+
const idbCmd = getIdbCmd();
|
|
76
|
+
const idbProbe = hostSupported && hostOs === 'macos' ? probeIdb(idbCmd) : { ok: false, raw: null, parsedVersion: null };
|
|
77
|
+
const idbCompanionProbe = hostSupported && hostOs === 'macos' ? probeCommand('idb_companion', ['--version']) : { ok: false, raw: null, parsedVersion: null };
|
|
78
|
+
const xcrunTool = makeToolEntry(xcrunCmd, describeXcrunDiscovery(xcrunCmd).discoverySource, xcrunProbe.raw, xcrunProbe.ok ? 'valid' : 'invalid');
|
|
79
|
+
const idbTool = makeToolEntry(idbCmd, describeIdbDiscovery(idbCmd).discoverySource, idbProbe.raw, idbProbe.ok ? 'valid' : 'invalid');
|
|
80
|
+
const idbCompanionTool = makeToolEntry('idb_companion', 'PATH', idbCompanionProbe.raw, idbCompanionProbe.ok ? 'valid' : 'invalid');
|
|
81
|
+
const lldbTool = makeToolEntry('xcrun lldb', 'xcrun', xcrunProbe.raw, xcrunProbe.ok ? 'valid' : 'invalid');
|
|
82
|
+
return { xcrunCmd, xcrunProbe, idbProbe, xcrunTool, idbTool, idbCompanionTool, lldbTool };
|
|
83
|
+
}
|
|
84
|
+
function iosFailures(platformUnsupported, simctlAvailable, deviceCount, xcrunCmd, rawVersion) {
|
|
85
|
+
const platformUnavailableFailure = !platformUnsupported && !simctlAvailable
|
|
86
|
+
? makeFailure('PLATFORM_UNAVAILABLE', 'ios', 'iOS tooling is unavailable on this host.', {
|
|
87
|
+
tool: 'xcrun',
|
|
88
|
+
searchedLocations: describeXcrunDiscovery(xcrunCmd).searchedLocations,
|
|
89
|
+
selectedProvider: null,
|
|
90
|
+
rawVersion: rawVersion,
|
|
91
|
+
remediation: ['Install Xcode Command Line Tools and ensure xcrun is on PATH.']
|
|
92
|
+
})
|
|
93
|
+
: null;
|
|
94
|
+
const deviceFailure = simctlAvailable && !deviceCount
|
|
95
|
+
? makeFailure('DEVICE_NOT_FOUND', 'ios', 'No iOS simulators are booted.', {
|
|
96
|
+
tool: 'simctl',
|
|
97
|
+
searchedLocations: ['xcrun simctl list devices booted --json'],
|
|
98
|
+
selectedProvider: 'simctl',
|
|
99
|
+
rawVersion: rawVersion,
|
|
100
|
+
remediation: ['Boot a simulator in Xcode or via xcrun simctl boot.']
|
|
101
|
+
})
|
|
102
|
+
: null;
|
|
103
|
+
return { platformUnavailableFailure, deviceFailure };
|
|
104
|
+
}
|
|
105
|
+
function iosCapabilities(selectedDiscoveryProvider, selectedInstallProvider, selectedDebugProvider, simctlAvailable, deviceCount, deviceFailure, iosFailure) {
|
|
106
|
+
const applicationCapability = {
|
|
107
|
+
availability: !!selectedInstallProvider && deviceCount > 0,
|
|
108
|
+
selectedProvider: selectedInstallProvider,
|
|
109
|
+
blockingFailure: selectedInstallProvider && deviceCount > 0 ? null : deviceFailure ?? iosFailure
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
device_discovery: {
|
|
113
|
+
availability: !!selectedDiscoveryProvider,
|
|
114
|
+
selectedProvider: selectedDiscoveryProvider,
|
|
115
|
+
blockingFailure: selectedDiscoveryProvider ? null : iosFailure
|
|
116
|
+
},
|
|
117
|
+
install_app: { ...applicationCapability },
|
|
118
|
+
launch_app: { ...applicationCapability },
|
|
119
|
+
ui_interaction: { ...applicationCapability },
|
|
120
|
+
screenshot: { ...applicationCapability },
|
|
121
|
+
log_capture: {
|
|
122
|
+
availability: simctlAvailable && deviceCount > 0,
|
|
123
|
+
selectedProvider: simctlAvailable ? 'simctl' : null,
|
|
124
|
+
blockingFailure: simctlAvailable && deviceCount > 0 ? null : deviceFailure ?? iosFailure
|
|
125
|
+
},
|
|
126
|
+
debug: {
|
|
127
|
+
availability: !!selectedDebugProvider,
|
|
128
|
+
selectedProvider: selectedDebugProvider,
|
|
129
|
+
blockingFailure: selectedDebugProvider ? null : iosFailure
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
package/dist/system/ios.js
CHANGED
|
@@ -12,14 +12,7 @@ export async function checkIOS() {
|
|
|
12
12
|
try {
|
|
13
13
|
const simOut = execSync(`${xcrun} simctl list devices booted --json`, { encoding: 'utf8', timeout: 1500, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
14
14
|
try {
|
|
15
|
-
|
|
16
|
-
let count = 0;
|
|
17
|
-
for (const k in data.devices) {
|
|
18
|
-
const arr = data.devices[k];
|
|
19
|
-
if (Array.isArray(arr))
|
|
20
|
-
count += arr.filter((d) => (d.state || '').toLowerCase() === 'booted').length;
|
|
21
|
-
}
|
|
22
|
-
iosDevices = count;
|
|
15
|
+
iosDevices = countBooted(simOut);
|
|
23
16
|
}
|
|
24
17
|
catch {
|
|
25
18
|
// Some xcrun builds include extra text around the JSON response.
|
|
@@ -44,3 +37,13 @@ export async function checkIOS() {
|
|
|
44
37
|
}
|
|
45
38
|
return { iosAvailable, iosDevices, issues };
|
|
46
39
|
}
|
|
40
|
+
function countBooted(simOut) {
|
|
41
|
+
const data = JSON.parse(simOut);
|
|
42
|
+
let count = 0;
|
|
43
|
+
for (const k in data.devices) {
|
|
44
|
+
const arr = data.devices[k];
|
|
45
|
+
if (Array.isArray(arr))
|
|
46
|
+
count += arr.filter((d) => (d.state || '').toLowerCase() === 'booted').length;
|
|
47
|
+
}
|
|
48
|
+
return count;
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export function normalizeHostOs(platform) {
|
|
4
|
+
switch (platform) {
|
|
5
|
+
case 'darwin':
|
|
6
|
+
return 'macos';
|
|
7
|
+
case 'linux':
|
|
8
|
+
return 'linux';
|
|
9
|
+
case 'win32':
|
|
10
|
+
return 'windows';
|
|
11
|
+
case 'freebsd':
|
|
12
|
+
case 'openbsd':
|
|
13
|
+
case 'netbsd':
|
|
14
|
+
case 'sunos':
|
|
15
|
+
return platform;
|
|
16
|
+
default:
|
|
17
|
+
return 'unknown';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function isSupportedHost(hostOs) {
|
|
21
|
+
return hostOs === 'macos' || hostOs === 'linux' || hostOs === 'windows';
|
|
22
|
+
}
|
|
23
|
+
export function firstVersionToken(raw) {
|
|
24
|
+
if (!raw)
|
|
25
|
+
return null;
|
|
26
|
+
const match = String(raw).match(/(\d+(?:\.\d+)+|\d+)/);
|
|
27
|
+
return match?.[1] ?? null;
|
|
28
|
+
}
|
|
29
|
+
export function probeCommand(command, args, timeoutMs = 2000) {
|
|
30
|
+
try {
|
|
31
|
+
const result = spawnSync(command, args, { encoding: 'utf8', timeout: timeoutMs, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
32
|
+
const raw = `${result.stdout || ''}${result.stderr || ''}`.trim() || null;
|
|
33
|
+
return {
|
|
34
|
+
ok: result.status === 0,
|
|
35
|
+
raw,
|
|
36
|
+
parsedVersion: firstVersionToken(raw)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { ok: false, raw: null, parsedVersion: null };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function makeFailure(code, platform, message, opts = {}) {
|
|
44
|
+
return {
|
|
45
|
+
code,
|
|
46
|
+
platform,
|
|
47
|
+
tool: opts.tool ?? null,
|
|
48
|
+
message,
|
|
49
|
+
searchedLocations: opts.searchedLocations ?? [],
|
|
50
|
+
selectedProvider: opts.selectedProvider ?? null,
|
|
51
|
+
rawVersion: opts.rawVersion ?? null,
|
|
52
|
+
remediation: opts.remediation ?? []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function makeToolEntry(resolvedPath, discoverySource, rawVersionOutput, validationStatus) {
|
|
56
|
+
return {
|
|
57
|
+
resolvedPath,
|
|
58
|
+
discoverySource,
|
|
59
|
+
parsedVersion: firstVersionToken(rawVersionOutput),
|
|
60
|
+
rawVersionOutput,
|
|
61
|
+
validationStatus
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function describeAdbDiscovery(adbCmd) {
|
|
65
|
+
const searchedLocations = ['ADB_PATH', 'ANDROID_SDK_ROOT/platform-tools/adb', 'ANDROID_HOME/platform-tools/adb', '~/Library/Android/sdk/platform-tools/adb', 'PATH'];
|
|
66
|
+
if (process.env.ADB_PATH && process.env.ADB_PATH.trim())
|
|
67
|
+
return { discoverySource: 'ADB_PATH', searchedLocations };
|
|
68
|
+
if (process.env.ANDROID_SDK_ROOT)
|
|
69
|
+
return { discoverySource: 'ANDROID_SDK_ROOT', searchedLocations };
|
|
70
|
+
if (process.env.ANDROID_HOME)
|
|
71
|
+
return { discoverySource: 'ANDROID_HOME', searchedLocations };
|
|
72
|
+
if (adbCmd.includes('Library/Android/sdk'))
|
|
73
|
+
return { discoverySource: 'HOME_SDK', searchedLocations };
|
|
74
|
+
if (adbCmd === 'adb')
|
|
75
|
+
return { discoverySource: 'PATH_OR_FALLBACK', searchedLocations };
|
|
76
|
+
return { discoverySource: 'PATH', searchedLocations };
|
|
77
|
+
}
|
|
78
|
+
export function describeXcrunDiscovery(xcrunCmd) {
|
|
79
|
+
const searchedLocations = ['XCRUN_PATH', 'PATH'];
|
|
80
|
+
if (process.env.XCRUN_PATH && process.env.XCRUN_PATH.trim())
|
|
81
|
+
return { discoverySource: 'XCRUN_PATH', searchedLocations };
|
|
82
|
+
return { discoverySource: xcrunCmd === 'xcrun' ? 'PATH' : 'PATH', searchedLocations };
|
|
83
|
+
}
|
|
84
|
+
export function describeIdbDiscovery(idbCmd) {
|
|
85
|
+
const searchedLocations = ['MCP_IDB_PATH', 'IDB_PATH', 'mcp.config.json', '~/.mcp/config.json', 'PATH', '/opt/homebrew/bin/idb', '/usr/local/bin/idb'];
|
|
86
|
+
if (process.env.MCP_IDB_PATH && process.env.MCP_IDB_PATH.trim())
|
|
87
|
+
return { discoverySource: 'MCP_IDB_PATH', searchedLocations };
|
|
88
|
+
if (process.env.IDB_PATH && process.env.IDB_PATH.trim())
|
|
89
|
+
return { discoverySource: 'IDB_PATH', searchedLocations };
|
|
90
|
+
return { discoverySource: idbCmd === 'idb' ? 'PATH_OR_FALLBACK' : 'PATH', searchedLocations };
|
|
91
|
+
}
|
|
92
|
+
export function describeJavaDiscovery(javaHome) {
|
|
93
|
+
const searchedLocations = ['GRADLE_JAVA_HOME', 'JAVA_HOME', 'GRADLE_HOME', 'detectJavaHome'];
|
|
94
|
+
if (process.env.GRADLE_JAVA_HOME && process.env.GRADLE_JAVA_HOME.trim())
|
|
95
|
+
return { discoverySource: 'GRADLE_JAVA_HOME', searchedLocations };
|
|
96
|
+
if (process.env.JAVA_HOME && process.env.JAVA_HOME.trim())
|
|
97
|
+
return { discoverySource: 'JAVA_HOME', searchedLocations };
|
|
98
|
+
if (process.env.GRADLE_HOME && process.env.GRADLE_HOME.trim())
|
|
99
|
+
return { discoverySource: 'GRADLE_HOME', searchedLocations };
|
|
100
|
+
return { discoverySource: javaHome ? 'detected' : null, searchedLocations };
|
|
101
|
+
}
|
|
102
|
+
export function resolveJavaBinaryPath(javaHome, hostOs) {
|
|
103
|
+
const javaBinary = hostOs === 'windows' ? 'java.exe' : 'java';
|
|
104
|
+
return path.join(javaHome, 'bin', javaBinary);
|
|
105
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function buildLegacySummary(androidStatus, iosStatus, gradle) {
|
|
2
|
+
const androidReady = androidStatus.status === 'ready';
|
|
3
|
+
const iosReady = iosStatus.status === 'ready';
|
|
4
|
+
const gradleReady = (gradle.issues || []).length === 0;
|
|
5
|
+
const overallStatus = (androidReady || iosReady || gradleReady)
|
|
6
|
+
? ((androidReady && iosReady && gradleReady) ? 'ready' : 'degraded')
|
|
7
|
+
: 'blocked';
|
|
8
|
+
return {
|
|
9
|
+
overall: overallStatus,
|
|
10
|
+
android: {
|
|
11
|
+
ready: androidReady,
|
|
12
|
+
summary: androidStatus.status === 'ready'
|
|
13
|
+
? `${androidStatus.devices.length} Android device(s) connected; log access available`
|
|
14
|
+
: androidStatus.status === 'partially_ready'
|
|
15
|
+
? 'Android toolchain available but device-dependent capabilities are limited'
|
|
16
|
+
: androidStatus.status === 'unsupported'
|
|
17
|
+
? 'Android unsupported on this host'
|
|
18
|
+
: 'ADB unavailable',
|
|
19
|
+
blockers: androidStatus.failures.map((failure) => failure.message)
|
|
20
|
+
},
|
|
21
|
+
ios: {
|
|
22
|
+
ready: iosReady,
|
|
23
|
+
summary: iosStatus.status === 'ready'
|
|
24
|
+
? `${iosStatus.devices.length} iOS simulator(s) booted`
|
|
25
|
+
: iosStatus.status === 'partially_ready'
|
|
26
|
+
? 'iOS toolchain available but device-dependent capabilities are limited'
|
|
27
|
+
: iosStatus.status === 'unsupported'
|
|
28
|
+
? 'iOS unsupported on this host'
|
|
29
|
+
: 'xcrun unavailable',
|
|
30
|
+
blockers: iosStatus.failures.map((failure) => failure.message)
|
|
31
|
+
},
|
|
32
|
+
gradle: {
|
|
33
|
+
ready: gradleReady,
|
|
34
|
+
summary: !gradle.gradleJavaHome
|
|
35
|
+
? 'No explicit Gradle JDK override detected'
|
|
36
|
+
: gradleReady
|
|
37
|
+
? `Gradle JDK configured at ${gradle.gradleJavaHome}`
|
|
38
|
+
: `Gradle JDK override invalid: ${gradle.gradleJavaHome}`,
|
|
39
|
+
blockers: gradle.issues || [],
|
|
40
|
+
suggestedFixes: gradle.suggestedFixes || []
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { makeFailure } from './probes.js';
|
|
2
|
+
export function buildUnsupportedHostStatus(hostOs) {
|
|
3
|
+
const failureMessage = `Host operating system '${hostOs}' is not supported.`;
|
|
4
|
+
const remediation = ['Run Mobile Debug MCP on macOS, Linux, or Windows.'];
|
|
5
|
+
const failureAndroid = makeFailure('HOST_OS_UNSUPPORTED', 'android', failureMessage, {
|
|
6
|
+
remediation
|
|
7
|
+
});
|
|
8
|
+
const failureIos = makeFailure('HOST_OS_UNSUPPORTED', 'ios', failureMessage, {
|
|
9
|
+
remediation
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
status: 'blocked',
|
|
14
|
+
adbAvailable: false,
|
|
15
|
+
adbVersion: '',
|
|
16
|
+
devices: 0,
|
|
17
|
+
deviceStates: '',
|
|
18
|
+
logsAvailable: false,
|
|
19
|
+
envValid: false,
|
|
20
|
+
issues: [failureMessage],
|
|
21
|
+
appInstalled: undefined,
|
|
22
|
+
iosAvailable: false,
|
|
23
|
+
iosDevices: 0,
|
|
24
|
+
gradleJavaHome: undefined,
|
|
25
|
+
gradleValid: false,
|
|
26
|
+
gradleFilesChecked: [],
|
|
27
|
+
gradleSuggestedFixes: [],
|
|
28
|
+
host: { os: hostOs, supported: false },
|
|
29
|
+
android: {
|
|
30
|
+
status: 'unsupported',
|
|
31
|
+
tools: {},
|
|
32
|
+
providers: {},
|
|
33
|
+
capabilities: {},
|
|
34
|
+
devices: [],
|
|
35
|
+
failures: [failureAndroid]
|
|
36
|
+
},
|
|
37
|
+
ios: {
|
|
38
|
+
status: 'unsupported',
|
|
39
|
+
tools: {},
|
|
40
|
+
providers: {},
|
|
41
|
+
capabilities: {},
|
|
42
|
+
devices: [],
|
|
43
|
+
failures: [failureIos]
|
|
44
|
+
},
|
|
45
|
+
summary: {
|
|
46
|
+
overall: 'blocked',
|
|
47
|
+
android: { ready: false, summary: `Host operating system '${hostOs}' is not supported.`, blockers: [failureMessage] },
|
|
48
|
+
ios: { ready: false, summary: `Host operating system '${hostOs}' is not supported.`, blockers: [failureMessage] },
|
|
49
|
+
gradle: { ready: false, summary: 'Gradle status unavailable on unsupported host', blockers: [], suggestedFixes: [] }
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execCmd } from '../exec.js';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
export function findInPath(cmd) {
|
|
6
|
+
try {
|
|
7
|
+
// prefer command -v for POSIX
|
|
8
|
+
const res = spawnSync('command', ['-v', cmd], { encoding: 'utf8' });
|
|
9
|
+
if (res.status === 0 && res.stdout)
|
|
10
|
+
return res.stdout.trim();
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
console.debug(`[findInPath] command -v ${cmd} failed: ${String(e)}`);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const res = spawnSync('which', [cmd], { encoding: 'utf8' });
|
|
17
|
+
if (res.status === 0 && res.stdout)
|
|
18
|
+
return res.stdout.trim();
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
console.debug(`[findInPath] which ${cmd} failed: ${String(e)}`);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
export function resolveAdbCmd() {
|
|
26
|
+
// Priority: explicit env ADB_PATH -> PATH -> known SDK locations -> fallback to 'adb'
|
|
27
|
+
if (process.env.ADB_PATH && process.env.ADB_PATH.trim())
|
|
28
|
+
return process.env.ADB_PATH;
|
|
29
|
+
const found = findInPath('adb');
|
|
30
|
+
if (found)
|
|
31
|
+
return found;
|
|
32
|
+
const sdkRoot = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME;
|
|
33
|
+
if (sdkRoot) {
|
|
34
|
+
const candidate = path.join(sdkRoot, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
|
|
35
|
+
if (existsSync(candidate))
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
// common macOS user SDK path
|
|
39
|
+
const homeSdk = path.join(process.env.HOME || '', 'Library', 'Android', 'sdk', 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
|
|
40
|
+
if (existsSync(homeSdk))
|
|
41
|
+
return homeSdk;
|
|
42
|
+
return 'adb';
|
|
43
|
+
}
|
|
44
|
+
export function getAdbCmd() { return resolveAdbCmd(); }
|
|
45
|
+
export function ensureAdbAvailable() {
|
|
46
|
+
const adb = resolveAdbCmd();
|
|
47
|
+
try {
|
|
48
|
+
const res = spawnSync(adb, ['--version'], { encoding: 'utf8' });
|
|
49
|
+
if (res.status === 0) {
|
|
50
|
+
return { adbCmd: adb, ok: true, version: (res.stdout || res.stderr || '').trim() };
|
|
51
|
+
}
|
|
52
|
+
return { adbCmd: adb, ok: false, error: (res.stderr || res.stdout || '').trim() };
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return { adbCmd: adb, ok: false, error: String(err) };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function getAdbArgs(args, deviceId) {
|
|
59
|
+
if (deviceId) {
|
|
60
|
+
return ['-s', deviceId, ...args];
|
|
61
|
+
}
|
|
62
|
+
return args;
|
|
63
|
+
}
|
|
64
|
+
export function getAdbTimeout(args, customTimeout) {
|
|
65
|
+
if (typeof customTimeout === 'number' && !isNaN(customTimeout))
|
|
66
|
+
return customTimeout;
|
|
67
|
+
const envTimeout = parseInt(process.env.MCP_ADB_TIMEOUT || process.env.ADB_TIMEOUT || '', 10);
|
|
68
|
+
if (!isNaN(envTimeout) && envTimeout > 0)
|
|
69
|
+
return envTimeout;
|
|
70
|
+
if (args.includes('logcat'))
|
|
71
|
+
return 10000;
|
|
72
|
+
if (args.includes('uiautomator') && args.includes('dump'))
|
|
73
|
+
return 20000;
|
|
74
|
+
return 120000;
|
|
75
|
+
}
|
|
76
|
+
export async function execAdb(args, deviceId, options = {}) {
|
|
77
|
+
const adbArgs = getAdbArgs(args, deviceId);
|
|
78
|
+
const timeoutMs = getAdbTimeout(args, options.timeout);
|
|
79
|
+
const res = await execCmd(getAdbCmd(), adbArgs, { timeout: timeoutMs, env: options.env, cwd: typeof options.cwd === 'string' ? options.cwd : undefined, shell: !!options.shell });
|
|
80
|
+
if (res.exitCode !== 0)
|
|
81
|
+
throw new Error(res.stderr || `Command failed with code ${res.exitCode}`);
|
|
82
|
+
return res.stdout;
|
|
83
|
+
}
|
|
84
|
+
export async function spawnAdb(args, deviceId, options = {}) {
|
|
85
|
+
const adbArgs = getAdbArgs(args, deviceId);
|
|
86
|
+
const timeoutMs = getAdbTimeout(args, options.timeout);
|
|
87
|
+
const res = await execCmd(getAdbCmd(), adbArgs, { timeout: timeoutMs, env: options.env, cwd: typeof options.cwd === 'string' ? options.cwd : undefined, shell: !!options.shell });
|
|
88
|
+
return { stdout: res.stdout, stderr: res.stderr, code: res.exitCode };
|
|
89
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { promises as fsPromises, existsSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { detectJavaHome } from '../java.js';
|
|
4
|
+
import { checkGradle } from '../../system/gradle.js';
|
|
5
|
+
import { resolveAdbCmd } from './commands.js';
|
|
6
|
+
export function mergeEnv(overrides) {
|
|
7
|
+
const env = {};
|
|
8
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
9
|
+
if (typeof value === 'string')
|
|
10
|
+
env[key] = value;
|
|
11
|
+
}
|
|
12
|
+
for (const [key, value] of Object.entries(overrides || {})) {
|
|
13
|
+
if (typeof value === 'string')
|
|
14
|
+
env[key] = value;
|
|
15
|
+
}
|
|
16
|
+
return env;
|
|
17
|
+
}
|
|
18
|
+
export async function prepareGradle(projectPath, envOverrides = {}) {
|
|
19
|
+
const env = mergeEnv(envOverrides);
|
|
20
|
+
const gradlewPath = path.join(projectPath, 'gradlew');
|
|
21
|
+
const gradleCmd = existsSync(gradlewPath) ? './gradlew' : 'gradle';
|
|
22
|
+
const execCmd = existsSync(gradlewPath) ? gradlewPath : gradleCmd;
|
|
23
|
+
// Start with a default task; callers may append/override via env flags
|
|
24
|
+
const gradleArgs = [env.MCP_GRADLE_TASK || 'assembleDebug'];
|
|
25
|
+
// Respect generic MCP_BUILD_JOBS and Android-specific MCP_GRADLE_WORKERS
|
|
26
|
+
const workers = env.MCP_GRADLE_WORKERS || env.MCP_BUILD_JOBS;
|
|
27
|
+
if (workers) {
|
|
28
|
+
gradleArgs.push(`--max-workers=${workers}`);
|
|
29
|
+
}
|
|
30
|
+
// Respect gradle cache env: default enabled; set MCP_GRADLE_CACHE=0 to disable
|
|
31
|
+
if (env.MCP_GRADLE_CACHE === '0') {
|
|
32
|
+
gradleArgs.push('-Dorg.gradle.caching=false');
|
|
33
|
+
}
|
|
34
|
+
const detectedJavaHome = await detectJavaHome().catch(() => undefined);
|
|
35
|
+
// Check for problematic org.gradle.java.home entries (env or properties) and avoid passing invalid values to Gradle
|
|
36
|
+
let gradleCheck;
|
|
37
|
+
try {
|
|
38
|
+
gradleCheck = await checkGradle();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
gradleCheck = { gradleJavaHome: undefined, gradleValid: false, filesChecked: [], issues: [] };
|
|
42
|
+
}
|
|
43
|
+
// Ensure child processes can find Android platform-tools (adb, etc.) by
|
|
44
|
+
// prepending the platform-tools directory to PATH for spawned processes.
|
|
45
|
+
const adbPath = resolveAdbCmd();
|
|
46
|
+
let platformToolsDir = undefined;
|
|
47
|
+
try {
|
|
48
|
+
if (adbPath && adbPath !== 'adb' && existsSync(adbPath)) {
|
|
49
|
+
platformToolsDir = path.dirname(adbPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.debug(`[prepareGradle] error resolving adbPath: ${String(e)}`);
|
|
54
|
+
}
|
|
55
|
+
const pathParts = [];
|
|
56
|
+
configureJava(env, gradleArgs, pathParts, detectedJavaHome, gradleCheck);
|
|
57
|
+
configurePlatformTools(env, pathParts, platformToolsDir);
|
|
58
|
+
if (pathParts.length > 0) {
|
|
59
|
+
env.PATH = `${pathParts.join(path.delimiter)}${path.delimiter}${env.PATH || ''}`;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
delete env.SHELL;
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
console.debug('[prepareGradle] failed to delete SHELL from env:', String(e));
|
|
66
|
+
}
|
|
67
|
+
const useWrapper = existsSync(gradlewPath);
|
|
68
|
+
const spawnOpts = { cwd: projectPath, env };
|
|
69
|
+
if (useWrapper) {
|
|
70
|
+
try {
|
|
71
|
+
await fsPromises.chmod(gradlewPath, 0o755);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
console.debug('[prepareGradle] chmod failed for gradlew:', String(e));
|
|
75
|
+
}
|
|
76
|
+
// Execute the wrapper directly without a shell to avoid shell tokenization of args (spaces in paths)
|
|
77
|
+
spawnOpts.shell = false;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Prefer executing gradle directly without invoking a shell to preserve argument boundaries
|
|
81
|
+
spawnOpts.shell = false;
|
|
82
|
+
}
|
|
83
|
+
return { execCmd, gradleArgs, spawnOpts };
|
|
84
|
+
}
|
|
85
|
+
function configureJava(env, gradleArgs, pathParts, detectedJavaHome, gradleCheck) {
|
|
86
|
+
// Prefer a detected (validated) Java home from the system/IDE
|
|
87
|
+
if (detectedJavaHome) {
|
|
88
|
+
if (env.JAVA_HOME !== detectedJavaHome) {
|
|
89
|
+
env.JAVA_HOME = detectedJavaHome;
|
|
90
|
+
}
|
|
91
|
+
const javaBin = path.join(detectedJavaHome, 'bin');
|
|
92
|
+
pathParts.push(javaBin);
|
|
93
|
+
gradleArgs.push(`-Dorg.gradle.java.home=${detectedJavaHome}`);
|
|
94
|
+
gradleArgs.push('--no-daemon');
|
|
95
|
+
env.GRADLE_JAVA_HOME = detectedJavaHome;
|
|
96
|
+
}
|
|
97
|
+
else if (gradleCheck && gradleCheck.gradleJavaHome) {
|
|
98
|
+
// There's an org.gradle.java.home configured somewhere (env or gradle.properties)
|
|
99
|
+
if (gradleCheck.gradleValid) {
|
|
100
|
+
const p = gradleCheck.gradleJavaHome;
|
|
101
|
+
const javaBin = path.join(p, 'bin');
|
|
102
|
+
if (!env.PATH || !env.PATH.includes(javaBin))
|
|
103
|
+
pathParts.push(javaBin);
|
|
104
|
+
gradleArgs.push(`-Dorg.gradle.java.home=${p}`);
|
|
105
|
+
gradleArgs.push('--no-daemon');
|
|
106
|
+
env.GRADLE_JAVA_HOME = p;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Invalid gradle java home detected: avoid passing it to Gradle and remove from spawn env
|
|
110
|
+
console.debug(`[prepareGradle] Invalid org.gradle.java.home detected (${gradleCheck.gradleJavaHome}); removing from spawn env to avoid Gradle error.`);
|
|
111
|
+
try {
|
|
112
|
+
delete env.GRADLE_JAVA_HOME;
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function configurePlatformTools(env, pathParts, platformToolsDir) {
|
|
119
|
+
if (platformToolsDir) {
|
|
120
|
+
// Prepend platform-tools so gradle and child tools find adb without modifying global env
|
|
121
|
+
if (!env.PATH || !env.PATH.includes(platformToolsDir)) {
|
|
122
|
+
pathParts.push(platformToolsDir);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME) {
|
|
126
|
+
const sdkRoot = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME || '';
|
|
127
|
+
const candidate = path.join(sdkRoot, 'platform-tools');
|
|
128
|
+
if (existsSync(candidate) && (!env.PATH || !env.PATH.includes(candidate))) {
|
|
129
|
+
pathParts.push(candidate);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// also try common user sdk location
|
|
134
|
+
const homeSdkTools = path.join(process.env.HOME || '', 'Library', 'Android', 'sdk', 'platform-tools');
|
|
135
|
+
if (existsSync(homeSdkTools) && (!env.PATH || !env.PATH.includes(homeSdkTools))) {
|
|
136
|
+
pathParts.push(homeSdkTools);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|