android-midscene-automation 0.2.4 → 1.1.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/CHANGELOG.md +7 -0
- package/README.md +35 -0
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +4 -2
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +12 -0
- package/src/App.vue +10 -5
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +340 -121
- package/src/types.ts +1 -1
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""System, environment, and device diagnostics (artemis doctor).
|
|
16
|
+
|
|
17
|
+
The command renders the same readiness probes the web console's device wizard
|
|
18
|
+
and the ``mobile_diagnose`` MCP tool run (:mod:`artemis.core.diagnostics`), so
|
|
19
|
+
the terminal, the browser and the IDE agree on one verdict. Only two rows are
|
|
20
|
+
CLI-specific and not engine probes: Node.js/npm and the Showcase UI build.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import asyncio
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
import json
|
|
28
|
+
import platform as sys_platform
|
|
29
|
+
import shutil
|
|
30
|
+
import subprocess
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
from rich.console import Console
|
|
34
|
+
from rich.markup import escape
|
|
35
|
+
from rich.panel import Panel
|
|
36
|
+
from rich.table import Table
|
|
37
|
+
import typer
|
|
38
|
+
|
|
39
|
+
from artemis.config.paths import ROOT_DIR
|
|
40
|
+
from artemis.core.diagnostics.engine import readiness_engine
|
|
41
|
+
from artemis.core.diagnostics.readiness import (
|
|
42
|
+
Verdict,
|
|
43
|
+
adb_keys_corrupted,
|
|
44
|
+
base_verdict,
|
|
45
|
+
collect_readiness,
|
|
46
|
+
sort_by_fix_order,
|
|
47
|
+
)
|
|
48
|
+
from artemis.core.diagnostics.schema import ProbeResult, ProbeStatus
|
|
49
|
+
from artemis.platform import OSType, platform
|
|
50
|
+
from artemis.runtime.device_lock import DeviceExecutionLock
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class ExtraRow:
|
|
55
|
+
"""A CLI-only diagnostic row that is not backed by an engine probe."""
|
|
56
|
+
|
|
57
|
+
key: str
|
|
58
|
+
title: str
|
|
59
|
+
status: str # "pass" | "missing"
|
|
60
|
+
status_markup: str
|
|
61
|
+
summary: str
|
|
62
|
+
detail: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class FixOutcome:
|
|
67
|
+
"""Result of one ``--fix`` repair step."""
|
|
68
|
+
|
|
69
|
+
fix: str
|
|
70
|
+
success: bool
|
|
71
|
+
message: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Collection
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def _collect() -> list[ProbeResult]:
|
|
80
|
+
report, host = await collect_readiness()
|
|
81
|
+
return sort_by_fix_order([*report.probes, host])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def _apply_fixes(results: list[ProbeResult]) -> list[FixOutcome]:
|
|
85
|
+
"""Repair corrupted ADB keys and sweep stale device locks left by dead runners."""
|
|
86
|
+
outcomes: list[FixOutcome] = []
|
|
87
|
+
|
|
88
|
+
if adb_keys_corrupted(results):
|
|
89
|
+
result = await readiness_engine.heal_adb_keys()
|
|
90
|
+
outcomes.append(
|
|
91
|
+
FixOutcome(
|
|
92
|
+
fix="heal_adb_keys",
|
|
93
|
+
success=bool(result.get("success")),
|
|
94
|
+
message=str(result.get("message") or "")
|
|
95
|
+
or ("Corrupted RSA keys regenerated." if result.get("success") else "Heal failed."),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
outcomes.append(
|
|
100
|
+
FixOutcome(
|
|
101
|
+
fix="heal_adb_keys",
|
|
102
|
+
success=True,
|
|
103
|
+
message="ADB authentication keys are healthy; nothing to repair.",
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
removed = await asyncio.to_thread(DeviceExecutionLock.cleanup_stale_locks)
|
|
108
|
+
outcomes.append(
|
|
109
|
+
FixOutcome(
|
|
110
|
+
fix="cleanup_stale_locks",
|
|
111
|
+
success=True,
|
|
112
|
+
message=(
|
|
113
|
+
f"Removed {removed} stale device lock(s)/queue ticket(s)."
|
|
114
|
+
if removed
|
|
115
|
+
else "No stale device locks or queue tickets found."
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
return outcomes
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def _diagnose(fix: bool) -> tuple[list[ProbeResult], list[FixOutcome]]:
|
|
123
|
+
results = await _collect()
|
|
124
|
+
fixes: list[FixOutcome] = []
|
|
125
|
+
if fix:
|
|
126
|
+
fixes = await _apply_fixes(results)
|
|
127
|
+
results = await _collect()
|
|
128
|
+
return results, fixes
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
# CLI-only extra rows (not engine probes)
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _npm_row() -> ExtraRow:
|
|
137
|
+
npm_path = shutil.which("npm")
|
|
138
|
+
if npm_path:
|
|
139
|
+
return ExtraRow(
|
|
140
|
+
key="nodejs_npm",
|
|
141
|
+
title="Node.js / npm",
|
|
142
|
+
status="pass",
|
|
143
|
+
status_markup="[bold green]✔ Installed[/bold green]",
|
|
144
|
+
summary="Installed",
|
|
145
|
+
detail=f"{npm_path} (Ready to build Showcase UI)",
|
|
146
|
+
)
|
|
147
|
+
hint = (
|
|
148
|
+
"winget install OpenJS.NodeJS.LTS"
|
|
149
|
+
if platform.os_type == OSType.WINDOWS
|
|
150
|
+
else "brew install node"
|
|
151
|
+
if platform.os_type == OSType.MACOS
|
|
152
|
+
else "sudo apt-get install -y nodejs npm"
|
|
153
|
+
)
|
|
154
|
+
return ExtraRow(
|
|
155
|
+
key="nodejs_npm",
|
|
156
|
+
title="Node.js / npm",
|
|
157
|
+
status="missing",
|
|
158
|
+
status_markup="[dim]⚪ Optional[/dim]",
|
|
159
|
+
summary="Not Found",
|
|
160
|
+
detail=f"Not found in PATH. ({hint})",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _showcase_row() -> ExtraRow:
|
|
165
|
+
from artemis.resources import get_bundled_showcase_dist
|
|
166
|
+
|
|
167
|
+
base_dist = ROOT_DIR / "apps" / "showcase_ui" / "dist"
|
|
168
|
+
candidates = [
|
|
169
|
+
base_dist / "frontend" / "browser" / "index.html",
|
|
170
|
+
base_dist / "browser" / "index.html",
|
|
171
|
+
base_dist / "frontend" / "index.html",
|
|
172
|
+
base_dist / "index.html",
|
|
173
|
+
]
|
|
174
|
+
found_showcase = next((p for p in candidates if p.exists()), None)
|
|
175
|
+
if found_showcase is None:
|
|
176
|
+
bundled_showcase = get_bundled_showcase_dist()
|
|
177
|
+
if bundled_showcase is not None:
|
|
178
|
+
found_showcase = bundled_showcase / "index.html"
|
|
179
|
+
if found_showcase:
|
|
180
|
+
return ExtraRow(
|
|
181
|
+
key="showcase_ui",
|
|
182
|
+
title="Showcase UI",
|
|
183
|
+
status="pass",
|
|
184
|
+
status_markup="[bold green]✔ Compiled[/bold green]",
|
|
185
|
+
summary="Compiled",
|
|
186
|
+
detail=f"{found_showcase.parent} (Angular ready)",
|
|
187
|
+
)
|
|
188
|
+
return ExtraRow(
|
|
189
|
+
key="showcase_ui",
|
|
190
|
+
title="Showcase UI",
|
|
191
|
+
status="missing",
|
|
192
|
+
status_markup="[bold yellow]○ Not Compiled[/bold yellow]",
|
|
193
|
+
summary="Not Compiled",
|
|
194
|
+
detail="Run ./start.sh or artemis ui to auto-compile.",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _helper_row(results: list[ProbeResult]) -> ExtraRow | None:
|
|
199
|
+
"""State of the Artemis accessibility helper on the one ready, idle device.
|
|
200
|
+
|
|
201
|
+
Tasks install the helper themselves when they take a device; this row lets a
|
|
202
|
+
person pre-install it so the first task does not pay the install delay, and
|
|
203
|
+
tells them the helper is a thing they can remove.
|
|
204
|
+
"""
|
|
205
|
+
from artemis.clients.screen_client_factory import resolve_backend
|
|
206
|
+
from artemis.runtime.helper_manager import helper_manager
|
|
207
|
+
|
|
208
|
+
if resolve_backend().value == "uiautomator":
|
|
209
|
+
return None
|
|
210
|
+
adb = next((r for r in results if r.id == "android_adb"), None)
|
|
211
|
+
devices = (adb.metadata.get("devices") if adb else None) or []
|
|
212
|
+
ready = [str(d.get("serial")) for d in devices if d.get("state") == "device"]
|
|
213
|
+
if len(ready) != 1:
|
|
214
|
+
return None
|
|
215
|
+
serial = ready[0]
|
|
216
|
+
try:
|
|
217
|
+
status = helper_manager.status(serial)
|
|
218
|
+
except (OSError, ValueError, subprocess.SubprocessError) as exc:
|
|
219
|
+
return ExtraRow(
|
|
220
|
+
key="accessibility_helper",
|
|
221
|
+
title="Artemis Accessibility Helper",
|
|
222
|
+
status="missing",
|
|
223
|
+
status_markup="[dim]⚪ Unknown[/dim]",
|
|
224
|
+
summary="Could not read",
|
|
225
|
+
detail=f"{serial}: {exc}",
|
|
226
|
+
)
|
|
227
|
+
healthy = status["installed"] and status["enabled"] and not status["outdated"]
|
|
228
|
+
if healthy and status["reachable"]:
|
|
229
|
+
version = status.get("installed_version")
|
|
230
|
+
note = " (newer than the bundled build)" if status.get("newer_than_bundled") else ""
|
|
231
|
+
return ExtraRow(
|
|
232
|
+
key="accessibility_helper",
|
|
233
|
+
title="Artemis Accessibility Helper",
|
|
234
|
+
status="pass",
|
|
235
|
+
status_markup="[bold green]✔ Ready[/bold green]",
|
|
236
|
+
summary=f"v{version} on {serial}{note}",
|
|
237
|
+
detail=f"Remove any time with: artemis helper uninstall --serial {serial}",
|
|
238
|
+
)
|
|
239
|
+
if not status["installed"]:
|
|
240
|
+
summary, why = "Not installed", "the first task on this device will install it (about 3 s)"
|
|
241
|
+
elif status["outdated"]:
|
|
242
|
+
summary = f"Outdated (v{status['installed_version']} < v{status['bundled_version']})"
|
|
243
|
+
why = "the next task will upgrade it"
|
|
244
|
+
elif not status["enabled"]:
|
|
245
|
+
summary, why = "Service disabled", "the next task will try to enable it"
|
|
246
|
+
else:
|
|
247
|
+
summary, why = "Not answering", "unlock the phone or reinstall with --force"
|
|
248
|
+
return ExtraRow(
|
|
249
|
+
key="accessibility_helper",
|
|
250
|
+
title="Artemis Accessibility Helper",
|
|
251
|
+
status="missing",
|
|
252
|
+
status_markup="[bold yellow]○ Pending[/bold yellow]",
|
|
253
|
+
summary=summary,
|
|
254
|
+
detail=f"{why}; pre-install now: artemis helper install --serial {serial}",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
# Rendering
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _status_markup(result: ProbeResult) -> str:
|
|
264
|
+
status = result.status
|
|
265
|
+
if status is ProbeStatus.PASS:
|
|
266
|
+
return "[bold green]✔ OK[/bold green]"
|
|
267
|
+
if status is ProbeStatus.SKIPPED:
|
|
268
|
+
return "[dim]○ Skipped[/dim]"
|
|
269
|
+
summary = escape(result.summary)
|
|
270
|
+
if status is ProbeStatus.WARN:
|
|
271
|
+
# A warning keeps its summary whether or not it blocks (the host
|
|
272
|
+
# probe's warnings degrade the verdict without blocking it).
|
|
273
|
+
return f"[bold yellow]⚠ {summary}[/bold yellow]"
|
|
274
|
+
if not result.is_blocker:
|
|
275
|
+
return "[dim]⚪ Optional[/dim]"
|
|
276
|
+
return f"[bold red]✖ {summary}[/bold red]"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _action_lines(result: ProbeResult) -> list[str]:
|
|
280
|
+
"""One plain-text line per remediation; ``&&`` chains split for PowerShell 5.1."""
|
|
281
|
+
lines: list[str] = []
|
|
282
|
+
for action in result.actions:
|
|
283
|
+
payload = action.payload.strip()
|
|
284
|
+
if not payload:
|
|
285
|
+
continue
|
|
286
|
+
if action.action_type == "command":
|
|
287
|
+
for part in payload.split("&&"):
|
|
288
|
+
if part.strip():
|
|
289
|
+
lines.append(f"Run: {part.strip()}")
|
|
290
|
+
else:
|
|
291
|
+
lines.append(payload)
|
|
292
|
+
return lines
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _details_markup(result: ProbeResult) -> str:
|
|
296
|
+
# Only summary/description/actions are ever rendered. Probe metadata can
|
|
297
|
+
# carry raw credentials (the console prefills its settings form from it)
|
|
298
|
+
# and must never reach a terminal or a log.
|
|
299
|
+
parts = [escape(result.description)]
|
|
300
|
+
if result.status is not ProbeStatus.PASS:
|
|
301
|
+
for line in _action_lines(result):
|
|
302
|
+
if line.startswith("Run: "):
|
|
303
|
+
parts.append(f"[bold cyan]Run:[/bold cyan] {escape(line[len('Run: ') :])}")
|
|
304
|
+
else:
|
|
305
|
+
parts.append(escape(line))
|
|
306
|
+
return "\n".join(parts)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _render_table(results: list[ProbeResult], extras: list[ExtraRow]) -> Table:
|
|
310
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
311
|
+
table.add_column("Component", style="dim", min_width=22, max_width=30)
|
|
312
|
+
table.add_column("Status", min_width=12, max_width=28)
|
|
313
|
+
table.add_column("Details & Recommendations", overflow="fold")
|
|
314
|
+
for result in results:
|
|
315
|
+
table.add_row(escape(result.title), _status_markup(result), _details_markup(result))
|
|
316
|
+
for extra in extras:
|
|
317
|
+
table.add_row(extra.title, extra.status_markup, escape(extra.detail))
|
|
318
|
+
return table
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _render_fixes(console: Console, fixes: list[FixOutcome]) -> None:
|
|
322
|
+
lines = []
|
|
323
|
+
for outcome in fixes:
|
|
324
|
+
mark = "[bold green]✔[/bold green]" if outcome.success else "[bold red]✖[/bold red]"
|
|
325
|
+
lines.append(f"{mark} {escape(outcome.fix)}: {escape(outcome.message)}")
|
|
326
|
+
console.print(Panel("\n".join(lines), title="Repairs (--fix)", expand=False))
|
|
327
|
+
console.print()
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _render_footer(console: Console, verdict: Verdict, results: list[ProbeResult]) -> None:
|
|
331
|
+
if verdict == "ready":
|
|
332
|
+
console.print(
|
|
333
|
+
Panel(
|
|
334
|
+
"[bold green]🎉 All system checks passed![/bold green]\n\n"
|
|
335
|
+
"Run your first automation task now:\n"
|
|
336
|
+
' [bold cyan]artemis run "Open Settings and check Battery level"[/bold cyan]',
|
|
337
|
+
title="Status: Ready",
|
|
338
|
+
expand=False,
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if verdict == "degraded":
|
|
344
|
+
console.print(
|
|
345
|
+
Panel(
|
|
346
|
+
"[bold green]Required checks passed.[/bold green] Optional components are "
|
|
347
|
+
"missing; tasks can run, but some features (video replay, OCR) are off.\n\n"
|
|
348
|
+
"Run your first automation task now:\n"
|
|
349
|
+
' [bold cyan]artemis run "Open Settings and check Battery level"[/bold cyan]',
|
|
350
|
+
title="Status: Ready (optional gaps)",
|
|
351
|
+
expand=False,
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
failing = [r.title for r in results if r.is_blocker and r.status is not ProbeStatus.PASS]
|
|
357
|
+
tips = [
|
|
358
|
+
"[bold yellow]💡 Blocking issues:[/bold yellow] " + escape(", ".join(failing)),
|
|
359
|
+
"",
|
|
360
|
+
"• API keys & device setup: run [bold cyan]artemis init[/bold cyan].",
|
|
361
|
+
"• Follow the [bold cyan]Run:[/bold cyan] lines above, then re-run "
|
|
362
|
+
"[bold cyan]artemis doctor[/bold cyan].",
|
|
363
|
+
"• Using an IDE (Claude Code, Cursor, ...)? Ask it to call "
|
|
364
|
+
"[bold cyan]mobile_diagnose[/bold cyan] - it runs these same checks and can "
|
|
365
|
+
"apply safe fixes from the assistant.",
|
|
366
|
+
]
|
|
367
|
+
if adb_keys_corrupted(results):
|
|
368
|
+
tips.append(
|
|
369
|
+
"• Corrupted ADB keys detected: run [bold cyan]artemis doctor --fix[/bold cyan]."
|
|
370
|
+
)
|
|
371
|
+
console.print(Panel("\n".join(tips), title="Action Required", expand=False))
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _json_document(
|
|
375
|
+
results: list[ProbeResult],
|
|
376
|
+
extras: list[ExtraRow],
|
|
377
|
+
fixes: list[FixOutcome],
|
|
378
|
+
verdict: Verdict,
|
|
379
|
+
) -> dict[str, Any]:
|
|
380
|
+
# Metadata is intentionally omitted: it may hold raw API keys.
|
|
381
|
+
checks = [
|
|
382
|
+
{
|
|
383
|
+
"id": r.id,
|
|
384
|
+
"title": r.title,
|
|
385
|
+
"status": r.status.value,
|
|
386
|
+
"required": r.is_blocker,
|
|
387
|
+
"summary": r.summary,
|
|
388
|
+
"detail": r.description,
|
|
389
|
+
"fix": []
|
|
390
|
+
if r.status is ProbeStatus.PASS
|
|
391
|
+
else [
|
|
392
|
+
{"type": a.action_type, "label": a.label, "payload": a.payload} for a in r.actions
|
|
393
|
+
],
|
|
394
|
+
}
|
|
395
|
+
for r in results
|
|
396
|
+
]
|
|
397
|
+
return {
|
|
398
|
+
"verdict": verdict,
|
|
399
|
+
"checks": checks,
|
|
400
|
+
"extras": {
|
|
401
|
+
e.key: {
|
|
402
|
+
"title": e.title,
|
|
403
|
+
"status": e.status,
|
|
404
|
+
"summary": e.summary,
|
|
405
|
+
"detail": e.detail,
|
|
406
|
+
}
|
|
407
|
+
for e in extras
|
|
408
|
+
},
|
|
409
|
+
"fixes": [{"fix": f.fix, "success": f.success, "message": f.message} for f in fixes],
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# ---------------------------------------------------------------------------
|
|
414
|
+
# Command
|
|
415
|
+
# ---------------------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def doctor_command(
|
|
419
|
+
fix: bool = typer.Option(
|
|
420
|
+
False,
|
|
421
|
+
"--fix",
|
|
422
|
+
"-f",
|
|
423
|
+
help=(
|
|
424
|
+
"Automatically repair fixable environment issues (corrupted ADB authentication "
|
|
425
|
+
"keys, stale device locks left by crashed runners)."
|
|
426
|
+
),
|
|
427
|
+
),
|
|
428
|
+
json_output: bool = typer.Option(
|
|
429
|
+
False,
|
|
430
|
+
"--json",
|
|
431
|
+
help="Print the diagnosis as a JSON document instead of a table.",
|
|
432
|
+
),
|
|
433
|
+
) -> None:
|
|
434
|
+
"""Run diagnostics to inspect system dependencies, device connectivity, and configuration."""
|
|
435
|
+
results, fixes = asyncio.run(_diagnose(fix))
|
|
436
|
+
extras = [_npm_row(), _showcase_row()]
|
|
437
|
+
helper_row = _helper_row(results)
|
|
438
|
+
if helper_row is not None:
|
|
439
|
+
extras.append(helper_row)
|
|
440
|
+
verdict = base_verdict(results)
|
|
441
|
+
|
|
442
|
+
if json_output:
|
|
443
|
+
typer.echo(
|
|
444
|
+
json.dumps(
|
|
445
|
+
_json_document(results, extras, fixes, verdict), indent=2, ensure_ascii=False
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
else:
|
|
449
|
+
console = Console()
|
|
450
|
+
console.print()
|
|
451
|
+
os_name = sys_platform.system()
|
|
452
|
+
arch_name = sys_platform.machine()
|
|
453
|
+
console.print(
|
|
454
|
+
Panel(
|
|
455
|
+
f"[bold cyan]☕ Artemis System & Environment Doctor[/bold cyan]\n"
|
|
456
|
+
f"[dim]Platform: {os_name} ({arch_name}) | PAL: {platform.os_type.value}[/dim]",
|
|
457
|
+
expand=False,
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
if fixes:
|
|
461
|
+
_render_fixes(console, fixes)
|
|
462
|
+
console.print(_render_table(results, extras))
|
|
463
|
+
console.print()
|
|
464
|
+
_render_footer(console, verdict, results)
|
|
465
|
+
console.print()
|
|
466
|
+
|
|
467
|
+
if verdict == "blocked":
|
|
468
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""``artemis helper``: manage the Accessibility Helper APK on attached devices.
|
|
16
|
+
|
|
17
|
+
Tasks provision the helper themselves when they take a device, so these
|
|
18
|
+
commands exist for the cases the automatic path deliberately leaves alone:
|
|
19
|
+
inspecting a device, forcing a reinstall, and removing the helper (which is
|
|
20
|
+
never done automatically).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
from typing import Annotated
|
|
27
|
+
|
|
28
|
+
from rich.console import Console
|
|
29
|
+
from rich.table import Table
|
|
30
|
+
import typer
|
|
31
|
+
|
|
32
|
+
from artemis.runtime.device_pool import device_pool
|
|
33
|
+
from artemis.runtime.helper_manager import helper_manager
|
|
34
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
35
|
+
|
|
36
|
+
logger = get_logger(__name__)
|
|
37
|
+
helper_app = typer.Typer(help="Install, inspect, or remove the Artemis Accessibility Helper APK.")
|
|
38
|
+
|
|
39
|
+
SerialOption = Annotated[
|
|
40
|
+
str | None,
|
|
41
|
+
typer.Option("--serial", "-s", help="Device serial. Defaults to the only ready device."),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _resolve_serial(serial: str | None) -> str:
|
|
46
|
+
if serial:
|
|
47
|
+
return serial
|
|
48
|
+
ready = [d.serial for d in device_pool.get_ready_devices()]
|
|
49
|
+
if len(ready) == 1:
|
|
50
|
+
return ready[0]
|
|
51
|
+
if not ready:
|
|
52
|
+
typer.secho("No authorized device is attached.", fg=typer.colors.RED)
|
|
53
|
+
else:
|
|
54
|
+
typer.secho(
|
|
55
|
+
"Several devices are ready; pass --serial, or --all to target every idle one: "
|
|
56
|
+
+ ", ".join(ready),
|
|
57
|
+
fg=typer.colors.RED,
|
|
58
|
+
)
|
|
59
|
+
raise typer.Exit(code=1)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _idle_ready_serials() -> list[str]:
|
|
63
|
+
"""Every authorized device that no Artemis task currently holds."""
|
|
64
|
+
return [d.serial for d in device_pool.get_ready_devices() if not d.is_busy]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _bool(value: object) -> str:
|
|
68
|
+
return "[green]yes[/green]" if value else "[red]no[/red]"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@helper_app.command("status")
|
|
72
|
+
def helper_status(
|
|
73
|
+
serial: SerialOption = None,
|
|
74
|
+
as_json: Annotated[bool, typer.Option("--json", help="Print raw JSON.")] = False,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Show install, version, service and tunnel state of the helper on a device."""
|
|
77
|
+
target = _resolve_serial(serial)
|
|
78
|
+
status = helper_manager.status(target)
|
|
79
|
+
if as_json:
|
|
80
|
+
typer.echo(json.dumps(status, indent=2))
|
|
81
|
+
return
|
|
82
|
+
table = Table(title=f"Accessibility helper on {target}")
|
|
83
|
+
table.add_column("Field", style="cyan")
|
|
84
|
+
table.add_column("Value")
|
|
85
|
+
table.add_row("Installed", _bool(status["installed"]))
|
|
86
|
+
version = str(status["installed_version"])
|
|
87
|
+
if status.get("newer_than_bundled"):
|
|
88
|
+
version += f" [yellow](newer than the bundled v{status['bundled_version']}; not downgraded)[/yellow]"
|
|
89
|
+
elif status.get("outdated"):
|
|
90
|
+
version += f" [yellow](bundled is v{status['bundled_version']}; the next task upgrades it)[/yellow]"
|
|
91
|
+
table.add_row("Installed version", version)
|
|
92
|
+
table.add_row("Bundled version", str(status["bundled_version"]))
|
|
93
|
+
table.add_row("Service enabled", _bool(status["enabled"]))
|
|
94
|
+
tunnel = {
|
|
95
|
+
"session": f"this process, host port {status['forward_port']}",
|
|
96
|
+
"shared": f"another Artemis process, host port {status['forward_port']}",
|
|
97
|
+
"probe": "none (probed through a temporary forward)",
|
|
98
|
+
None: "none",
|
|
99
|
+
}[status.get("tunnel")]
|
|
100
|
+
table.add_row("Tunnel", tunnel)
|
|
101
|
+
table.add_row("Service answering", _bool(status["reachable"]))
|
|
102
|
+
if status.get("reachable"):
|
|
103
|
+
protocol = status.get("protocol_version")
|
|
104
|
+
proto_text = str(protocol)
|
|
105
|
+
if not status.get("protocol_supported"):
|
|
106
|
+
proto_text += " [red](too old for this host; reinstall with --force)[/red]"
|
|
107
|
+
table.add_row("Protocol", proto_text)
|
|
108
|
+
table.add_row("Session token pushed", _bool(status.get("token_set")))
|
|
109
|
+
table.add_row("Auto-install by tasks", _bool(status.get("auto_install", True)))
|
|
110
|
+
table.add_row("Transport id", str(status["transport_id"]))
|
|
111
|
+
Console().print(table)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _install_one(target: str, force: bool) -> bool:
|
|
115
|
+
def on_event(event: str, details: dict) -> None:
|
|
116
|
+
if event == "installing":
|
|
117
|
+
typer.echo(f"{target}: installing v{details.get('version_name')}...")
|
|
118
|
+
elif event == "upgrading":
|
|
119
|
+
typer.echo(
|
|
120
|
+
f"{target}: upgrading v{details.get('from_version')} -> "
|
|
121
|
+
f"v{details.get('to_version')}..."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
result = helper_manager.provision(target, force=force, on_event=on_event)
|
|
125
|
+
if result.ok:
|
|
126
|
+
typer.secho(
|
|
127
|
+
f"{target}: {result.action} (version {result.installed_version}, service enabled).",
|
|
128
|
+
fg=typer.colors.GREEN,
|
|
129
|
+
)
|
|
130
|
+
return True
|
|
131
|
+
typer.secho(f"{target}: {result.action}: {result.error}", fg=typer.colors.RED)
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@helper_app.command("install")
|
|
136
|
+
def helper_install(
|
|
137
|
+
serial: SerialOption = None,
|
|
138
|
+
force: Annotated[bool, typer.Option("--force", help="Reinstall even when up to date.")] = False,
|
|
139
|
+
all_devices: Annotated[
|
|
140
|
+
bool, typer.Option("--all", help="Every authorized device that no task is using.")
|
|
141
|
+
] = False,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Install or upgrade the helper to the bundled version and enable its service."""
|
|
144
|
+
if all_devices:
|
|
145
|
+
targets = _idle_ready_serials()
|
|
146
|
+
if not targets:
|
|
147
|
+
typer.secho("No idle, authorized device is attached.", fg=typer.colors.RED)
|
|
148
|
+
raise typer.Exit(code=1)
|
|
149
|
+
else:
|
|
150
|
+
targets = [_resolve_serial(serial)]
|
|
151
|
+
outcomes = [_install_one(target, force) for target in targets]
|
|
152
|
+
if not all(outcomes):
|
|
153
|
+
raise typer.Exit(code=1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@helper_app.command("uninstall")
|
|
157
|
+
def helper_uninstall(
|
|
158
|
+
serial: SerialOption = None,
|
|
159
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Do not ask for confirmation.")] = False,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Disable the service and remove the helper package from a device."""
|
|
162
|
+
target = _resolve_serial(serial)
|
|
163
|
+
if not yes and not typer.confirm(f"Remove the Artemis accessibility helper from {target}?"):
|
|
164
|
+
raise typer.Exit(code=0)
|
|
165
|
+
if helper_manager.uninstall(target):
|
|
166
|
+
typer.secho(f"{target}: helper removed.", fg=typer.colors.GREEN)
|
|
167
|
+
return
|
|
168
|
+
typer.secho(f"{target}: uninstall failed (is the package installed?).", fg=typer.colors.RED)
|
|
169
|
+
raise typer.Exit(code=1)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@helper_app.command("parity")
|
|
173
|
+
def helper_parity(
|
|
174
|
+
serial: SerialOption = None,
|
|
175
|
+
rounds: Annotated[int, typer.Option("--rounds", help="Timed dumps per backend.")] = 3,
|
|
176
|
+
as_json: Annotated[bool, typer.Option("--json", help="Print raw JSON.")] = False,
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Dump the current screen with the helper and with UIAutomator2 and compare them.
|
|
179
|
+
|
|
180
|
+
The two backends must describe the same visible screen: every labelled
|
|
181
|
+
UIAutomator element should have a helper element with the same label at
|
|
182
|
+
the same place, the helper must emit no off-screen or negative bounds, and
|
|
183
|
+
its dump should not be slower. Run it on a busy screen (a scrolled list, a
|
|
184
|
+
dialog over an app) to catch regressions the unit tests cannot see.
|
|
185
|
+
"""
|
|
186
|
+
from artemis.core.diagnostics.hierarchy_parity import compare_backends
|
|
187
|
+
|
|
188
|
+
target = _resolve_serial(serial)
|
|
189
|
+
report = compare_backends(target, rounds=max(1, rounds))
|
|
190
|
+
if as_json:
|
|
191
|
+
typer.echo(json.dumps(report, indent=2, ensure_ascii=False))
|
|
192
|
+
raise typer.Exit(code=0 if report["ok"] else 1)
|
|
193
|
+
|
|
194
|
+
table = Table(title=f"Hierarchy parity on {target}")
|
|
195
|
+
table.add_column("Metric", style="cyan")
|
|
196
|
+
table.add_column("Helper")
|
|
197
|
+
table.add_column("UIAutomator2")
|
|
198
|
+
h, u = report["helper"], report["uiautomator"]
|
|
199
|
+
table.add_row("Dump time, median", f"{h['median_ms']:.0f} ms", f"{u['median_ms']:.0f} ms")
|
|
200
|
+
table.add_row("Nodes", str(h["nodes"]), str(u["nodes"]))
|
|
201
|
+
table.add_row("Labelled nodes (text / desc)", str(h["labelled"]), str(u["labelled"]))
|
|
202
|
+
table.add_row("Negative bounds", str(h["negative_bounds"]), str(u["negative_bounds"]))
|
|
203
|
+
table.add_row("Off-screen bounds", str(h["offscreen_bounds"]), str(u["offscreen_bounds"]))
|
|
204
|
+
table.add_row("Foreground package", str(h["package"]), str(u["package"]))
|
|
205
|
+
Console().print(table)
|
|
206
|
+
|
|
207
|
+
m = report["match"]
|
|
208
|
+
typer.echo(
|
|
209
|
+
f"UIAutomator labels found in helper dump: {m['uiautomator_in_helper']}/{m['uiautomator_labelled']}"
|
|
210
|
+
f" ({m['recall']:.0%}); helper labels found in UIAutomator dump: "
|
|
211
|
+
f"{m['helper_in_uiautomator']}/{m['helper_labelled']} ({m['precision']:.0%})"
|
|
212
|
+
)
|
|
213
|
+
for label in m["missing_in_helper"][:10]:
|
|
214
|
+
typer.echo(f" helper lacks: {label}")
|
|
215
|
+
for label in m["extra_in_helper"][:10]:
|
|
216
|
+
typer.echo(f" helper adds: {label}")
|
|
217
|
+
for problem in report["problems"]:
|
|
218
|
+
typer.secho(f" ! {problem}", fg=typer.colors.RED)
|
|
219
|
+
typer.secho(
|
|
220
|
+
"PARITY OK" if report["ok"] else "PARITY FAILED",
|
|
221
|
+
fg=typer.colors.GREEN if report["ok"] else typer.colors.RED,
|
|
222
|
+
)
|
|
223
|
+
raise typer.Exit(code=0 if report["ok"] else 1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
__all__ = ["helper_app"]
|