mobster-cli 0.4.3__py3-none-any.whl
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.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""`mobster ci`'s parser and output (ci.py does the work)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import unicodedata
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import DevError, main, say, subcommands
|
|
10
|
+
from .ci import HOSTED_HOSTS
|
|
11
|
+
|
|
12
|
+
MARKS = {"ok": "✓", "warn": "!", "fail": "✗", "skip": "-"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def add_arguments(parser, helpers):
|
|
16
|
+
add = subcommands(parser, "ci_command")
|
|
17
|
+
init = add("init", help="write the CI workflow for this repository (GitHub Actions, GitLab CI or a hosted Mac)",
|
|
18
|
+
description="Write the workflow that runs `mobster ci run` on every pull request or "
|
|
19
|
+
"merge request: on a Mac of yours as a self-hosted runner, or with --hosted "
|
|
20
|
+
"on a GitHub-hosted macOS runner. --host xcode-cloud, codemagic, bitrise or "
|
|
21
|
+
"circleci writes that service's file, which runs on its hosted Macs.")
|
|
22
|
+
init.add_argument("--host", choices=("github", "gitlab", *HOSTED_HOSTS),
|
|
23
|
+
help="where the repository lives, or the CI service to write for (default: from the origin "
|
|
24
|
+
"remote)")
|
|
25
|
+
init.add_argument("--hosted", action="store_true",
|
|
26
|
+
help="run on a GitHub-hosted macOS runner and sign in with a CI token, with no Mac of yours "
|
|
27
|
+
"(no iPhone either)")
|
|
28
|
+
init.add_argument("--device", action="append", metavar="NAME",
|
|
29
|
+
help="also test on this iPhone, plugged into the runner; repeatable (the runner must list each "
|
|
30
|
+
"in MOBSTER_RUNNER_DEVICES)")
|
|
31
|
+
init.add_argument("--testflight", action="append", metavar="BUNDLE",
|
|
32
|
+
help="with --device: your app's TestFlight build on the phone; repeatable")
|
|
33
|
+
init.add_argument("--force", action="store_true", help="replace the workflow file if it exists")
|
|
34
|
+
init.add_argument("--print", dest="print_only", action="store_true",
|
|
35
|
+
help="print the workflow instead of writing it")
|
|
36
|
+
|
|
37
|
+
doctor = add("doctor", help="check that this Mac is ready to be a CI runner",
|
|
38
|
+
description="Check this Mac as a CI runner: Xcode, a simulator runtime, Mobster for "
|
|
39
|
+
"Mac and its plan, system sleep, automatic login, the runner service, "
|
|
40
|
+
"free disk and the phones the runner allows. Reads only.")
|
|
41
|
+
doctor.add_argument("--json", action="store_true", help="print the rows as JSON")
|
|
42
|
+
doctor.add_argument("--hosted", action="store_true",
|
|
43
|
+
help="check for a hosted runner: skip the rows about keeping a Mac running and check the CI "
|
|
44
|
+
"token (automatic inside a hosted job)")
|
|
45
|
+
|
|
46
|
+
run = add("run", help="test the PR or MR this job builds; the report goes to .mobster/ci",
|
|
47
|
+
description="Run `mobster test pr` for the pull request or merge request this CI job "
|
|
48
|
+
"builds, with a screen recording of each simulator run. The suite's report, "
|
|
49
|
+
"JUnit file, frames, GIFs and videos go to .mobster/ci for the job's "
|
|
50
|
+
"artifacts. Works the same in a terminal.",
|
|
51
|
+
epilog="exit codes: 0 passed, 1 failed, 2 needs review, 3 couldn't run\n\n"
|
|
52
|
+
"examples:\n mobster ci run\n mobster ci run --device \"Test iPhone\"\n"
|
|
53
|
+
" mobster ci run --device \"Test iPhone\" --device \"Lab iPhone\" -- --spread\n"
|
|
54
|
+
" mobster ci run -- --retries 2 --parallel 2")
|
|
55
|
+
run.add_argument("--base", metavar="REF",
|
|
56
|
+
help="the branch the PR merges into (default: the job's target branch, else origin/main)")
|
|
57
|
+
run.add_argument("--sim", action="append", metavar="TYPE[@RUNTIME]",
|
|
58
|
+
help="a simulator device type to run on; repeatable (default: each check's own)")
|
|
59
|
+
run.add_argument("--device", action="append", metavar="NAME",
|
|
60
|
+
help="an iPhone plugged into this Mac; repeatable. In a CI job the runner must list each in "
|
|
61
|
+
"MOBSTER_RUNNER_DEVICES, or nothing runs")
|
|
62
|
+
run.add_argument("--testflight", action="append", metavar="BUNDLE",
|
|
63
|
+
help="with --device: your app's TestFlight build on the phone; repeatable")
|
|
64
|
+
run.add_argument("--no-video", action="store_true", help="don't record the simulator's screen")
|
|
65
|
+
run.add_argument("extra", nargs="*", metavar="TEST_FLAG",
|
|
66
|
+
help="more `mobster test pr` flags, after a --: mobster ci run -- --retries 2")
|
|
67
|
+
|
|
68
|
+
comment = add("comment", help="keep one comment on the PR or MR with the verdict and repro steps",
|
|
69
|
+
description="Post the verdict in .mobster/ci as one comment on the pull request "
|
|
70
|
+
"(GITHUB_TOKEN) or merge request (MOBSTER_GITLAB_TOKEN), updated in place "
|
|
71
|
+
"on every push: each failure's repro steps, frame, GIF, video and the "
|
|
72
|
+
"command that runs it again. Never fails the job.")
|
|
73
|
+
comment.add_argument("--artifact-url", metavar="URL",
|
|
74
|
+
help="the uploaded report's link (default: $MOBSTER_ARTIFACT_URL, else the job's page)")
|
|
75
|
+
comment.add_argument("--dry-run", action="store_true", help="print the comment and post nothing")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def run(args, *, cwd=None, environ=None):
|
|
79
|
+
environ = os.environ if environ is None else environ
|
|
80
|
+
handlers = {"init": _init, "doctor": _doctor, "run": _run, "comment": _comment}
|
|
81
|
+
return main("ci", handlers, args, cwd=Path(cwd or Path.cwd()), environ=environ)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _phones(args):
|
|
85
|
+
"""(--device, --testflight) as tuples; --testflight goes with --device (a TestFlight build is on an iPhone)."""
|
|
86
|
+
devices, testflight = tuple(args.device or ()), tuple(args.testflight or ())
|
|
87
|
+
for flag, value in [*(("--device", name) for name in devices), *(("--testflight", app) for app in testflight)]:
|
|
88
|
+
# a line break would end the run line of the workflow ci init writes
|
|
89
|
+
if any(unicodedata.category(char) in ("Cc", "Zl", "Zp") for char in value):
|
|
90
|
+
raise DevError(f"{flag} {value!r} holds a line break, a tab or another control character, so Mobster did "
|
|
91
|
+
"nothing.", "Remove it: no iPhone's name or app's bundle ID has one.")
|
|
92
|
+
if testflight and not devices:
|
|
93
|
+
raise DevError("--testflight goes with --device: a TestFlight build is on an iPhone, not a simulator.")
|
|
94
|
+
return devices, testflight
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _init(args, cwd, environ):
|
|
98
|
+
from . import ci
|
|
99
|
+
root = ci.repo_root(cwd)
|
|
100
|
+
host = args.host
|
|
101
|
+
if host is None:
|
|
102
|
+
code, url = ci._git(root, "remote", "get-url", "origin")
|
|
103
|
+
host = ci.remote_host(url) if code == 0 else None
|
|
104
|
+
devices, testflight = _phones(args)
|
|
105
|
+
if args.print_only:
|
|
106
|
+
say(ci.workflow(host, devices=devices, testflight=testflight, hosted=args.hosted)[1], sys.stdout)
|
|
107
|
+
return 0
|
|
108
|
+
hosted = args.hosted or host in HOSTED_HOSTS
|
|
109
|
+
target, notes = ci.init(root, host=host, devices=devices, testflight=testflight, force=args.force,
|
|
110
|
+
hosted=args.hosted)
|
|
111
|
+
if hosted:
|
|
112
|
+
from . import ci_hosts
|
|
113
|
+
lines = [f"Wrote {target.relative_to(root)}.", *notes, "", "Next:",
|
|
114
|
+
*(f" {number}. {step}" for number, step in enumerate(ci_hosts.NEXT[host], 1)),
|
|
115
|
+
"", "On the runner, `mobster ci doctor` checks this runner before the run."]
|
|
116
|
+
say("\n".join(lines))
|
|
117
|
+
return 0
|
|
118
|
+
github = host == "github"
|
|
119
|
+
steps = ["On the Mac that runs the checks: mobster ci doctor",
|
|
120
|
+
("Register it as a self-hosted runner with the label mobster" if github else
|
|
121
|
+
"Register it as a GitLab runner (shell executor) with the tag mobster") + f": {ci.DOCS}/ci/mac-runner",
|
|
122
|
+
f"Add ANTHROPIC_API_KEY as a {'repository secret' if github else 'masked CI/CD variable'}, for checks "
|
|
123
|
+
"with steps."]
|
|
124
|
+
if not github:
|
|
125
|
+
steps.append("Add MOBSTER_GITLAB_TOKEN, a project access token with the api scope, for the MR comment.")
|
|
126
|
+
if devices:
|
|
127
|
+
steps.append(f"On the runner, allow the phone{'' if len(devices) == 1 else 's'}: "
|
|
128
|
+
f"MOBSTER_RUNNER_DEVICES={','.join(devices)}")
|
|
129
|
+
steps.append(f"Commit the file and open a {'pull' if github else 'merge'} request.")
|
|
130
|
+
lines = [f"Wrote {target.relative_to(root)}.", *notes, "", "Next:",
|
|
131
|
+
*(f" {number}. {step}" for number, step in enumerate(steps, 1))]
|
|
132
|
+
if not (root / ".mobster" / "checks").is_dir():
|
|
133
|
+
lines += ["", "This project has no checks yet. `mobster test pr` drafts one for each screen a PR changes, "
|
|
134
|
+
"and `mobster test record` turns a finished task into one."]
|
|
135
|
+
say("\n".join(lines))
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _doctor(args, cwd, environ):
|
|
140
|
+
from . import ci
|
|
141
|
+
rows = ci.doctor(environ=environ, hosted=True if args.hosted else None)
|
|
142
|
+
failed = any(row.status == "fail" for row in rows)
|
|
143
|
+
if args.json:
|
|
144
|
+
say(json.dumps({"ok": not failed, "rows": [row.public() for row in rows]}, ensure_ascii=False))
|
|
145
|
+
return 1 if failed else 0
|
|
146
|
+
width = max(len(row.name) for row in rows)
|
|
147
|
+
lines = []
|
|
148
|
+
for row in rows:
|
|
149
|
+
lines.append(f"{MARKS[row.status]} {row.name.ljust(width)} {row.detail}")
|
|
150
|
+
if row.fix and row.status != "ok":
|
|
151
|
+
lines.append(f" {' ' * width} {row.fix}")
|
|
152
|
+
if failed:
|
|
153
|
+
lines += ["", "Fix the rows marked ✗, then run `mobster ci doctor` again."]
|
|
154
|
+
else:
|
|
155
|
+
lines += ["", "This Mac is ready to run Mobster's checks. Next, `mobster sim doctor --fix` prepares a "
|
|
156
|
+
"simulator, so the first job doesn't wait for one."]
|
|
157
|
+
if args.hosted or ci.is_hosted(environ):
|
|
158
|
+
lines[-1:] = ["This runner is ready to run Mobster's checks."]
|
|
159
|
+
say("\n".join(lines))
|
|
160
|
+
return 1 if failed else 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _run(args, cwd, environ):
|
|
164
|
+
from ..devtools import ProgressLines
|
|
165
|
+
from . import ci
|
|
166
|
+
extra = list(args.extra or ())
|
|
167
|
+
devices, testflight = _phones(args)
|
|
168
|
+
root = ci.repo_root(cwd)
|
|
169
|
+
progress = ProgressLines(sys.stderr)
|
|
170
|
+
code, doc = ci.run_checks(root, devices=devices, testflight=testflight, sims=tuple(args.sim or ()),
|
|
171
|
+
video=not args.no_video, base=args.base, extra=extra, environ=environ,
|
|
172
|
+
progress=progress)
|
|
173
|
+
progress.end()
|
|
174
|
+
say(f"{doc['headline']}. Report: {ci.CI_DIR}/")
|
|
175
|
+
return code
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _comment(args, cwd, environ):
|
|
179
|
+
from . import ci
|
|
180
|
+
root = ci.repo_root(cwd)
|
|
181
|
+
body = ci.comment_markdown(root, environ, args.artifact_url)
|
|
182
|
+
target = root / ci.CI_DIR / "comment.md"
|
|
183
|
+
try:
|
|
184
|
+
target.write_text(body, encoding="utf-8")
|
|
185
|
+
except OSError:
|
|
186
|
+
pass
|
|
187
|
+
if args.dry_run:
|
|
188
|
+
say(body)
|
|
189
|
+
return 0
|
|
190
|
+
ci.job_summary(body, environ)
|
|
191
|
+
if ci.host_of(environ) is None:
|
|
192
|
+
say(body)
|
|
193
|
+
say(f"Not in a CI job, so Mobster posted nothing. The comment is in {ci.CI_DIR}/comment.md.", sys.stderr)
|
|
194
|
+
return 0
|
|
195
|
+
say(ci.post(body, environ), sys.stderr)
|
|
196
|
+
return 0
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""The files `mobster ci init --hosted` and `--host xcode-cloud|codemagic|bitrise|circleci` write.
|
|
2
|
+
|
|
3
|
+
Every file installs uv, prepares a simulator and runs the pinned standalone Mobster CLI.
|
|
4
|
+
None of them asks for a phone: a hosted runner has none."""
|
|
5
|
+
|
|
6
|
+
from .. import __version__
|
|
7
|
+
|
|
8
|
+
DOCS = "https://docs.mobster.dev"
|
|
9
|
+
INSTALL = "brew install uv"
|
|
10
|
+
COMMAND = f"uvx --python 3.12 --from mobster-cli=={__version__} mobster"
|
|
11
|
+
|
|
12
|
+
GITHUB_HOSTED = """# Mobster checks on every pull request, on a GitHub-hosted Mac (@DOCS@/ci/github-hosted).
|
|
13
|
+
# Add ANTHROPIC_API_KEY as a repository secret for checks with steps.
|
|
14
|
+
name: mobster
|
|
15
|
+
on: pull_request
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
pull-requests: write # the one comment Mobster keeps up to date
|
|
19
|
+
concurrency:
|
|
20
|
+
group: mobster-${{ github.event.pull_request.number }}
|
|
21
|
+
cancel-in-progress: true
|
|
22
|
+
jobs:
|
|
23
|
+
checks:
|
|
24
|
+
runs-on: macos-15
|
|
25
|
+
timeout-minutes: 60
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
with:
|
|
29
|
+
fetch-depth: 0 # the diff needs the base branch
|
|
30
|
+
- name: Install Mobster
|
|
31
|
+
id: mobster
|
|
32
|
+
run: |
|
|
33
|
+
@INSTALL@
|
|
34
|
+
echo "version=@VERSION@" >> "$GITHUB_OUTPUT"
|
|
35
|
+
echo "xcode=$(xcodebuild -version | head -1 | tr -d ' ')" >> "$GITHUB_OUTPUT"
|
|
36
|
+
- name: Cache WebDriverAgent
|
|
37
|
+
uses: actions/cache@v4
|
|
38
|
+
with:
|
|
39
|
+
path: ~/Library/Application Support/app.mobster.desktop/wda
|
|
40
|
+
key: mobster-wda-${{ runner.arch }}-${{ steps.mobster.outputs.xcode }}-${{ steps.mobster.outputs.version }}
|
|
41
|
+
restore-keys: |
|
|
42
|
+
mobster-wda-${{ runner.arch }}-${{ steps.mobster.outputs.xcode }}-
|
|
43
|
+
- name: Prepare a simulator
|
|
44
|
+
run: |
|
|
45
|
+
@COMMAND@ sim doctor --fix
|
|
46
|
+
- name: Test what this PR changed
|
|
47
|
+
run: |
|
|
48
|
+
@COMMAND@ ci run
|
|
49
|
+
env:
|
|
50
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # only checks with steps use it
|
|
51
|
+
- name: Upload the report
|
|
52
|
+
id: upload
|
|
53
|
+
if: always()
|
|
54
|
+
uses: actions/upload-artifact@v4
|
|
55
|
+
with:
|
|
56
|
+
name: mobster-report
|
|
57
|
+
path: .mobster/ci/
|
|
58
|
+
include-hidden-files: true
|
|
59
|
+
retention-days: 14
|
|
60
|
+
if-no-files-found: ignore
|
|
61
|
+
- name: Comment on the PR
|
|
62
|
+
if: always()
|
|
63
|
+
run: |
|
|
64
|
+
@COMMAND@ ci comment
|
|
65
|
+
env:
|
|
66
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
67
|
+
MOBSTER_ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
XCODE_CLOUD = """#!/bin/sh
|
|
71
|
+
# Mobster checks after Xcode Cloud builds a pull request (@DOCS@/ci/xcode-cloud).
|
|
72
|
+
# Add ANTHROPIC_API_KEY as a secret environment variable for checks with steps.
|
|
73
|
+
set -eu
|
|
74
|
+
if [ -z "${CI_PULL_REQUEST_NUMBER:-}" ]; then
|
|
75
|
+
echo "This build isn't for a pull request, so Mobster tested nothing."
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
@INSTALL@
|
|
79
|
+
@COMMAND@ sim doctor --fix
|
|
80
|
+
status=0
|
|
81
|
+
@COMMAND@ ci run || status=$?
|
|
82
|
+
@COMMAND@ ci comment || true # Xcode Cloud has no comment token; the verdict is in this log
|
|
83
|
+
exit "$status"
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
CODEMAGIC = """# Mobster checks on every pull request, on a Codemagic Mac (@DOCS@/ci/codemagic).
|
|
87
|
+
# Put ANTHROPIC_API_KEY in a variable group named mobster for checks with steps.
|
|
88
|
+
workflows:
|
|
89
|
+
mobster:
|
|
90
|
+
name: Mobster checks
|
|
91
|
+
instance_type: mac_mini_m2
|
|
92
|
+
max_build_duration: 60
|
|
93
|
+
triggering:
|
|
94
|
+
events:
|
|
95
|
+
- pull_request
|
|
96
|
+
environment:
|
|
97
|
+
groups:
|
|
98
|
+
- mobster
|
|
99
|
+
vars:
|
|
100
|
+
MOBSTER_BASE_BRANCH: main # the branch your pull requests merge into
|
|
101
|
+
scripts:
|
|
102
|
+
- name: Install Mobster
|
|
103
|
+
script: @INSTALL@
|
|
104
|
+
- name: Prepare a simulator
|
|
105
|
+
script: @COMMAND@ sim doctor --fix
|
|
106
|
+
- name: Test what this PR changed
|
|
107
|
+
script: @COMMAND@ ci run
|
|
108
|
+
artifacts:
|
|
109
|
+
- .mobster/ci/**
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
BITRISE = """# Mobster checks on every pull request, on a Bitrise Mac (@DOCS@/ci/bitrise).
|
|
113
|
+
# Add ANTHROPIC_API_KEY as a Secret for checks with steps. Trigger the workflow on pull requests.
|
|
114
|
+
format_version: "13"
|
|
115
|
+
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
|
|
116
|
+
workflows:
|
|
117
|
+
mobster:
|
|
118
|
+
steps:
|
|
119
|
+
- git-clone@8: {}
|
|
120
|
+
- script@1:
|
|
121
|
+
title: Install Mobster
|
|
122
|
+
inputs:
|
|
123
|
+
- content: |-
|
|
124
|
+
#!/usr/bin/env bash
|
|
125
|
+
set -euo pipefail
|
|
126
|
+
@INSTALL@
|
|
127
|
+
@COMMAND@ sim doctor --fix
|
|
128
|
+
- script@1:
|
|
129
|
+
title: Test what this PR changed
|
|
130
|
+
inputs:
|
|
131
|
+
- content: |-
|
|
132
|
+
#!/usr/bin/env bash
|
|
133
|
+
set -uo pipefail
|
|
134
|
+
@COMMAND@ ci run
|
|
135
|
+
- deploy-to-bitrise-io@2:
|
|
136
|
+
is_always_run: true
|
|
137
|
+
inputs:
|
|
138
|
+
- deploy_path: .mobster/ci
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
CIRCLECI = """# Mobster checks on every pull request, on a CircleCI Mac (@DOCS@/ci/circleci).
|
|
142
|
+
# Add ANTHROPIC_API_KEY for checks with steps. Pick the Xcode version from CircleCI's macOS list.
|
|
143
|
+
version: 2.1
|
|
144
|
+
jobs:
|
|
145
|
+
mobster:
|
|
146
|
+
macos:
|
|
147
|
+
xcode: "16.4.0"
|
|
148
|
+
resource_class: m4pro.medium
|
|
149
|
+
environment:
|
|
150
|
+
MOBSTER_BASE_BRANCH: main # the branch your pull requests merge into
|
|
151
|
+
steps:
|
|
152
|
+
- checkout
|
|
153
|
+
- run:
|
|
154
|
+
name: Install Mobster
|
|
155
|
+
command: |
|
|
156
|
+
@INSTALL@
|
|
157
|
+
@COMMAND@ sim doctor --fix
|
|
158
|
+
- run:
|
|
159
|
+
name: Test what this PR changed
|
|
160
|
+
command: |
|
|
161
|
+
if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then echo "This build isn't for a pull request."; exit 0; fi
|
|
162
|
+
@COMMAND@ ci run
|
|
163
|
+
- store_artifacts:
|
|
164
|
+
path: .mobster/ci
|
|
165
|
+
workflows:
|
|
166
|
+
mobster:
|
|
167
|
+
jobs:
|
|
168
|
+
- mobster
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
TEMPLATES = {"github": GITHUB_HOSTED, "xcode-cloud": XCODE_CLOUD, "codemagic": CODEMAGIC, "bitrise": BITRISE,
|
|
172
|
+
"circleci": CIRCLECI}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def render(host):
|
|
176
|
+
"""The file for ``host`` ("github" means GitHub's hosted macOS runner)."""
|
|
177
|
+
return (TEMPLATES[host].replace("@DOCS@", DOCS).replace("@COMMAND@", COMMAND)
|
|
178
|
+
.replace("@INSTALL@", INSTALL).replace("@VERSION@", __version__))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
NEXT = {
|
|
182
|
+
"github": ["Add ANTHROPIC_API_KEY as a repository secret, for checks with steps.",
|
|
183
|
+
"Commit the file and open a pull request."],
|
|
184
|
+
"xcode-cloud": ["Add ANTHROPIC_API_KEY as a secret environment variable for checks with steps.",
|
|
185
|
+
"Run the script after the build: it is `ci_scripts/ci_post_xcodebuild.sh`, and Xcode Cloud finds "
|
|
186
|
+
"it by name.",
|
|
187
|
+
"Commit the file and open a pull request."],
|
|
188
|
+
"codemagic": ["Put ANTHROPIC_API_KEY in a Codemagic variable group named mobster for checks with steps.",
|
|
189
|
+
"Commit the file and open a pull request."],
|
|
190
|
+
"bitrise": ["Add ANTHROPIC_API_KEY as a Secret for checks with steps, and trigger the mobster workflow on pull "
|
|
191
|
+
"requests.", "Commit the file and open a pull request."],
|
|
192
|
+
"circleci": ["Add ANTHROPIC_API_KEY as a project environment variable for checks with steps.",
|
|
193
|
+
"Commit the file and open a pull request."],
|
|
194
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""ES256 signing for App Store Connect's JSON Web Tokens in plain Python: the agent ships no crypto library."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import hmac
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
|
|
9
|
+
A = P - 3
|
|
10
|
+
B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
|
|
11
|
+
N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
|
|
12
|
+
G = (0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,
|
|
13
|
+
0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5)
|
|
14
|
+
EC_PUBLIC_KEY = bytes.fromhex("2a8648ce3d0201") # 1.2.840.10045.2.1
|
|
15
|
+
PRIME256V1 = bytes.fromhex("2a8648ce3d030107") # 1.2.840.10045.3.1.7
|
|
16
|
+
# The PEM lines around a .p8 key, built from the label so a secret scanner never reads the source as a key.
|
|
17
|
+
P8_LABEL = "PRIVATE KEY"
|
|
18
|
+
P8_BEGIN = f"-----BEGIN {P8_LABEL}-----"
|
|
19
|
+
P8_END = f"-----END {P8_LABEL}-----"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class P8Error(ValueError):
|
|
23
|
+
"""The text isn't a P-256 private key in the .p8 format App Store Connect gives."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# -- the curve, in Jacobian coordinates ------------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
def _double(point):
|
|
29
|
+
x, y, z = point
|
|
30
|
+
if y == 0 or z == 0:
|
|
31
|
+
return (0, 1, 0)
|
|
32
|
+
ysq = y * y % P
|
|
33
|
+
s = 4 * x * ysq % P
|
|
34
|
+
m = (3 * (x - z * z) * (x + z * z)) % P # a = -3
|
|
35
|
+
nx = (m * m - 2 * s) % P
|
|
36
|
+
ny = (m * (s - nx) - 8 * ysq * ysq) % P
|
|
37
|
+
nz = 2 * y * z % P
|
|
38
|
+
return (nx, ny, nz)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _add(p, q):
|
|
42
|
+
if p[2] == 0:
|
|
43
|
+
return q
|
|
44
|
+
if q[2] == 0:
|
|
45
|
+
return p
|
|
46
|
+
z1z1, z2z2 = p[2] * p[2] % P, q[2] * q[2] % P
|
|
47
|
+
u1, u2 = p[0] * z2z2 % P, q[0] * z1z1 % P
|
|
48
|
+
s1, s2 = p[1] * q[2] * z2z2 % P, q[1] * p[2] * z1z1 % P
|
|
49
|
+
if u1 == u2:
|
|
50
|
+
return _double(p) if s1 == s2 else (0, 1, 0)
|
|
51
|
+
h, r = (u2 - u1) % P, (s2 - s1) % P
|
|
52
|
+
hh = h * h % P
|
|
53
|
+
hhh = h * hh % P
|
|
54
|
+
v = u1 * hh % P
|
|
55
|
+
nx = (r * r - hhh - 2 * v) % P
|
|
56
|
+
ny = (r * (v - nx) - s1 * hhh) % P
|
|
57
|
+
nz = h * p[2] * q[2] % P
|
|
58
|
+
return (nx, ny, nz)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Not constant-time: the key and every signature stay in this process, and only the token goes to Apple.
|
|
62
|
+
def _multiply(k, point=G):
|
|
63
|
+
result, addend = (0, 1, 0), (point[0], point[1], 1)
|
|
64
|
+
while k:
|
|
65
|
+
if k & 1:
|
|
66
|
+
result = _add(result, addend)
|
|
67
|
+
addend = _double(addend)
|
|
68
|
+
k >>= 1
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _affine(point):
|
|
73
|
+
if point[2] == 0:
|
|
74
|
+
return None
|
|
75
|
+
inverse = pow(point[2], P - 2, P)
|
|
76
|
+
return point[0] * inverse * inverse % P, point[1] * inverse ** 3 % P
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def public_key(d):
|
|
80
|
+
return _affine(_multiply(d))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# -- RFC 6979 nonces ----------------------------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
def _bits2int(data):
|
|
86
|
+
return int.from_bytes(data, "big") # qlen = hlen = 256
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _int2octets(value):
|
|
90
|
+
return value.to_bytes(32, "big")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _nonces(d, digest):
|
|
94
|
+
h1 = _int2octets(_bits2int(digest) % N)
|
|
95
|
+
x = _int2octets(d)
|
|
96
|
+
v, k = b"\x01" * 32, b"\x00" * 32
|
|
97
|
+
k = hmac.new(k, v + b"\x00" + x + h1, hashlib.sha256).digest()
|
|
98
|
+
v = hmac.new(k, v, hashlib.sha256).digest()
|
|
99
|
+
k = hmac.new(k, v + b"\x01" + x + h1, hashlib.sha256).digest()
|
|
100
|
+
v = hmac.new(k, v, hashlib.sha256).digest()
|
|
101
|
+
while True:
|
|
102
|
+
v = hmac.new(k, v, hashlib.sha256).digest()
|
|
103
|
+
candidate = _bits2int(v)
|
|
104
|
+
if 1 <= candidate < N:
|
|
105
|
+
yield candidate
|
|
106
|
+
k = hmac.new(k, v + b"\x00", hashlib.sha256).digest()
|
|
107
|
+
v = hmac.new(k, v, hashlib.sha256).digest()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def sign(d, message):
|
|
111
|
+
"""(r, s) over SHA-256(``message``) with the private scalar ``d``."""
|
|
112
|
+
if not 1 <= d < N:
|
|
113
|
+
raise P8Error("The private key is out of range.")
|
|
114
|
+
digest = hashlib.sha256(message).digest()
|
|
115
|
+
e = _bits2int(digest) % N
|
|
116
|
+
for k in _nonces(d, digest):
|
|
117
|
+
point = _affine(_multiply(k))
|
|
118
|
+
r = point[0] % N
|
|
119
|
+
if r == 0:
|
|
120
|
+
continue
|
|
121
|
+
s = pow(k, N - 2, N) * (e + r * d) % N
|
|
122
|
+
if s:
|
|
123
|
+
return r, s
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def verify(public, message, signature):
|
|
127
|
+
"""Whether ``signature`` (r, s) is ``public``'s (x, y) over SHA-256(``message``)."""
|
|
128
|
+
r, s = signature
|
|
129
|
+
if not (1 <= r < N and 1 <= s < N):
|
|
130
|
+
return False
|
|
131
|
+
e = _bits2int(hashlib.sha256(message).digest()) % N
|
|
132
|
+
w = pow(s, N - 2, N)
|
|
133
|
+
point = _affine(_add(_multiply(e * w % N), _multiply(r * w % N, public)))
|
|
134
|
+
return point is not None and point[0] % N == r
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# -- the .p8 file (PKCS #8, RFC 5208, wrapping an RFC 5915 ECPrivateKey) ------------------------------------------
|
|
138
|
+
|
|
139
|
+
def _der(data, at=0):
|
|
140
|
+
"""(tag, value, next offset) of the DER element at ``at``."""
|
|
141
|
+
if at + 2 > len(data):
|
|
142
|
+
raise P8Error("The key is cut short.")
|
|
143
|
+
tag, length, at = data[at], data[at + 1], at + 2
|
|
144
|
+
if length & 0x80:
|
|
145
|
+
count = length & 0x7F
|
|
146
|
+
if not 1 <= count <= 3 or at + count > len(data):
|
|
147
|
+
raise P8Error("The key isn't valid DER.")
|
|
148
|
+
length, at = int.from_bytes(data[at:at + count], "big"), at + count
|
|
149
|
+
if at + length > len(data):
|
|
150
|
+
raise P8Error("The key is cut short.")
|
|
151
|
+
return tag, data[at:at + length], at + length
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _children(data):
|
|
155
|
+
out, at = [], 0
|
|
156
|
+
while at < len(data):
|
|
157
|
+
tag, value, at = _der(data, at)
|
|
158
|
+
out.append((tag, value))
|
|
159
|
+
return out
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def private_key_from_p8(text):
|
|
163
|
+
"""The private scalar of a .p8 key: a PKCS #8 PEM between ``P8_BEGIN`` and ``P8_END``."""
|
|
164
|
+
lines = [line.strip() for line in str(text or "").strip().splitlines() if line.strip()]
|
|
165
|
+
if not lines or lines[0] != P8_BEGIN or lines[-1] != P8_END:
|
|
166
|
+
raise P8Error(f"That isn't a .p8 key: it starts with {P8_BEGIN}.")
|
|
167
|
+
try:
|
|
168
|
+
data = base64.b64decode("".join(lines[1:-1]), validate=True)
|
|
169
|
+
except ValueError:
|
|
170
|
+
raise P8Error("The key's text isn't base64.") from None
|
|
171
|
+
tag, body, _ = _der(data)
|
|
172
|
+
parts = _children(body) if tag == 0x30 else []
|
|
173
|
+
if len(parts) < 3 or parts[0] != (0x02, b"\x00") or parts[1][0] != 0x30 or parts[2][0] != 0x04:
|
|
174
|
+
raise P8Error("That isn't a PKCS #8 private key.")
|
|
175
|
+
algorithm = _children(parts[1][1])
|
|
176
|
+
if len(algorithm) < 2 or algorithm[0] != (0x06, EC_PUBLIC_KEY) or algorithm[1] != (0x06, PRIME256V1):
|
|
177
|
+
raise P8Error("That key isn't on the P-256 curve App Store Connect uses.")
|
|
178
|
+
tag, inner, _ = _der(parts[2][1])
|
|
179
|
+
fields = _children(inner) if tag == 0x30 else []
|
|
180
|
+
if len(fields) < 2 or fields[0] != (0x02, b"\x01") or fields[1][0] != 0x04 or len(fields[1][1]) != 32:
|
|
181
|
+
raise P8Error("That isn't an EC private key.")
|
|
182
|
+
d = int.from_bytes(fields[1][1], "big")
|
|
183
|
+
if not 1 <= d < N:
|
|
184
|
+
raise P8Error("The private key is out of range.")
|
|
185
|
+
return d
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _b64url(data):
|
|
189
|
+
return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def jwt(header, claims, d):
|
|
193
|
+
"""A signed JWT: base64url(header).base64url(claims).base64url(r || s)."""
|
|
194
|
+
signing_input = (_b64url(json.dumps(header, separators=(",", ":")).encode()) + "." +
|
|
195
|
+
_b64url(json.dumps(claims, separators=(",", ":")).encode()))
|
|
196
|
+
r, s = sign(d, signing_input.encode())
|
|
197
|
+
return signing_input + "." + _b64url(r.to_bytes(32, "big") + s.to_bytes(32, "big"))
|