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,37 @@
|
|
|
1
|
+
"""Apple's Worldwide Developer Relations intermediate certificate (G3), which issues Apple Development certificates.
|
|
2
|
+
|
|
3
|
+
A Mac without Xcode doesn't have it, and ``codesign`` accepts a signing certificate only when it can build the chain to
|
|
4
|
+
Apple's root. Mobster puts this public certificate in its own signing keychain. It is Apple's published
|
|
5
|
+
https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer, valid to 20 February 2030, and is checked against its
|
|
6
|
+
SHA-256 before it is imported.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import hashlib
|
|
11
|
+
|
|
12
|
+
SHA256 = "dcf21878c77f4198e4b4614f03d696d89c66c66008d4244e1b99161aac91601f"
|
|
13
|
+
_DER = (
|
|
14
|
+
"MIIEUTCCAzmgAwIBAgIQfK9pCiW3Of57m0R6wXjF7jANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw"
|
|
15
|
+
"bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcN"
|
|
16
|
+
"MjAwMjE5MTgxMzQ3WhcNMzAwMjIwMDAwMDAwWjB1MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9u"
|
|
17
|
+
"cyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMIIB"
|
|
18
|
+
"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2PWJ/KhZC4fHTJEuLVaQ03gdpDDppUjvC0O/LYT7JF1FG+XrWTYSXFRknmxi"
|
|
19
|
+
"LbTGl8rMPPbWBpH85QKmHGq0edVny6zpPwcR4YS8Rx1mjjmi6LRJ7TrS4RBgeo6TjMrA2gzAg9Dj+ZHWp4zIwXPirkbRYp2SqJBg"
|
|
20
|
+
"N31ols2N4Pyb+ni743uvLRfdW/6AWSN1F7gSwe0b5TTO/iK1nkmw5VW/j4SiPKi6xYaVFuQAyZ8D0MyzOhZ71gVcnetHrg21LYwO"
|
|
21
|
+
"aU1A0EtMOwSejSGxrC5DVDDOwYqGlJhL32oNP/77HK6XF8J4CjDgXx9UO0m3JQAaN4LSVpelUkl8YDib7wIDAQABo4HvMIHsMBIG"
|
|
22
|
+
"A1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUF"
|
|
23
|
+
"BzABhihodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLWFwcGxlcm9vdGNhMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9jcmwu"
|
|
24
|
+
"YXBwbGUuY29tL3Jvb3QuY3JsMB0GA1UdDgQWBBQJ/sAVkPmvZAqSErkmKGMMl+ynsjAOBgNVHQ8BAf8EBAMCAQYwEAYKKoZIhvdj"
|
|
25
|
+
"ZAYCAQQCBQAwDQYJKoZIhvcNAQELBQADggEBAK1lE+j24IF3RAJHQr5fpTkg6mKp/cWQyXMT1Z6b0KoPjY3L7QHPbChAW8dVJEH4"
|
|
26
|
+
"/M/BtSPp3Ozxb8qAHXfCxGFJJWevD8o5Ja3T43rMMygNDi6hV0Bz+uZcrgZRKe3jhQxPYdwyFot30ETKXXIDMUacrptAGvr04NM+"
|
|
27
|
+
"+i+MZp+XxFRZ79JI9AeZSWBZGcfdlNHAwWx/eCHvDOs7bJmCS1JgOLU5gm3sUjFTvg+RTElJdI+mUcuER04ddSduvfnSXPN/wmwL"
|
|
28
|
+
"CTbiZOTCNwMUGdXqapSqqdv+9poIZ4vvK7iqF0mDr8/LvOnP6pVxsLRFoszlh6oKw0E6eVzaUDSdlTs="
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def wwdr_g3():
|
|
33
|
+
"""The certificate's DER bytes. ValueError if the bytes in this file are not Apple's."""
|
|
34
|
+
der = base64.b64decode(_DER)
|
|
35
|
+
if hashlib.sha256(der).hexdigest() != SHA256:
|
|
36
|
+
raise ValueError("The built-in Apple certificate does not match its fingerprint")
|
|
37
|
+
return der
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Path C: a signed helper and its profile, exported from one Mac and installed from another.
|
|
2
|
+
|
|
3
|
+
One Mac with a team's App Store Connect key (or with Xcode) signs the helper once; the other Macs, which need neither,
|
|
4
|
+
import it and only install and launch it. A bundle is a gzip tar of ``manifest.json`` and ``Runner.app``. The signature
|
|
5
|
+
inside is Apple's team signature, so what the bundle can do on a phone is fixed by its profile: it runs only on the
|
|
6
|
+
phones the profile lists, until the profile ends.
|
|
7
|
+
|
|
8
|
+
Importing trusts nothing in the file. It extracts into a private folder with tar's ``data`` filter (no absolute paths,
|
|
9
|
+
no links out, no devices), then refuses unless: the person named the team the bundle says it is from, the digest of
|
|
10
|
+
every file matches the manifest, the signature verifies, the profile inside says what the manifest says, it was built
|
|
11
|
+
with this Mobster's patches, and it has more than a day left. The team check is the person's: a bundle signed by
|
|
12
|
+
another team would verify too.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import datetime
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import shutil
|
|
19
|
+
import tarfile
|
|
20
|
+
import tempfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from ..devkit import DevError
|
|
24
|
+
from ..wda import patches as wda_patches
|
|
25
|
+
from . import resign
|
|
26
|
+
|
|
27
|
+
KIND = "mobster-signed-helper"
|
|
28
|
+
VERSION = 1
|
|
29
|
+
MIN_LEFT = 86400
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def export(runner, destination, *, run=None, decode=None, now=None):
|
|
33
|
+
"""Write the signed ``runner`` (…/Runner.app) as a bundle at ``destination`` (a file path). Returns the manifest.
|
|
34
|
+
DevError when the runner's signature doesn't verify, or it is a runner this Mobster didn't sign with its patches."""
|
|
35
|
+
runner, destination = Path(runner), Path(destination)
|
|
36
|
+
facts = resign.check(runner, run=run, decode=decode)
|
|
37
|
+
now = datetime.datetime.now(datetime.timezone.utc).timestamp() if now is None else now
|
|
38
|
+
manifest = {
|
|
39
|
+
"kind": KIND,
|
|
40
|
+
"version": VERSION,
|
|
41
|
+
"team": facts["team"],
|
|
42
|
+
"bundle_id": facts["bundle_id"],
|
|
43
|
+
"udids": facts["udids"],
|
|
44
|
+
"expires": facts["expires"],
|
|
45
|
+
"profile": facts["uuid"],
|
|
46
|
+
"profile_name": facts["name"],
|
|
47
|
+
"patches": wda_patches.patch_digest(),
|
|
48
|
+
"tree": resign.tree_digest(runner),
|
|
49
|
+
"created": now,
|
|
50
|
+
}
|
|
51
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
partial = destination.with_name(destination.name + ".part")
|
|
53
|
+
try:
|
|
54
|
+
with tarfile.open(partial, "w:gz") as archive:
|
|
55
|
+
data = json.dumps(manifest, indent=1, sort_keys=True).encode()
|
|
56
|
+
info = tarfile.TarInfo("manifest.json")
|
|
57
|
+
info.size, info.mtime, info.mode = len(data), now, 0o600
|
|
58
|
+
archive.addfile(info, io.BytesIO(data))
|
|
59
|
+
archive.add(runner, arcname="Runner.app")
|
|
60
|
+
partial.replace(destination)
|
|
61
|
+
destination.chmod(0o600)
|
|
62
|
+
finally:
|
|
63
|
+
partial.unlink(missing_ok=True)
|
|
64
|
+
return manifest
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def read_manifest(path):
|
|
68
|
+
"""The manifest of a bundle, without extracting the runner (so a person can be shown the team before importing)."""
|
|
69
|
+
try:
|
|
70
|
+
with tarfile.open(path, "r:gz") as archive:
|
|
71
|
+
member = archive.getmember("manifest.json")
|
|
72
|
+
if member.size > 1 << 20:
|
|
73
|
+
raise DevError("That isn't a Mobster helper bundle.")
|
|
74
|
+
manifest = json.loads(archive.extractfile(member).read())
|
|
75
|
+
except (OSError, KeyError, ValueError, tarfile.TarError):
|
|
76
|
+
raise DevError("That isn't a Mobster helper bundle.", "Export one with `mobster signing export`.") from None
|
|
77
|
+
if not isinstance(manifest, dict) or manifest.get("kind") != KIND or manifest.get("version") != VERSION:
|
|
78
|
+
raise DevError("That isn't a Mobster helper bundle this Mobster can read.")
|
|
79
|
+
return manifest
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def import_bundle(path, destination, *, team, udid=None, run=None, decode=None, now=None):
|
|
83
|
+
"""Check a bundle and put its runner at ``destination``/Runner.app. ``team`` is the team id the person says it is
|
|
84
|
+
from. Returns {path, manifest-facts}. DevError, with the fix, on any refusal; nothing is left behind then."""
|
|
85
|
+
manifest = read_manifest(path)
|
|
86
|
+
if manifest.get("team") != team:
|
|
87
|
+
raise DevError(
|
|
88
|
+
f"That bundle is from team {manifest.get('team')}, not {team}, so Mobster didn't open it.",
|
|
89
|
+
"Use the bundle from the team you mean.",
|
|
90
|
+
)
|
|
91
|
+
now = datetime.datetime.now(datetime.timezone.utc).timestamp() if now is None else now
|
|
92
|
+
if manifest.get("patches") != wda_patches.patch_digest():
|
|
93
|
+
raise DevError(
|
|
94
|
+
"That helper was built with other patches than this Mobster has.",
|
|
95
|
+
"Export a new bundle from a Mobster of the same version.",
|
|
96
|
+
)
|
|
97
|
+
if not isinstance(manifest.get("expires"), (int, float)) or manifest["expires"] < now + MIN_LEFT:
|
|
98
|
+
raise DevError("That helper's profile has ended or ends within a day.", "Export a new bundle.")
|
|
99
|
+
if udid is not None and udid not in (manifest.get("udids") or ()):
|
|
100
|
+
raise DevError(
|
|
101
|
+
"That bundle's profile doesn't list this iPhone.",
|
|
102
|
+
"Register the iPhone on the signing Mac, sign again and export a new bundle.",
|
|
103
|
+
)
|
|
104
|
+
destination = Path(destination)
|
|
105
|
+
scratch = Path(tempfile.mkdtemp(prefix="mobster-import-"))
|
|
106
|
+
try:
|
|
107
|
+
with tarfile.open(path, "r:gz") as archive:
|
|
108
|
+
members = [m for m in archive.getmembers() if m.name == "Runner.app" or m.name.startswith("Runner.app/")]
|
|
109
|
+
if not members:
|
|
110
|
+
raise DevError("That bundle has no helper in it.")
|
|
111
|
+
archive.extractall(scratch, members=members, filter="data")
|
|
112
|
+
runner = scratch / "Runner.app"
|
|
113
|
+
if resign.tree_digest(runner) != manifest.get("tree"):
|
|
114
|
+
raise DevError("That bundle changed after it was exported, so Mobster didn't use it.")
|
|
115
|
+
facts = resign.check(runner, run=run, decode=decode)
|
|
116
|
+
if (facts["team"], facts["bundle_id"], facts["udids"]) != (
|
|
117
|
+
manifest["team"],
|
|
118
|
+
manifest["bundle_id"],
|
|
119
|
+
manifest["udids"],
|
|
120
|
+
):
|
|
121
|
+
raise DevError("That bundle's profile doesn't say what its label says, so Mobster didn't use it.")
|
|
122
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
target = destination / "Runner.app"
|
|
124
|
+
shutil.rmtree(target, ignore_errors=True)
|
|
125
|
+
shutil.move(str(runner), str(target))
|
|
126
|
+
finally:
|
|
127
|
+
shutil.rmtree(scratch, ignore_errors=True)
|
|
128
|
+
return {
|
|
129
|
+
"path": str(target),
|
|
130
|
+
"team": manifest["team"],
|
|
131
|
+
"bundle_id": manifest["bundle_id"],
|
|
132
|
+
"udids": manifest["udids"],
|
|
133
|
+
"expires": manifest["expires"],
|
|
134
|
+
"profile": manifest.get("profile"),
|
|
135
|
+
"certificate": None,
|
|
136
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""`mobster signing`: put Mobster's helper on an iPhone without Xcode, for a paid Apple Developer team.
|
|
2
|
+
|
|
3
|
+
mobster signing status
|
|
4
|
+
mobster signing card # reads your team and prints exactly what Mobster would send to Apple
|
|
5
|
+
mobster signing apply --approve DIGEST # sends it, signs the helper here and installs it
|
|
6
|
+
mobster signing resolve DIGEST --made # after a change whose answer Mobster never saw
|
|
7
|
+
mobster signing export helper.tgz # the signed helper, for Macs without a key or Xcode
|
|
8
|
+
mobster signing import helper.tgz --team ABCDE12345
|
|
9
|
+
|
|
10
|
+
Off unless ``MOBSTER_EXPERIMENTAL_NO_XCODE=1``. Exit codes: 0 done, 3 couldn't (usage too).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from ..devkit import main, say, subcommands
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def add_arguments(parser, helpers):
|
|
20
|
+
add = subcommands(parser, "signing_command")
|
|
21
|
+
|
|
22
|
+
def phone_options(sub):
|
|
23
|
+
sub.add_argument(
|
|
24
|
+
"--phone-udid",
|
|
25
|
+
metavar="UDID",
|
|
26
|
+
help="the iPhone to register and sign for (default: the iPhone chosen in Setup)",
|
|
27
|
+
)
|
|
28
|
+
sub.add_argument("--phone-name", metavar="NAME", help="with --phone-udid: its name on your team")
|
|
29
|
+
sub.add_argument("--bundle-id", metavar="ID", help="sign for this bundle id instead of the helper's (a test)")
|
|
30
|
+
sub.add_argument(
|
|
31
|
+
"--team", metavar="ID", help="your team's 10-character id (default: read from App Store Connect)"
|
|
32
|
+
)
|
|
33
|
+
sub.add_argument("--json", action="store_true", help="print JSON")
|
|
34
|
+
|
|
35
|
+
status = add("status", help="say whether signing without Xcode is on, what it has and what it waits for")
|
|
36
|
+
status.add_argument("--json", action="store_true", help="print JSON")
|
|
37
|
+
|
|
38
|
+
card = add(
|
|
39
|
+
"card",
|
|
40
|
+
help="read your team and print the requests Mobster would send to Apple, with a digest",
|
|
41
|
+
description="Reads App Store Connect with your key and prints each request Mobster would send: register "
|
|
42
|
+
"your iPhone, create a development certificate from a request made on this Mac, create a "
|
|
43
|
+
"development profile. Sends nothing. Approve it with `mobster signing apply --approve DIGEST`.",
|
|
44
|
+
)
|
|
45
|
+
phone_options(card)
|
|
46
|
+
|
|
47
|
+
apply = add(
|
|
48
|
+
"apply",
|
|
49
|
+
help="send the card you approved, sign the helper on this Mac and install it",
|
|
50
|
+
description="Reads your team again and sends the card only if its digest is the one you approved. Each "
|
|
51
|
+
"request is recorded before it is sent. A request whose answer never arrives is not sent "
|
|
52
|
+
"again.",
|
|
53
|
+
)
|
|
54
|
+
apply.add_argument("--approve", required=True, metavar="DIGEST", help="the digest `mobster signing card` printed")
|
|
55
|
+
apply.add_argument(
|
|
56
|
+
"--writes-only",
|
|
57
|
+
action="store_true",
|
|
58
|
+
help="make the changes at Apple and stop: sign and install nothing (a test with no iPhone)",
|
|
59
|
+
)
|
|
60
|
+
phone_options(apply)
|
|
61
|
+
|
|
62
|
+
resolve = add("resolve", help="say whether a change whose answer Mobster never saw was made")
|
|
63
|
+
resolve.add_argument("digest", metavar="DIGEST", help="the change, from `mobster signing status`")
|
|
64
|
+
which = resolve.add_mutually_exclusive_group(required=True)
|
|
65
|
+
which.add_argument("--made", action="store_true", help="you found it at developer.apple.com")
|
|
66
|
+
which.add_argument("--not-made", action="store_true", help="it isn't there; Mobster may send it once more")
|
|
67
|
+
|
|
68
|
+
export = add("export", help="write the signed helper and its profile to a file other Macs can install")
|
|
69
|
+
export.add_argument("file", metavar="FILE", help="where to write it (a .tgz)")
|
|
70
|
+
export.add_argument("--phone-udid", metavar="UDID", help="the iPhone it was signed for (default: the one chosen)")
|
|
71
|
+
|
|
72
|
+
imp = add(
|
|
73
|
+
"import",
|
|
74
|
+
help="install a helper that another Mac signed",
|
|
75
|
+
description="Opens the file only if it is from the team you name, still has a day of profile left, lists "
|
|
76
|
+
"this iPhone and is intact. The team is your check: any team's signature verifies.",
|
|
77
|
+
)
|
|
78
|
+
imp.add_argument("file", metavar="FILE", help="the .tgz `mobster signing export` wrote")
|
|
79
|
+
imp.add_argument("--team", required=True, metavar="ID", help="the team id you expect the file to be from")
|
|
80
|
+
imp.add_argument("--phone-udid", metavar="UDID", help="the iPhone to install on (default: the one chosen)")
|
|
81
|
+
imp.add_argument("--phone-name", metavar="NAME", help="with --phone-udid: its name")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def run(args, *, service=None):
|
|
85
|
+
handlers = {
|
|
86
|
+
"status": _status,
|
|
87
|
+
"card": _card,
|
|
88
|
+
"apply": _apply,
|
|
89
|
+
"resolve": _resolve,
|
|
90
|
+
"export": _export,
|
|
91
|
+
"import": _import,
|
|
92
|
+
}
|
|
93
|
+
return main("signing", handlers, args, service=service)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _service(service):
|
|
97
|
+
if service is not None:
|
|
98
|
+
return service
|
|
99
|
+
from .. import device_manager, paths
|
|
100
|
+
from .service import TeamSigning
|
|
101
|
+
|
|
102
|
+
return TeamSigning(device_manager.DeviceManager(paths.user_data_dir()))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _phones(args):
|
|
106
|
+
udid = getattr(args, "phone_udid", None)
|
|
107
|
+
if not udid:
|
|
108
|
+
return None
|
|
109
|
+
return [{"udid": udid, "name": getattr(args, "phone_name", None) or "iPhone"}]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _status(args, *, service, **_):
|
|
113
|
+
from . import enabled
|
|
114
|
+
|
|
115
|
+
if not enabled():
|
|
116
|
+
say("Without Xcode is off. Turn it on with MOBSTER_EXPERIMENTAL_NO_XCODE=1.")
|
|
117
|
+
return 0
|
|
118
|
+
info = _service(service).status()
|
|
119
|
+
if getattr(args, "json", False):
|
|
120
|
+
say(json.dumps(info, ensure_ascii=False, default=str))
|
|
121
|
+
return 0
|
|
122
|
+
key = info["key"]
|
|
123
|
+
lines = [
|
|
124
|
+
"Key "
|
|
125
|
+
+ (
|
|
126
|
+
f"{key['keyId']} ({key['kind']})"
|
|
127
|
+
if key
|
|
128
|
+
else "none: mobster asc key add --key-id KEY_ID --issuer-id ISSUER_ID --p8 FILE"
|
|
129
|
+
),
|
|
130
|
+
f"Driver {info['driver'] or 'none: pipx install pymobiledevice3'}",
|
|
131
|
+
f"Stage {info['stage']}",
|
|
132
|
+
]
|
|
133
|
+
for open_write in info["unresolved"]:
|
|
134
|
+
lines.append(
|
|
135
|
+
f"Waiting {open_write['write'].replace('_', ' ')} was sent and never answered "
|
|
136
|
+
f"({open_write['digest'][:12]}): mobster signing resolve {open_write['digest']} --made | --not-made"
|
|
137
|
+
)
|
|
138
|
+
say("\n".join(lines))
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def render(card):
|
|
143
|
+
lines = [
|
|
144
|
+
f"App ID {card['bundle']['identifier']}"
|
|
145
|
+
+ (" (your team's wildcard)" if card["bundle"]["identifier"] == "*" else "")
|
|
146
|
+
]
|
|
147
|
+
if not card["steps"]:
|
|
148
|
+
lines.append("Mobster sends nothing to Apple: your team already has everything the helper needs.")
|
|
149
|
+
for step in card["steps"]:
|
|
150
|
+
lines += ["", f"{step['number']}. {step['title']}", f" {step['method']} {step['url']}"]
|
|
151
|
+
lines += [f" {item['label']:<10}{item['value']}" for item in step["lines"]]
|
|
152
|
+
lines += ["", f"Digest {card['digest']}"]
|
|
153
|
+
return "\n".join(lines)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _card(args, *, service, **_):
|
|
157
|
+
card = _service(service).card(_phones(args), args.bundle_id, args.team)
|
|
158
|
+
say(
|
|
159
|
+
json.dumps(card, ensure_ascii=False)
|
|
160
|
+
if args.json
|
|
161
|
+
else render(card) + f"\n\nApprove: mobster signing apply --approve {card['digest']}"
|
|
162
|
+
)
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _apply(args, *, service, **_):
|
|
167
|
+
info = _service(service).apply(args.approve, _phones(args), args.bundle_id, args.team, args.writes_only)
|
|
168
|
+
if info["stage"] == "failed":
|
|
169
|
+
say(f"mobster signing: {info['error']} {info['fix'] or ''}".rstrip(), sys.stderr)
|
|
170
|
+
return 3
|
|
171
|
+
say(
|
|
172
|
+
json.dumps(info["result"], ensure_ascii=False, default=str)
|
|
173
|
+
if args.json
|
|
174
|
+
else "Done: "
|
|
175
|
+
+ (
|
|
176
|
+
", ".join(f"{name} {ident}" for name, ident in (info["result"].get("writes") or {}).items())
|
|
177
|
+
if args.writes_only
|
|
178
|
+
else f"the helper {info['result']['runner']} is on the iPhone."
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _resolve(args, *, service, **_):
|
|
185
|
+
_service(service).resolve(args.digest, bool(args.made))
|
|
186
|
+
say("Recorded." + ("" if args.made else " Mobster may send it once more."))
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _export(args, *, service, **_):
|
|
191
|
+
manifest = _service(service).export_bundle(args.file, args.phone_udid)
|
|
192
|
+
say(
|
|
193
|
+
f"Wrote {args.file}: team {manifest['team']}, {len(manifest['udids'])} iPhone(s), profile ends "
|
|
194
|
+
f"{_date(manifest['expires'])}."
|
|
195
|
+
)
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _import(args, *, service, **_):
|
|
200
|
+
manifest = _service(service).import_bundle(args.file, args.team, _phones(args))
|
|
201
|
+
say(f"Installed {manifest['bundle_id']} (team {manifest['team']}, profile ends {_date(manifest['expires'])}).")
|
|
202
|
+
return 0
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _date(epoch):
|
|
206
|
+
import datetime
|
|
207
|
+
|
|
208
|
+
return datetime.datetime.fromtimestamp(epoch, datetime.timezone.utc).strftime("%-d %B %Y")
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""The signing key and certificate on this Mac, in a Keychain only Mobster opens.
|
|
2
|
+
|
|
3
|
+
Mobster makes the key pair here and sends Apple only a certificate signing request (CSR). The private key is made in
|
|
4
|
+
a private temporary folder, imported into a keychain file of Mobster's own (``mobster-signing.keychain-db`` in the
|
|
5
|
+
signing folder, not on the login keychain's search list), and the temporary file is deleted. That keychain's password is
|
|
6
|
+
random and kept in the login Keychain (keychain_acl, readable only by Mobster's own programs); it is never put on a
|
|
7
|
+
command line. Apple's certificate comes back as a public file, and ``security import`` pairs it with the key.
|
|
8
|
+
|
|
9
|
+
``run`` is the one place a command is executed, so a test records the commands and needs no Keychain.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import base64
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import secrets
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import tempfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from ..devkit import DevError
|
|
24
|
+
|
|
25
|
+
SECURITY = "/usr/bin/security"
|
|
26
|
+
OPENSSL = "/usr/bin/openssl"
|
|
27
|
+
KEYCHAIN_FILE = "mobster-signing.keychain-db"
|
|
28
|
+
SERVICE = "Mobster Team Signing"
|
|
29
|
+
ACCOUNT = "keychain"
|
|
30
|
+
SUBJECT = "/CN=Mobster helper signing"
|
|
31
|
+
KEY_BITS = 2048
|
|
32
|
+
HEX40 = re.compile(r"[0-9A-F]{40}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _run(argv, stdin=None, timeout=60):
|
|
36
|
+
try:
|
|
37
|
+
done = subprocess.run(argv, input=stdin, capture_output=True, text=True, timeout=timeout)
|
|
38
|
+
except (OSError, subprocess.SubprocessError) as error:
|
|
39
|
+
return 127, "", type(error).__name__
|
|
40
|
+
return done.returncode, done.stdout, done.stderr
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _store():
|
|
44
|
+
from ..keychain_acl import Store
|
|
45
|
+
|
|
46
|
+
return Store(SERVICE)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Identity:
|
|
50
|
+
"""The key pair and certificate of this Mac's signing for one folder (``team_signing.state_dir()``)."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, folder, *, store=None, run=_run):
|
|
53
|
+
self.folder, self.run = Path(folder), run
|
|
54
|
+
self._store = store
|
|
55
|
+
self.keychain = self.folder / KEYCHAIN_FILE
|
|
56
|
+
self.state_path = self.folder / "identity.json"
|
|
57
|
+
|
|
58
|
+
# -- what is on disk ------------------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def state(self):
|
|
61
|
+
try:
|
|
62
|
+
value = json.loads(self.state_path.read_text())
|
|
63
|
+
return value if isinstance(value, dict) else {}
|
|
64
|
+
except (OSError, ValueError):
|
|
65
|
+
return {}
|
|
66
|
+
|
|
67
|
+
def _save(self, **changes):
|
|
68
|
+
from .. import env_file
|
|
69
|
+
|
|
70
|
+
merged = {**self.state(), **changes} # read before the file is opened for writing, which empties it
|
|
71
|
+
env_file.private_dir(self.folder)
|
|
72
|
+
with env_file.open_private(self.state_path, "w") as stream:
|
|
73
|
+
stream.write(json.dumps(merged, indent=1))
|
|
74
|
+
|
|
75
|
+
# -- the keychain ---------------------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def _password(self, create=False):
|
|
78
|
+
store = self._store if self._store is not None else _store()
|
|
79
|
+
self._store = store
|
|
80
|
+
stored = store.get(ACCOUNT)
|
|
81
|
+
if stored is None and create:
|
|
82
|
+
stored = secrets.token_urlsafe(32)
|
|
83
|
+
store.set(ACCOUNT, stored)
|
|
84
|
+
return stored
|
|
85
|
+
|
|
86
|
+
def _security(self, *commands):
|
|
87
|
+
"""Commands for security(1)'s interactive mode, fed on stdin: a password never reaches an argument list."""
|
|
88
|
+
code, out, err = self.run([SECURITY, "-i"], "\n".join(commands) + "\n")
|
|
89
|
+
if code != 0:
|
|
90
|
+
raise DevError(
|
|
91
|
+
"macOS's Keychain tool refused Mobster's signing keychain.",
|
|
92
|
+
"Unlock your Mac's login Keychain and try again.",
|
|
93
|
+
)
|
|
94
|
+
return out
|
|
95
|
+
|
|
96
|
+
def _search_list(self):
|
|
97
|
+
code, out, _ = self.run([SECURITY, "list-keychains", "-d", "user"])
|
|
98
|
+
return [line.strip().strip('"') for line in out.splitlines() if line.strip()] if code == 0 else []
|
|
99
|
+
|
|
100
|
+
def ensure_keychain(self):
|
|
101
|
+
"""Create Mobster's signing keychain if it isn't there, and unlock it. It never stays on the search list."""
|
|
102
|
+
from .. import env_file
|
|
103
|
+
|
|
104
|
+
env_file.private_dir(self.folder)
|
|
105
|
+
password = self._password(create=not self.keychain.exists())
|
|
106
|
+
if password is None:
|
|
107
|
+
raise DevError(
|
|
108
|
+
"Mobster's signing keychain is missing its password, so it can't open.",
|
|
109
|
+
"Remove the team-signing folder in Mobster's data folder to start again.",
|
|
110
|
+
)
|
|
111
|
+
quoted = _quote(password)
|
|
112
|
+
if not self.keychain.exists():
|
|
113
|
+
before = self._search_list()
|
|
114
|
+
self._security(f"create-keychain -p {quoted} {_quote(str(self.keychain))}")
|
|
115
|
+
if before and str(self.keychain) in " ".join(self._search_list()):
|
|
116
|
+
self.run([SECURITY, "list-keychains", "-d", "user", "-s", *before])
|
|
117
|
+
self._security(f"set-keychain-settings {_quote(str(self.keychain))}") # no lock timeout
|
|
118
|
+
self._save(intermediate=None, pending_key=None, certificate_sha1=None)
|
|
119
|
+
self._security(f"unlock-keychain -p {quoted} {_quote(str(self.keychain))}")
|
|
120
|
+
if not self.state().get("intermediate"):
|
|
121
|
+
self._import_intermediate()
|
|
122
|
+
return password
|
|
123
|
+
|
|
124
|
+
def _import_intermediate(self):
|
|
125
|
+
"""Apple's WWDR G3 certificate, so codesign can build the chain on a Mac without Xcode (apple_ca.py)."""
|
|
126
|
+
from .. import env_file
|
|
127
|
+
from . import apple_ca
|
|
128
|
+
|
|
129
|
+
certificate = self.folder / "AppleWWDRCAG3.cer"
|
|
130
|
+
with env_file.open_private(certificate, "wb") as stream:
|
|
131
|
+
stream.write(apple_ca.wwdr_g3())
|
|
132
|
+
try:
|
|
133
|
+
self._security(f"import {_quote(str(certificate))} -k {_quote(str(self.keychain))}")
|
|
134
|
+
finally:
|
|
135
|
+
certificate.unlink(missing_ok=True)
|
|
136
|
+
self._save(intermediate=apple_ca.SHA256)
|
|
137
|
+
|
|
138
|
+
# -- the request ----------------------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
def request(self):
|
|
141
|
+
"""The CSR (PEM) for the key in the signing keychain; a pending one is returned again, so the same card
|
|
142
|
+
shows the same request. The private key is made here and never leaves this Mac."""
|
|
143
|
+
pending = self.folder / "pending.csr"
|
|
144
|
+
if pending.exists() and self.state().get("pending_key") == "imported":
|
|
145
|
+
return pending.read_text()
|
|
146
|
+
password = self.ensure_keychain()
|
|
147
|
+
workspace = Path(tempfile.mkdtemp(prefix="mobster-csr-"))
|
|
148
|
+
try:
|
|
149
|
+
os.chmod(workspace, 0o700)
|
|
150
|
+
key, csr = workspace / "key.pem", workspace / "request.csr"
|
|
151
|
+
key.touch(mode=0o600) # openssl keeps this mode when it writes the key into the file
|
|
152
|
+
code, _, _ = self.run(
|
|
153
|
+
[
|
|
154
|
+
OPENSSL,
|
|
155
|
+
"req",
|
|
156
|
+
"-new",
|
|
157
|
+
"-newkey",
|
|
158
|
+
f"rsa:{KEY_BITS}",
|
|
159
|
+
"-nodes",
|
|
160
|
+
"-keyout",
|
|
161
|
+
str(key),
|
|
162
|
+
"-out",
|
|
163
|
+
str(csr),
|
|
164
|
+
"-subj",
|
|
165
|
+
SUBJECT,
|
|
166
|
+
]
|
|
167
|
+
)
|
|
168
|
+
if code != 0 or not key.exists() or not csr.exists():
|
|
169
|
+
raise DevError(
|
|
170
|
+
"Mobster couldn't make a certificate signing request on this Mac.",
|
|
171
|
+
"Check that /usr/bin/openssl runs.",
|
|
172
|
+
)
|
|
173
|
+
self._security(f"import {_quote(str(key))} -k {_quote(str(self.keychain))} -x -T /usr/bin/codesign")
|
|
174
|
+
self._security(
|
|
175
|
+
f"set-key-partition-list -S apple-tool:,apple:,codesign: -s -k {_quote(password)} "
|
|
176
|
+
f"{_quote(str(self.keychain))}"
|
|
177
|
+
)
|
|
178
|
+
text = csr.read_text()
|
|
179
|
+
finally:
|
|
180
|
+
shutil.rmtree(workspace, ignore_errors=True)
|
|
181
|
+
from .. import env_file
|
|
182
|
+
|
|
183
|
+
with env_file.open_private(pending, "w") as stream:
|
|
184
|
+
stream.write(text)
|
|
185
|
+
self._save(pending_key="imported")
|
|
186
|
+
return text
|
|
187
|
+
|
|
188
|
+
# -- the certificate ------------------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def add_certificate(self, certificate_content, serial=None):
|
|
191
|
+
"""Keep the certificate Apple made (``certificateContent``, base64 DER) beside its key. Returns
|
|
192
|
+
{sha1, serial}. DevError when it isn't a certificate or the key isn't here."""
|
|
193
|
+
try:
|
|
194
|
+
der = base64.b64decode(certificate_content, validate=True)
|
|
195
|
+
except (ValueError, TypeError):
|
|
196
|
+
raise DevError("Apple's certificate wasn't readable.") from None
|
|
197
|
+
if len(der) < 64 or der[:1] != b"\x30":
|
|
198
|
+
raise DevError("Apple's certificate wasn't readable.")
|
|
199
|
+
sha1 = hashlib.sha1(der).hexdigest().upper()
|
|
200
|
+
self.ensure_keychain()
|
|
201
|
+
from .. import env_file
|
|
202
|
+
|
|
203
|
+
env_file.private_dir(self.folder)
|
|
204
|
+
certificate = self.folder / "certificate.cer"
|
|
205
|
+
with env_file.open_private(certificate, "wb") as stream:
|
|
206
|
+
stream.write(der)
|
|
207
|
+
self._security(f"import {_quote(str(certificate))} -k {_quote(str(self.keychain))} -T /usr/bin/codesign")
|
|
208
|
+
if sha1 not in self.identities():
|
|
209
|
+
raise DevError(
|
|
210
|
+
"Apple's certificate doesn't match the key on this Mac.",
|
|
211
|
+
"Remove the team-signing folder in Mobster's data folder and start again.",
|
|
212
|
+
)
|
|
213
|
+
(self.folder / "pending.csr").unlink(missing_ok=True)
|
|
214
|
+
self._save(pending_key=None, certificate_sha1=sha1, certificate_serial=serial)
|
|
215
|
+
return {"sha1": sha1, "serial": serial}
|
|
216
|
+
|
|
217
|
+
def identities(self):
|
|
218
|
+
"""SHA-1s of the code-signing identities (a certificate with its key) in the signing keychain."""
|
|
219
|
+
if not self.keychain.exists():
|
|
220
|
+
return []
|
|
221
|
+
self.ensure_keychain()
|
|
222
|
+
code, out, _ = self.run([SECURITY, "find-identity", "-p", "codesigning", str(self.keychain)])
|
|
223
|
+
return [match for match in re.findall(r"\b([0-9A-F]{40})\b", out)] if code == 0 else []
|
|
224
|
+
|
|
225
|
+
def signing_identity(self):
|
|
226
|
+
"""{sha1, serial} of the certificate Mobster made here, while its key is still in the keychain; else None."""
|
|
227
|
+
state = self.state()
|
|
228
|
+
sha1 = state.get("certificate_sha1")
|
|
229
|
+
if sha1 and HEX40.fullmatch(sha1) and sha1 in self.identities():
|
|
230
|
+
return {"sha1": sha1, "serial": state.get("certificate_serial")}
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
def forget(self):
|
|
234
|
+
"""Delete the signing keychain and its password, e.g. after the test certificate is revoked."""
|
|
235
|
+
password_store = self._store if self._store is not None else _store()
|
|
236
|
+
if self.keychain.exists():
|
|
237
|
+
self.run([SECURITY, "delete-keychain", str(self.keychain)])
|
|
238
|
+
for name in ("pending.csr", "certificate.cer", "identity.json"):
|
|
239
|
+
(self.folder / name).unlink(missing_ok=True)
|
|
240
|
+
try:
|
|
241
|
+
password_store.delete(ACCOUNT)
|
|
242
|
+
except Exception: # noqa: BLE001 -- nothing stored is fine
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _quote(value):
|
|
247
|
+
"""One word for security(1)'s interactive parser: double-quoted, with \\ and " escaped."""
|
|
248
|
+
return '"' + str(value).replace("\\", "\\\\").replace('"', '\\"') + '"'
|