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.
Files changed (1009) hide show
  1. mobile_agent/__init__.py +4 -0
  2. mobile_agent/__main__.py +598 -0
  3. mobile_agent/accounts/__init__.py +1 -0
  4. mobile_agent/accounts/context.py +80 -0
  5. mobile_agent/accounts/mcp.py +55 -0
  6. mobile_agent/accounts/pacing.py +318 -0
  7. mobile_agent/accounts/register.py +78 -0
  8. mobile_agent/accounts/routes.py +212 -0
  9. mobile_agent/accounts/routing.py +69 -0
  10. mobile_agent/accounts/screen.py +273 -0
  11. mobile_agent/accounts/stats.py +54 -0
  12. mobile_agent/accounts/store.py +394 -0
  13. mobile_agent/agent_hooks.py +123 -0
  14. mobile_agent/ai_plans/__init__.py +7 -0
  15. mobile_agent/ai_plans/agents.py +96 -0
  16. mobile_agent/ai_plans/claude_code.py +208 -0
  17. mobile_agent/ai_plans/cli.py +400 -0
  18. mobile_agent/ai_plans/codex.py +258 -0
  19. mobile_agent/ai_plans/compat_chat.py +307 -0
  20. mobile_agent/ai_plans/device_login.py +164 -0
  21. mobile_agent/ai_plans/draft_client.py +68 -0
  22. mobile_agent/ai_plans/gemini.py +137 -0
  23. mobile_agent/ai_plans/instructions.py +65 -0
  24. mobile_agent/ai_plans/mcp_http.py +160 -0
  25. mobile_agent/ai_plans/phone_tools.py +892 -0
  26. mobile_agent/ai_plans/providers.py +235 -0
  27. mobile_agent/ai_plans/routes.py +160 -0
  28. mobile_agent/ai_plans/runner.py +400 -0
  29. mobile_agent/ai_plans/vault.py +114 -0
  30. mobile_agent/alerts.py +337 -0
  31. mobile_agent/android/__init__.py +18 -0
  32. mobile_agent/android/adb.py +268 -0
  33. mobile_agent/android/conditions.py +128 -0
  34. mobile_agent/android/doctor.py +54 -0
  35. mobile_agent/android/driver.py +137 -0
  36. mobile_agent/android/emulator.py +216 -0
  37. mobile_agent/android/logs.py +85 -0
  38. mobile_agent/android/manager.py +225 -0
  39. mobile_agent/android/records.py +31 -0
  40. mobile_agent/android/tree.py +96 -0
  41. mobile_agent/answer_shapes.py +230 -0
  42. mobile_agent/api_devices.py +333 -0
  43. mobile_agent/api_errors.py +17 -0
  44. mobile_agent/api_get.py +249 -0
  45. mobile_agent/api_http.py +467 -0
  46. mobile_agent/api_post.py +453 -0
  47. mobile_agent/api_routes.py +173 -0
  48. mobile_agent/app_api/__init__.py +4 -0
  49. mobile_agent/app_api/cli.py +151 -0
  50. mobile_agent/app_api/endpoints.py +193 -0
  51. mobile_agent/app_api/mcp.py +54 -0
  52. mobile_agent/app_api/openapi.py +146 -0
  53. mobile_agent/app_api/reading.py +172 -0
  54. mobile_agent/app_api/register.py +10 -0
  55. mobile_agent/app_api/screen.py +216 -0
  56. mobile_agent/app_api/service.py +382 -0
  57. mobile_agent/app_api/sessions.py +404 -0
  58. mobile_agent/app_api/tokens.py +167 -0
  59. mobile_agent/app_choice.py +131 -0
  60. mobile_agent/app_icons.py +183 -0
  61. mobile_agent/app_killer.py +69 -0
  62. mobile_agent/artifacts.py +606 -0
  63. mobile_agent/attach/__init__.py +13 -0
  64. mobile_agent/attach/cli.py +46 -0
  65. mobile_agent/attach/flutter.py +28 -0
  66. mobile_agent/attach/score.py +119 -0
  67. mobile_agent/attach/web.py +13 -0
  68. mobile_agent/attachments/__init__.py +1 -0
  69. mobile_agent/attachments/agent.py +418 -0
  70. mobile_agent/attachments/extract.py +217 -0
  71. mobile_agent/attachments/mcp.py +104 -0
  72. mobile_agent/attachments/register.py +73 -0
  73. mobile_agent/attachments/routes.py +326 -0
  74. mobile_agent/attachments/service.py +170 -0
  75. mobile_agent/attachments/sniff.py +152 -0
  76. mobile_agent/attachments/store.py +441 -0
  77. mobile_agent/audit_verify.py +436 -0
  78. mobile_agent/bench/README.md +410 -0
  79. mobile_agent/bench/__init__.py +0 -0
  80. mobile_agent/bench/__main__.py +394 -0
  81. mobile_agent/bench/ab.py +168 -0
  82. mobile_agent/bench/actuation.py +368 -0
  83. mobile_agent/bench/agents/__init__.py +9 -0
  84. mobile_agent/bench/agents/base.py +105 -0
  85. mobile_agent/bench/agents/plan_clients.py +245 -0
  86. mobile_agent/bench/agents/smart.py +533 -0
  87. mobile_agent/bench/checks.py +358 -0
  88. mobile_agent/bench/decide_gate.py +214 -0
  89. mobile_agent/bench/diagnose.py +181 -0
  90. mobile_agent/bench/evals/__init__.py +14 -0
  91. mobile_agent/bench/evals/build.py +565 -0
  92. mobile_agent/bench/evals/checkpoint.py +214 -0
  93. mobile_agent/bench/evals/irt.py +219 -0
  94. mobile_agent/bench/evals/l0.py +647 -0
  95. mobile_agent/bench/evals/l1.py +866 -0
  96. mobile_agent/bench/evals/models.py +124 -0
  97. mobile_agent/bench/evals/pairwise.py +189 -0
  98. mobile_agent/bench/evals/proxy.py +90 -0
  99. mobile_agent/bench/evals/sets.py +149 -0
  100. mobile_agent/bench/evals/traces.py +446 -0
  101. mobile_agent/bench/fixtures.py +176 -0
  102. mobile_agent/bench/guard_replay.py +129 -0
  103. mobile_agent/bench/h2-manifest.json +20 -0
  104. mobile_agent/bench/h3-manifest.json +20 -0
  105. mobile_agent/bench/heldout-manifest.json +32 -0
  106. mobile_agent/bench/heldout.py +375 -0
  107. mobile_agent/bench/iosworld.py +901 -0
  108. mobile_agent/bench/iosworld_stats.py +510 -0
  109. mobile_agent/bench/llm_units.py +116 -0
  110. mobile_agent/bench/manifest.json +93 -0
  111. mobile_agent/bench/metrics.py +66 -0
  112. mobile_agent/bench/phone.py +44 -0
  113. mobile_agent/bench/replay.py +172 -0
  114. mobile_agent/bench/report.py +207 -0
  115. mobile_agent/bench/runner.py +456 -0
  116. mobile_agent/bench/safety.py +310 -0
  117. mobile_agent/bench/stream_timing.py +213 -0
  118. mobile_agent/bench/suite.py +444 -0
  119. mobile_agent/bench/sweep.py +176 -0
  120. mobile_agent/bench/tape.py +798 -0
  121. mobile_agent/bench/tape_replay.py +88 -0
  122. mobile_agent/bench/trace_evals.py +128 -0
  123. mobile_agent/bench/trace_view.py +81 -0
  124. mobile_agent/bench/truth/iphone15pro.json +138 -0
  125. mobile_agent/bench/truth.py +340 -0
  126. mobile_agent/bench/tuning.py +543 -0
  127. mobile_agent/bench/tuning_analysis.py +656 -0
  128. mobile_agent/bench/vague-heldout-manifest.json +12 -0
  129. mobile_agent/bench/vague_rubric.py +135 -0
  130. mobile_agent/bench/vague_run.py +243 -0
  131. mobile_agent/brand.py +454 -0
  132. mobile_agent/brand_fonts.py +756 -0
  133. mobile_agent/build_identity.py +21 -0
  134. mobile_agent/cable_bt.py +623 -0
  135. mobile_agent/cable_bt_runtime.py +128 -0
  136. mobile_agent/catalog.py +69 -0
  137. mobile_agent/chart_svg.py +415 -0
  138. mobile_agent/chat_here.py +189 -0
  139. mobile_agent/cli_constants.py +5 -0
  140. mobile_agent/cli_parser.py +611 -0
  141. mobile_agent/cli_run.py +115 -0
  142. mobile_agent/compiled_read/__init__.py +21 -0
  143. mobile_agent/compiled_read/delta.py +89 -0
  144. mobile_agent/compiled_read/extractors.py +202 -0
  145. mobile_agent/compiled_read/routes.py +56 -0
  146. mobile_agent/completion.py +267 -0
  147. mobile_agent/compose.py +48 -0
  148. mobile_agent/config.py +114 -0
  149. mobile_agent/connectors/__init__.py +1 -0
  150. mobile_agent/connectors/api_session.py +76 -0
  151. mobile_agent/connectors/callback_page.py +146 -0
  152. mobile_agent/connectors/client.py +582 -0
  153. mobile_agent/connectors/github_app.py +55 -0
  154. mobile_agent/connectors/graph_api.py +396 -0
  155. mobile_agent/connectors/imports.py +170 -0
  156. mobile_agent/connectors/issue.py +66 -0
  157. mobile_agent/connectors/keystore.py +85 -0
  158. mobile_agent/connectors/marks.py +222 -0
  159. mobile_agent/connectors/oauth.py +476 -0
  160. mobile_agent/connectors/picker.py +228 -0
  161. mobile_agent/connectors/presets.py +591 -0
  162. mobile_agent/connectors/register.py +48 -0
  163. mobile_agent/connectors/routes.py +153 -0
  164. mobile_agent/connectors/service.py +966 -0
  165. mobile_agent/connectors/shared.py +136 -0
  166. mobile_agent/connectors/skill.py +446 -0
  167. mobile_agent/connectors/slack_api.py +269 -0
  168. mobile_agent/connectors/steps.py +92 -0
  169. mobile_agent/connectors/store.py +144 -0
  170. mobile_agent/connectors/vault.py +72 -0
  171. mobile_agent/console.py +178 -0
  172. mobile_agent/content_queue/__init__.py +1 -0
  173. mobile_agent/content_queue/client.py +263 -0
  174. mobile_agent/content_queue/importing.py +153 -0
  175. mobile_agent/content_queue/media_check.py +224 -0
  176. mobile_agent/content_queue/prompt.py +64 -0
  177. mobile_agent/content_queue/register.py +91 -0
  178. mobile_agent/content_queue/routes.py +370 -0
  179. mobile_agent/content_queue/runner.py +475 -0
  180. mobile_agent/content_queue/store.py +521 -0
  181. mobile_agent/contract.py +996 -0
  182. mobile_agent/contract_asks.py +248 -0
  183. mobile_agent/contract_prompt.py +45 -0
  184. mobile_agent/contract_screen.py +981 -0
  185. mobile_agent/contract_view.py +84 -0
  186. mobile_agent/corpus/__init__.py +26 -0
  187. mobile_agent/corpus/cli.py +63 -0
  188. mobile_agent/corpus/consent.py +55 -0
  189. mobile_agent/corpus/listener.py +76 -0
  190. mobile_agent/corpus/register.py +9 -0
  191. mobile_agent/corpus/skeleton.py +72 -0
  192. mobile_agent/corpus/store.py +143 -0
  193. mobile_agent/costs.py +161 -0
  194. mobile_agent/decider_scope.py +40 -0
  195. mobile_agent/decision_provider.py +189 -0
  196. mobile_agent/deeplinks.py +352 -0
  197. mobile_agent/device_info.py +116 -0
  198. mobile_agent/device_manager.py +978 -0
  199. mobile_agent/device_status.py +195 -0
  200. mobile_agent/device_targets.py +217 -0
  201. mobile_agent/devices.py +676 -0
  202. mobile_agent/devkit/__init__.py +64 -0
  203. mobile_agent/devkit/account_cli.py +90 -0
  204. mobile_agent/devkit/asc.py +562 -0
  205. mobile_agent/devkit/asc_cli.py +214 -0
  206. mobile_agent/devkit/ci.py +737 -0
  207. mobile_agent/devkit/ci_cli.py +196 -0
  208. mobile_agent/devkit/ci_hosts.py +194 -0
  209. mobile_agent/devkit/es256.py +197 -0
  210. mobile_agent/devkit/expo.py +440 -0
  211. mobile_agent/devkit/expo_cli.py +405 -0
  212. mobile_agent/devkit/expo_dev.py +203 -0
  213. mobile_agent/devkit/expo_routes.py +90 -0
  214. mobile_agent/devkit/fixtures_cli.py +133 -0
  215. mobile_agent/devkit/fixtures_routes.py +82 -0
  216. mobile_agent/devkit/links.py +375 -0
  217. mobile_agent/devkit/links_cli.py +123 -0
  218. mobile_agent/devkit/links_routes.py +83 -0
  219. mobile_agent/devkit/nightly.py +152 -0
  220. mobile_agent/devkit/shots.py +203 -0
  221. mobile_agent/devkit/testflight.py +155 -0
  222. mobile_agent/devkit/testflight_cli.py +127 -0
  223. mobile_agent/devkit/testflight_routes.py +111 -0
  224. mobile_agent/devkit/xcode.py +236 -0
  225. mobile_agent/devkit/xcode_cli.py +101 -0
  226. mobile_agent/devtools.py +419 -0
  227. mobile_agent/doctor.py +509 -0
  228. mobile_agent/doctor_fix.py +467 -0
  229. mobile_agent/downloads.py +55 -0
  230. mobile_agent/drivers.py +793 -0
  231. mobile_agent/dry_run.py +102 -0
  232. mobile_agent/effect_ledger.py +83 -0
  233. mobile_agent/engine_health.py +378 -0
  234. mobile_agent/engine_hooks.py +96 -0
  235. mobile_agent/engines.py +408 -0
  236. mobile_agent/env_file.py +63 -0
  237. mobile_agent/env_secrets.py +149 -0
  238. mobile_agent/errors.py +15 -0
  239. mobile_agent/evals/__init__.py +3 -0
  240. mobile_agent/evals/app_survey.py +149 -0
  241. mobile_agent/evals/development/__init__.py +1 -0
  242. mobile_agent/evals/development/score.py +276 -0
  243. mobile_agent/evals/latency_report.py +312 -0
  244. mobile_agent/evals/offline_report.py +374 -0
  245. mobile_agent/evals/oracles.py +298 -0
  246. mobile_agent/evals/redteam/__init__.py +15 -0
  247. mobile_agent/evals/redteam/__main__.py +72 -0
  248. mobile_agent/evals/redteam/cases.py +730 -0
  249. mobile_agent/evals/redteam/harness.py +604 -0
  250. mobile_agent/evals/redteam/live.py +139 -0
  251. mobile_agent/evals/redteam/plain.py +85 -0
  252. mobile_agent/evals/vision/__init__.py +0 -0
  253. mobile_agent/evals/vision/images/dog_001.jpg +0 -0
  254. mobile_agent/evals/vision/images/dog_002.jpg +0 -0
  255. mobile_agent/evals/vision/images/dog_003.jpg +0 -0
  256. mobile_agent/evals/vision/images/dog_004.jpg +0 -0
  257. mobile_agent/evals/vision/images/dog_005.jpg +0 -0
  258. mobile_agent/evals/vision/images/dog_006.jpg +0 -0
  259. mobile_agent/evals/vision/images/dog_007.jpg +0 -0
  260. mobile_agent/evals/vision/images/dog_010.jpg +0 -0
  261. mobile_agent/evals/vision/images/dog_011.jpg +0 -0
  262. mobile_agent/evals/vision/images/dog_013.jpg +0 -0
  263. mobile_agent/evals/vision/images/dog_014.jpg +0 -0
  264. mobile_agent/evals/vision/images/dog_015.jpg +0 -0
  265. mobile_agent/evals/vision/images/dog_016.jpg +0 -0
  266. mobile_agent/evals/vision/images/dog_017.jpg +0 -0
  267. mobile_agent/evals/vision/images/dog_019.jpg +0 -0
  268. mobile_agent/evals/vision/images/dog_020.jpg +0 -0
  269. mobile_agent/evals/vision/images/dog_021.jpg +0 -0
  270. mobile_agent/evals/vision/images/dog_022.jpg +0 -0
  271. mobile_agent/evals/vision/images/dog_023.jpg +0 -0
  272. mobile_agent/evals/vision/images/dog_024.jpg +0 -0
  273. mobile_agent/evals/vision/images/dog_026.jpg +0 -0
  274. mobile_agent/evals/vision/images/dog_027.jpg +0 -0
  275. mobile_agent/evals/vision/images/dog_034.jpg +0 -0
  276. mobile_agent/evals/vision/images/dog_035.jpg +0 -0
  277. mobile_agent/evals/vision/images/dog_038.jpg +0 -0
  278. mobile_agent/evals/vision/images/dog_042.jpg +0 -0
  279. mobile_agent/evals/vision/images/dog_047.jpg +0 -0
  280. mobile_agent/evals/vision/images/dog_052.jpg +0 -0
  281. mobile_agent/evals/vision/images/dog_058.jpg +0 -0
  282. mobile_agent/evals/vision/images/dog_062.jpg +0 -0
  283. mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
  284. mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
  285. mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
  286. mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
  287. mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
  288. mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
  289. mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
  290. mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
  291. mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
  292. mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
  293. mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
  294. mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
  295. mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
  296. mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
  297. mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
  298. mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
  299. mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
  300. mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
  301. mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
  302. mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
  303. mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
  304. mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
  305. mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
  306. mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
  307. mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
  308. mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
  309. mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
  310. mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
  311. mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
  312. mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
  313. mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
  314. mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
  315. mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
  316. mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
  317. mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
  318. mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
  319. mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
  320. mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
  321. mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
  322. mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
  323. mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
  324. mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
  325. mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
  326. mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
  327. mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
  328. mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
  329. mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
  330. mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
  331. mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
  332. mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
  333. mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
  334. mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
  335. mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
  336. mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
  337. mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
  338. mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
  339. mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
  340. mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
  341. mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
  342. mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
  343. mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
  344. mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
  345. mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
  346. mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
  347. mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
  348. mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
  349. mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
  350. mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
  351. mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
  352. mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
  353. mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
  354. mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
  355. mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
  356. mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
  357. mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
  358. mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
  359. mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
  360. mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
  361. mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
  362. mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
  363. mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
  364. mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
  365. mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
  366. mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
  367. mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
  368. mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
  369. mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
  370. mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
  371. mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
  372. mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
  373. mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
  374. mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
  375. mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
  376. mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
  377. mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
  378. mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
  379. mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
  380. mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
  381. mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
  382. mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
  383. mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
  384. mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
  385. mobile_agent/evals/vision/manifest.json +2355 -0
  386. mobile_agent/eventbus.py +110 -0
  387. mobile_agent/explain.py +34 -0
  388. mobile_agent/failure_line.py +197 -0
  389. mobile_agent/fanout.py +663 -0
  390. mobile_agent/feedback/__init__.py +2 -0
  391. mobile_agent/feedback/bundle.py +204 -0
  392. mobile_agent/feedback/outbox.py +196 -0
  393. mobile_agent/feedback/register.py +9 -0
  394. mobile_agent/feedback/routes.py +177 -0
  395. mobile_agent/fleet.py +841 -0
  396. mobile_agent/fleet_health.py +729 -0
  397. mobile_agent/fleet_phone.py +186 -0
  398. mobile_agent/frontier/__init__.py +79 -0
  399. mobile_agent/frontier/act.py +380 -0
  400. mobile_agent/frontier/actions.py +70 -0
  401. mobile_agent/frontier/agent.py +256 -0
  402. mobile_agent/frontier/approvals.py +366 -0
  403. mobile_agent/frontier/hooks.py +199 -0
  404. mobile_agent/frontier/interrupts.py +125 -0
  405. mobile_agent/frontier/observe.py +331 -0
  406. mobile_agent/frontier/phases.py +381 -0
  407. mobile_agent/frontier/pipeline.py +61 -0
  408. mobile_agent/frontier/prompt.py +384 -0
  409. mobile_agent/frontier/protocol.py +91 -0
  410. mobile_agent/frontier/safety.py +467 -0
  411. mobile_agent/frontier/saved.py +375 -0
  412. mobile_agent/frontier/screen.py +419 -0
  413. mobile_agent/frontier/skills.py +147 -0
  414. mobile_agent/frontier/stages/__init__.py +1 -0
  415. mobile_agent/frontier/stages/after.py +192 -0
  416. mobile_agent/frontier/stages/approve.py +138 -0
  417. mobile_agent/frontier/stages/controls.py +115 -0
  418. mobile_agent/frontier/stages/ends.py +102 -0
  419. mobile_agent/frontier/stages/gate.py +92 -0
  420. mobile_agent/frontier/stages/guards.py +132 -0
  421. mobile_agent/frontier/stages/points.py +155 -0
  422. mobile_agent/frontier/stages/send.py +177 -0
  423. mobile_agent/frontier/turn.py +151 -0
  424. mobile_agent/full_disk_access.py +83 -0
  425. mobile_agent/gemini.py +69 -0
  426. mobile_agent/genuine.py +120 -0
  427. mobile_agent/graph/__init__.py +4 -0
  428. mobile_agent/graph/catalog.py +789 -0
  429. mobile_agent/graph/chain.py +306 -0
  430. mobile_agent/graph/dispatch.py +98 -0
  431. mobile_agent/graph/draft.py +373 -0
  432. mobile_agent/graph/edit.py +76 -0
  433. mobile_agent/graph/estimate.py +189 -0
  434. mobile_agent/graph/expr.py +562 -0
  435. mobile_agent/graph/functions.py +271 -0
  436. mobile_agent/graph/infer.py +144 -0
  437. mobile_agent/graph/nodes.py +936 -0
  438. mobile_agent/graph/phone_steps.py +178 -0
  439. mobile_agent/graph/rerun.py +327 -0
  440. mobile_agent/graph/run.py +899 -0
  441. mobile_agent/graph/schema.py +842 -0
  442. mobile_agent/graph/state.py +161 -0
  443. mobile_agent/graph/values.py +198 -0
  444. mobile_agent/harness/__init__.py +2 -0
  445. mobile_agent/harness/checkpoints.py +468 -0
  446. mobile_agent/harness/control.py +101 -0
  447. mobile_agent/harness/prewarm.py +213 -0
  448. mobile_agent/harness/register.py +43 -0
  449. mobile_agent/harness/routes.py +114 -0
  450. mobile_agent/harness/settings.py +66 -0
  451. mobile_agent/harness/tools/__init__.py +67 -0
  452. mobile_agent/harness/tools/ask_user.py +102 -0
  453. mobile_agent/harness/tools/watch.py +120 -0
  454. mobile_agent/harness_api.py +815 -0
  455. mobile_agent/hedge.py +203 -0
  456. mobile_agent/help_topics.py +305 -0
  457. mobile_agent/hints.py +248 -0
  458. mobile_agent/history.py +69 -0
  459. mobile_agent/home.py +214 -0
  460. mobile_agent/http_pool.py +175 -0
  461. mobile_agent/image_policy.py +139 -0
  462. mobile_agent/inbox.py +148 -0
  463. mobile_agent/installed_apps.py +344 -0
  464. mobile_agent/integrations/__init__.py +1 -0
  465. mobile_agent/integrations/cli.py +472 -0
  466. mobile_agent/integrations/client_files.py +65 -0
  467. mobile_agent/integrations/clients.py +998 -0
  468. mobile_agent/integrations/doctor.py +185 -0
  469. mobile_agent/integrations/jsonc.py +383 -0
  470. mobile_agent/integrations/routes.py +135 -0
  471. mobile_agent/integrations/rules.py +112 -0
  472. mobile_agent/integrations/server_options.py +46 -0
  473. mobile_agent/integrations/skill.py +152 -0
  474. mobile_agent/integrations/skill_files.py +10 -0
  475. mobile_agent/integrations/textedit.py +309 -0
  476. mobile_agent/intents/__init__.py +17 -0
  477. mobile_agent/intents/atlas.py +216 -0
  478. mobile_agent/intents/callback.py +44 -0
  479. mobile_agent/intents/cli.py +55 -0
  480. mobile_agent/intents/harvest.py +91 -0
  481. mobile_agent/intents/plan.py +93 -0
  482. mobile_agent/ios/__init__.py +1 -0
  483. mobile_agent/ios/guide.py +16 -0
  484. mobile_agent/ios/kb.py +212 -0
  485. mobile_agent/ios/kb_data.py +1588 -0
  486. mobile_agent/ios/mcp.py +44 -0
  487. mobile_agent/ios/register.py +8 -0
  488. mobile_agent/ios/system.py +369 -0
  489. mobile_agent/journal.py +531 -0
  490. mobile_agent/keychain_acl.py +374 -0
  491. mobile_agent/keys.py +395 -0
  492. mobile_agent/latency_overlap.py +36 -0
  493. mobile_agent/latency_trace.py +170 -0
  494. mobile_agent/learned/__init__.py +15 -0
  495. mobile_agent/learned/app_path.py +160 -0
  496. mobile_agent/learned/code_read.py +38 -0
  497. mobile_agent/learned/parts.py +546 -0
  498. mobile_agent/learned/programs.py +691 -0
  499. mobile_agent/learned/register.py +171 -0
  500. mobile_agent/learned/routes.py +61 -0
  501. mobile_agent/learned/taught.py +329 -0
  502. mobile_agent/learned/teach.py +538 -0
  503. mobile_agent/learned/workflows.py +647 -0
  504. mobile_agent/learned/zero.py +41 -0
  505. mobile_agent/links.py +37 -0
  506. mobile_agent/listen.py +607 -0
  507. mobile_agent/lock_spike.py +278 -0
  508. mobile_agent/lockscreen.py +526 -0
  509. mobile_agent/login.py +354 -0
  510. mobile_agent/longform/__init__.py +15 -0
  511. mobile_agent/longform/ads.py +191 -0
  512. mobile_agent/longform/blocks.py +117 -0
  513. mobile_agent/longform/budget.py +265 -0
  514. mobile_agent/longform/context.py +285 -0
  515. mobile_agent/longform/costs.py +93 -0
  516. mobile_agent/longform/digest.py +145 -0
  517. mobile_agent/longform/extract.py +508 -0
  518. mobile_agent/longform/feed.py +505 -0
  519. mobile_agent/longform/followup.py +168 -0
  520. mobile_agent/longform/frames.py +82 -0
  521. mobile_agent/longform/identity.py +87 -0
  522. mobile_agent/longform/inbox.py +221 -0
  523. mobile_agent/longform/items.py +425 -0
  524. mobile_agent/longform/models.py +115 -0
  525. mobile_agent/longform/notebook.py +329 -0
  526. mobile_agent/longform/plan.py +614 -0
  527. mobile_agent/longform/recall.py +195 -0
  528. mobile_agent/longform/receipts.py +81 -0
  529. mobile_agent/longform/register.py +124 -0
  530. mobile_agent/longform/report.py +343 -0
  531. mobile_agent/longform/routes.py +102 -0
  532. mobile_agent/longform/session.py +644 -0
  533. mobile_agent/longform/store.py +146 -0
  534. mobile_agent/longform/synthesis.py +347 -0
  535. mobile_agent/mac_tools.py +223 -0
  536. mobile_agent/manual_control.py +143 -0
  537. mobile_agent/maps.py +149 -0
  538. mobile_agent/marks.py +92 -0
  539. mobile_agent/mcp_server/__init__.py +3 -0
  540. mobile_agent/mcp_server/__main__.py +29 -0
  541. mobile_agent/mcp_server/action_journal.py +24 -0
  542. mobile_agent/mcp_server/action_tools.py +638 -0
  543. mobile_agent/mcp_server/approvals.py +219 -0
  544. mobile_agent/mcp_server/catalog.py +92 -0
  545. mobile_agent/mcp_server/cli.py +194 -0
  546. mobile_agent/mcp_server/codes.py +147 -0
  547. mobile_agent/mcp_server/device_tools.py +306 -0
  548. mobile_agent/mcp_server/direct.py +272 -0
  549. mobile_agent/mcp_server/expo_tools.py +105 -0
  550. mobile_agent/mcp_server/phone_tools.py +510 -0
  551. mobile_agent/mcp_server/protocol.py +364 -0
  552. mobile_agent/mcp_server/registry.py +55 -0
  553. mobile_agent/mcp_server/run_tools.py +531 -0
  554. mobile_agent/mcp_server/schemas.py +425 -0
  555. mobile_agent/mcp_server/screen_tools.py +168 -0
  556. mobile_agent/mcp_server/session.py +469 -0
  557. mobile_agent/mcp_server/support.py +145 -0
  558. mobile_agent/mcp_server/tools.py +493 -0
  559. mobile_agent/memory/__init__.py +9 -0
  560. mobile_agent/memory/cli.py +207 -0
  561. mobile_agent/memory/extract.py +191 -0
  562. mobile_agent/memory/listener.py +100 -0
  563. mobile_agent/memory/model.py +144 -0
  564. mobile_agent/memory/project.py +96 -0
  565. mobile_agent/memory/provider.py +127 -0
  566. mobile_agent/memory/register.py +21 -0
  567. mobile_agent/memory/retrieval.py +172 -0
  568. mobile_agent/memory/routes.py +216 -0
  569. mobile_agent/memory/runs.py +124 -0
  570. mobile_agent/memory/store.py +806 -0
  571. mobile_agent/memory/writer.py +278 -0
  572. mobile_agent/mirroring.py +585 -0
  573. mobile_agent/mirroring_runtime.py +221 -0
  574. mobile_agent/model_clients.py +604 -0
  575. mobile_agent/narrate.py +813 -0
  576. mobile_agent/native_helpers.py +84 -0
  577. mobile_agent/netxray/__init__.py +1 -0
  578. mobile_agent/netxray/analyze.py +322 -0
  579. mobile_agent/netxray/attribution.py +62 -0
  580. mobile_agent/netxray/capture.py +88 -0
  581. mobile_agent/netxray/cli.py +142 -0
  582. mobile_agent/netxray/job.py +219 -0
  583. mobile_agent/netxray/mobster-pcap.c +560 -0
  584. mobile_agent/netxray/packets.py +410 -0
  585. mobile_agent/netxray/pcapng.py +223 -0
  586. mobile_agent/netxray/register.py +93 -0
  587. mobile_agent/netxray/report.py +214 -0
  588. mobile_agent/netxray/store.py +48 -0
  589. mobile_agent/netxray/summary.py +157 -0
  590. mobile_agent/netxray/target.py +67 -0
  591. mobile_agent/netxray/tool.py +66 -0
  592. mobile_agent/netxray/trackers.py +194 -0
  593. mobile_agent/notify_triggers/__init__.py +1 -0
  594. mobile_agent/notify_triggers/rules.py +39 -0
  595. mobile_agent/notify_triggers/sources.py +315 -0
  596. mobile_agent/notify_triggers/watch.py +135 -0
  597. mobile_agent/offers.py +86 -0
  598. mobile_agent/orchestrate.py +215 -0
  599. mobile_agent/outlets/__init__.py +1 -0
  600. mobile_agent/outlets/connect.py +249 -0
  601. mobile_agent/outlets/folder.py +180 -0
  602. mobile_agent/outlets/hooks.py +453 -0
  603. mobile_agent/outlets/inbound.py +472 -0
  604. mobile_agent/outlets/inbound_reply.py +57 -0
  605. mobile_agent/outlets/inbound_routes.py +81 -0
  606. mobile_agent/outlets/kinds.py +308 -0
  607. mobile_agent/outlets/mail.py +110 -0
  608. mobile_agent/outlets/net.py +86 -0
  609. mobile_agent/outlets/notion.py +244 -0
  610. mobile_agent/outlets/register.py +56 -0
  611. mobile_agent/outlets/routes.py +135 -0
  612. mobile_agent/outlets/service.py +580 -0
  613. mobile_agent/outlets/sheets.py +169 -0
  614. mobile_agent/outlets/slack.py +177 -0
  615. mobile_agent/outlets/store.py +194 -0
  616. mobile_agent/outlets/vault.py +80 -0
  617. mobile_agent/outlets/webhook.py +316 -0
  618. mobile_agent/output_contract.py +141 -0
  619. mobile_agent/owner_presence.py +211 -0
  620. mobile_agent/ownership.py +219 -0
  621. mobile_agent/packs/__init__.py +1 -0
  622. mobile_agent/packs/calendar.py +243 -0
  623. mobile_agent/packs/context.py +68 -0
  624. mobile_agent/packs/core.py +757 -0
  625. mobile_agent/packs/front_door.py +316 -0
  626. mobile_agent/packs/heal.py +214 -0
  627. mobile_agent/packs/health.py +122 -0
  628. mobile_agent/packs/insights.py +76 -0
  629. mobile_agent/packs/instagram.py +640 -0
  630. mobile_agent/packs/linkedin.py +106 -0
  631. mobile_agent/packs/maps.py +196 -0
  632. mobile_agent/packs/mcp.py +149 -0
  633. mobile_agent/packs/messages.py +270 -0
  634. mobile_agent/packs/notes.py +226 -0
  635. mobile_agent/packs/photos.py +166 -0
  636. mobile_agent/packs/pins.py +47 -0
  637. mobile_agent/packs/reddit.py +110 -0
  638. mobile_agent/packs/register.py +276 -0
  639. mobile_agent/packs/rows.py +108 -0
  640. mobile_agent/packs/safari.py +143 -0
  641. mobile_agent/packs/settings.py +253 -0
  642. mobile_agent/packs/social.py +300 -0
  643. mobile_agent/packs/threads.py +97 -0
  644. mobile_agent/packs/tiktok.py +455 -0
  645. mobile_agent/packs/tools.py +290 -0
  646. mobile_agent/packs/x.py +127 -0
  647. mobile_agent/packs/youtube.py +199 -0
  648. mobile_agent/parity.py +270 -0
  649. mobile_agent/paths.py +99 -0
  650. mobile_agent/perception.py +470 -0
  651. mobile_agent/permissions/__init__.py +32 -0
  652. mobile_agent/permissions/consequence.py +535 -0
  653. mobile_agent/permissions/exceptions.py +99 -0
  654. mobile_agent/permissions/gate.py +316 -0
  655. mobile_agent/permissions/outside.py +316 -0
  656. mobile_agent/permissions/policy.py +288 -0
  657. mobile_agent/permissions/project_policy.py +411 -0
  658. mobile_agent/permissions/register.py +148 -0
  659. mobile_agent/permissions/rules.py +291 -0
  660. mobile_agent/phone_io/__init__.py +146 -0
  661. mobile_agent/phone_io/cli.py +427 -0
  662. mobile_agent/phone_io/clipboard.py +78 -0
  663. mobile_agent/phone_io/controls.py +78 -0
  664. mobile_agent/phone_io/files.py +289 -0
  665. mobile_agent/phone_io/handoff.py +123 -0
  666. mobile_agent/phone_io/icloud.py +118 -0
  667. mobile_agent/phone_io/install.py +188 -0
  668. mobile_agent/phone_io/network_capture.py +188 -0
  669. mobile_agent/phone_io/network_cli.py +138 -0
  670. mobile_agent/phone_io/network_profiles.py +138 -0
  671. mobile_agent/phone_io/photos.py +512 -0
  672. mobile_agent/phone_io/recording.py +115 -0
  673. mobile_agent/phone_io/secrets.py +146 -0
  674. mobile_agent/phone_io/transfer.py +895 -0
  675. mobile_agent/phone_io/tunnel.py +129 -0
  676. mobile_agent/phone_ready.py +91 -0
  677. mobile_agent/pinning.py +158 -0
  678. mobile_agent/plan_gate.py +807 -0
  679. mobile_agent/plugins/__init__.py +3 -0
  680. mobile_agent/plugins/install.py +163 -0
  681. mobile_agent/plugins/manifest.py +242 -0
  682. mobile_agent/plugins/register.py +15 -0
  683. mobile_agent/plugins/routes.py +82 -0
  684. mobile_agent/plugins/service.py +322 -0
  685. mobile_agent/plugins/skills.py +76 -0
  686. mobile_agent/plugins/store.py +79 -0
  687. mobile_agent/pool.py +285 -0
  688. mobile_agent/power_watch.py +275 -0
  689. mobile_agent/premium_model.py +22 -0
  690. mobile_agent/project_env.py +291 -0
  691. mobile_agent/project_env_cli.py +74 -0
  692. mobile_agent/project_env_routes.py +55 -0
  693. mobile_agent/projects/__init__.py +27 -0
  694. mobile_agent/projects/ci.py +332 -0
  695. mobile_agent/projects/clone.py +339 -0
  696. mobile_agent/projects/drafts.py +158 -0
  697. mobile_agent/projects/folder.py +436 -0
  698. mobile_agent/projects/github.py +131 -0
  699. mobile_agent/projects/register.py +31 -0
  700. mobile_agent/projects/revisions.py +253 -0
  701. mobile_agent/projects/routes.py +234 -0
  702. mobile_agent/projects/runs.py +297 -0
  703. mobile_agent/projects/service.py +695 -0
  704. mobile_agent/projects/setup.py +84 -0
  705. mobile_agent/projects/store.py +379 -0
  706. mobile_agent/projects/sync.py +380 -0
  707. mobile_agent/projects/workflow_files.py +270 -0
  708. mobile_agent/replay_core/__init__.py +26 -0
  709. mobile_agent/replay_core/checks.py +594 -0
  710. mobile_agent/replay_core/engine.py +596 -0
  711. mobile_agent/replay_core/heal.py +364 -0
  712. mobile_agent/replay_core/path.py +240 -0
  713. mobile_agent/replay_core/recorder.py +206 -0
  714. mobile_agent/replay_core/store.py +171 -0
  715. mobile_agent/result_blocks.py +941 -0
  716. mobile_agent/run.py +642 -0
  717. mobile_agent/run_delivery.py +123 -0
  718. mobile_agent/run_executor.py +200 -0
  719. mobile_agent/run_export.py +284 -0
  720. mobile_agent/run_gif.py +891 -0
  721. mobile_agent/runner_hints.py +86 -0
  722. mobile_agent/runner_renewal.py +205 -0
  723. mobile_agent/runtime_create.py +549 -0
  724. mobile_agent/runtime_driver.py +458 -0
  725. mobile_agent/runtime_limits.py +319 -0
  726. mobile_agent/runtime_queue.py +394 -0
  727. mobile_agent/runtime_runs.py +383 -0
  728. mobile_agent/runtime_status.py +181 -0
  729. mobile_agent/runtime_teach.py +295 -0
  730. mobile_agent/runtime_work.py +599 -0
  731. mobile_agent/screen_text.py +101 -0
  732. mobile_agent/secret_filter.py +113 -0
  733. mobile_agent/serve.py +160 -0
  734. mobile_agent/server.py +363 -0
  735. mobile_agent/service_support.py +42 -0
  736. mobile_agent/settle/__init__.py +1 -0
  737. mobile_agent/settle/predict.py +102 -0
  738. mobile_agent/settle/replay.py +92 -0
  739. mobile_agent/settle/store.py +93 -0
  740. mobile_agent/settle/stream.py +43 -0
  741. mobile_agent/setup_cli.py +225 -0
  742. mobile_agent/setup_errors.py +87 -0
  743. mobile_agent/setup_service.py +527 -0
  744. mobile_agent/share.py +104 -0
  745. mobile_agent/signing.py +184 -0
  746. mobile_agent/sim/__init__.py +6 -0
  747. mobile_agent/sim/__main__.py +26 -0
  748. mobile_agent/sim/api.py +952 -0
  749. mobile_agent/sim/apps.py +172 -0
  750. mobile_agent/sim/biometrics.py +84 -0
  751. mobile_agent/sim/cli.py +271 -0
  752. mobile_agent/sim/doctor.py +293 -0
  753. mobile_agent/sim/governor.py +90 -0
  754. mobile_agent/sim/holders.py +88 -0
  755. mobile_agent/sim/models.py +62 -0
  756. mobile_agent/sim/registry.py +161 -0
  757. mobile_agent/sim/simctl.py +269 -0
  758. mobile_agent/sim/wda.py +618 -0
  759. mobile_agent/sim/xcode.py +74 -0
  760. mobile_agent/skills/__init__.py +12 -0
  761. mobile_agent/skills/codes.py +597 -0
  762. mobile_agent/skills/notifications.py +281 -0
  763. mobile_agent/skills/open_url.py +127 -0
  764. mobile_agent/smart_events.py +588 -0
  765. mobile_agent/smart_result.py +490 -0
  766. mobile_agent/smart_run.py +413 -0
  767. mobile_agent/state.py +662 -0
  768. mobile_agent/states.py +217 -0
  769. mobile_agent/steering.py +70 -0
  770. mobile_agent/style.py +178 -0
  771. mobile_agent/supervised.py +234 -0
  772. mobile_agent/swift/helper_bt_hid.swift +534 -0
  773. mobile_agent/swift/helper_doc_text.swift +184 -0
  774. mobile_agent/swift/helper_draft_reply.swift +170 -0
  775. mobile_agent/swift/helper_map.swift +110 -0
  776. mobile_agent/swift/helper_mirroring.swift +1099 -0
  777. mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
  778. mobile_agent/swift/helper_pdf.swift +100 -0
  779. mobile_agent/swift/helper_phone_audio.swift +993 -0
  780. mobile_agent/swift/helper_phone_screen.swift +416 -0
  781. mobile_agent/swift/helper_screen_text.swift +24 -0
  782. mobile_agent/swift/helper_transfer.swift +246 -0
  783. mobile_agent/swift/ocr.swift +22 -0
  784. mobile_agent/task_policy.py +337 -0
  785. mobile_agent/team_signing/__init__.py +34 -0
  786. mobile_agent/team_signing/apple_ca.py +37 -0
  787. mobile_agent/team_signing/bundle.py +136 -0
  788. mobile_agent/team_signing/cli.py +208 -0
  789. mobile_agent/team_signing/identity.py +248 -0
  790. mobile_agent/team_signing/ledger.py +105 -0
  791. mobile_agent/team_signing/prebuilt.py +61 -0
  792. mobile_agent/team_signing/resign.py +256 -0
  793. mobile_agent/team_signing/service.py +382 -0
  794. mobile_agent/team_signing/tooling.py +243 -0
  795. mobile_agent/team_signing/writes.py +268 -0
  796. mobile_agent/terminal_image.py +81 -0
  797. mobile_agent/testkit/__init__.py +1 -0
  798. mobile_agent/testkit/a11y_baseline.py +132 -0
  799. mobile_agent/testkit/api.py +403 -0
  800. mobile_agent/testkit/capsule.py +400 -0
  801. mobile_agent/testkit/capsule_cli.py +57 -0
  802. mobile_agent/testkit/cli.py +948 -0
  803. mobile_agent/testkit/cli_arguments.py +193 -0
  804. mobile_agent/testkit/comparison.py +288 -0
  805. mobile_agent/testkit/comparison_cli.py +41 -0
  806. mobile_agent/testkit/discover.py +257 -0
  807. mobile_agent/testkit/editor.py +243 -0
  808. mobile_agent/testkit/execute.py +515 -0
  809. mobile_agent/testkit/github_checks.py +248 -0
  810. mobile_agent/testkit/gitref.py +66 -0
  811. mobile_agent/testkit/heal_review.py +176 -0
  812. mobile_agent/testkit/health.py +480 -0
  813. mobile_agent/testkit/history.py +255 -0
  814. mobile_agent/testkit/hosted_results.py +321 -0
  815. mobile_agent/testkit/input_files.py +173 -0
  816. mobile_agent/testkit/jobs.py +285 -0
  817. mobile_agent/testkit/junit_verdict.py +33 -0
  818. mobile_agent/testkit/maestro.py +802 -0
  819. mobile_agent/testkit/mcp.py +562 -0
  820. mobile_agent/testkit/mcp_comparison.py +88 -0
  821. mobile_agent/testkit/minimize.py +471 -0
  822. mobile_agent/testkit/minimizer_cli.py +48 -0
  823. mobile_agent/testkit/notify.py +212 -0
  824. mobile_agent/testkit/phone_video.py +191 -0
  825. mobile_agent/testkit/pr.py +491 -0
  826. mobile_agent/testkit/pr_report.py +562 -0
  827. mobile_agent/testkit/prbuild.py +148 -0
  828. mobile_agent/testkit/proof_report.py +55 -0
  829. mobile_agent/testkit/provenance.py +119 -0
  830. mobile_agent/testkit/prrun.py +244 -0
  831. mobile_agent/testkit/recent.py +166 -0
  832. mobile_agent/testkit/recipes.py +146 -0
  833. mobile_agent/testkit/record.py +204 -0
  834. mobile_agent/testkit/register.py +288 -0
  835. mobile_agent/testkit/reports.py +998 -0
  836. mobile_agent/testkit/repro.py +432 -0
  837. mobile_agent/testkit/scenario.py +440 -0
  838. mobile_agent/testkit/scenario_cli.py +37 -0
  839. mobile_agent/testkit/snapshots_cli.py +60 -0
  840. mobile_agent/testkit/suite.py +670 -0
  841. mobile_agent/testkit/targets.py +497 -0
  842. mobile_agent/testkit/xctest.py +378 -0
  843. mobile_agent/threads/__init__.py +1 -0
  844. mobile_agent/threads/chat_once.py +264 -0
  845. mobile_agent/threads/cli.py +740 -0
  846. mobile_agent/threads/client.py +326 -0
  847. mobile_agent/threads/context.py +246 -0
  848. mobile_agent/threads/mcp.py +739 -0
  849. mobile_agent/threads/recent.py +34 -0
  850. mobile_agent/threads/register.py +54 -0
  851. mobile_agent/threads/routes.py +188 -0
  852. mobile_agent/threads/service.py +975 -0
  853. mobile_agent/threads/store.py +361 -0
  854. mobile_agent/threads/summary.py +117 -0
  855. mobile_agent/threads/term.py +101 -0
  856. mobile_agent/threads/workflow_cli.py +104 -0
  857. mobile_agent/tls.py +36 -0
  858. mobile_agent/traces.py +271 -0
  859. mobile_agent/tracks.py +235 -0
  860. mobile_agent/transport.py +271 -0
  861. mobile_agent/triggers/__init__.py +1 -0
  862. mobile_agent/triggers/actions.py +204 -0
  863. mobile_agent/triggers/model.py +146 -0
  864. mobile_agent/triggers/register.py +29 -0
  865. mobile_agent/triggers/routes.py +101 -0
  866. mobile_agent/triggers/service.py +387 -0
  867. mobile_agent/triggers/store.py +142 -0
  868. mobile_agent/tui/__init__.py +89 -0
  869. mobile_agent/tui/app.py +582 -0
  870. mobile_agent/tui/chrome.py +321 -0
  871. mobile_agent/tui/commands.py +375 -0
  872. mobile_agent/tui/demo_phone.py +369 -0
  873. mobile_agent/tui/images.py +29 -0
  874. mobile_agent/tui/keys.py +64 -0
  875. mobile_agent/tui/prompt.py +195 -0
  876. mobile_agent/tui/render.py +395 -0
  877. mobile_agent/tui/screens.py +180 -0
  878. mobile_agent/tui/session.py +308 -0
  879. mobile_agent/tui/tasks.py +287 -0
  880. mobile_agent/tui/theme.py +118 -0
  881. mobile_agent/tui/widgets.py +272 -0
  882. mobile_agent/update_check.py +213 -0
  883. mobile_agent/usage.py +184 -0
  884. mobile_agent/verify/__init__.py +0 -0
  885. mobile_agent/verify/__main__.py +41 -0
  886. mobile_agent/verify/a11y.py +189 -0
  887. mobile_agent/verify/actions.py +290 -0
  888. mobile_agent/verify/assertions.py +570 -0
  889. mobile_agent/verify/audit.py +312 -0
  890. mobile_agent/verify/business.py +221 -0
  891. mobile_agent/verify/business_run.py +131 -0
  892. mobile_agent/verify/business_state.py +168 -0
  893. mobile_agent/verify/check_schema.py +398 -0
  894. mobile_agent/verify/checks.py +614 -0
  895. mobile_agent/verify/cli.py +423 -0
  896. mobile_agent/verify/conditions.py +483 -0
  897. mobile_agent/verify/crashes.py +321 -0
  898. mobile_agent/verify/device_audit.py +246 -0
  899. mobile_agent/verify/dialogs.py +287 -0
  900. mobile_agent/verify/evidence.py +705 -0
  901. mobile_agent/verify/fixtures.py +305 -0
  902. mobile_agent/verify/flow.py +213 -0
  903. mobile_agent/verify/l10n.py +141 -0
  904. mobile_agent/verify/netcerts.py +67 -0
  905. mobile_agent/verify/netproxy.py +356 -0
  906. mobile_agent/verify/netrun.py +81 -0
  907. mobile_agent/verify/netshim.m +28 -0
  908. mobile_agent/verify/netshim.py +48 -0
  909. mobile_agent/verify/netstubs.py +132 -0
  910. mobile_agent/verify/overlay.py +174 -0
  911. mobile_agent/verify/perf.py +239 -0
  912. mobile_agent/verify/proof.py +192 -0
  913. mobile_agent/verify/replay_admission.py +48 -0
  914. mobile_agent/verify/report.py +952 -0
  915. mobile_agent/verify/run_cleanup.py +67 -0
  916. mobile_agent/verify/run_links.py +48 -0
  917. mobile_agent/verify/run_perf.py +44 -0
  918. mobile_agent/verify/run_secrets.py +39 -0
  919. mobile_agent/verify/runner.py +976 -0
  920. mobile_agent/verify/runs.py +57 -0
  921. mobile_agent/verify/screen_coverage.py +181 -0
  922. mobile_agent/verify/smart.py +106 -0
  923. mobile_agent/verify/snapshot_run.py +114 -0
  924. mobile_agent/verify/snapshots.py +306 -0
  925. mobile_agent/verify/sound.py +335 -0
  926. mobile_agent/verify/source_observation.py +130 -0
  927. mobile_agent/verify/system_surfaces/__init__.py +7 -0
  928. mobile_agent/verify/system_surfaces/play.py +106 -0
  929. mobile_agent/verify/system_surfaces/spec.py +139 -0
  930. mobile_agent/verify/system_surfaces/stage.py +138 -0
  931. mobile_agent/verify/system_surfaces/surfaces.py +121 -0
  932. mobile_agent/verify/tree.py +223 -0
  933. mobile_agent/verify/verdict.py +377 -0
  934. mobile_agent/verify/wire.py +392 -0
  935. mobile_agent/verify/wirestore.py +176 -0
  936. mobile_agent/waiting/__init__.py +1 -0
  937. mobile_agent/waiting/contacts.py +73 -0
  938. mobile_agent/waiting/drafts.py +136 -0
  939. mobile_agent/waiting/job.py +145 -0
  940. mobile_agent/waiting/messages.py +174 -0
  941. mobile_agent/waiting/rank.py +115 -0
  942. mobile_agent/waiting/register.py +26 -0
  943. mobile_agent/watch.py +502 -0
  944. mobile_agent/wda/__init__.py +2 -0
  945. mobile_agent/wda/frame_clock.py +646 -0
  946. mobile_agent/wda/frame_source.py +325 -0
  947. mobile_agent/wda/frame_store.py +115 -0
  948. mobile_agent/wda/gate.py +163 -0
  949. mobile_agent/wda/gestures.py +291 -0
  950. mobile_agent/wda/guards.py +94 -0
  951. mobile_agent/wda/identity.py +55 -0
  952. mobile_agent/wda/observe.py +884 -0
  953. mobile_agent/wda/patches.py +856 -0
  954. mobile_agent/wda/point_gestures.py +150 -0
  955. mobile_agent/wda/source.py +32 -0
  956. mobile_agent/wda/system_dialogs.py +571 -0
  957. mobile_agent/wda/text.py +694 -0
  958. mobile_agent/wda/timeouts.py +77 -0
  959. mobile_agent/wda/video.py +505 -0
  960. mobile_agent/wda/watchdog.py +243 -0
  961. mobile_agent/wireless/__init__.py +15 -0
  962. mobile_agent/wireless/api.py +354 -0
  963. mobile_agent/wireless/cli.py +287 -0
  964. mobile_agent/wireless/devicectl.py +229 -0
  965. mobile_agent/wireless/exposure.py +33 -0
  966. mobile_agent/wireless/mobster-wifi-pair.c +122 -0
  967. mobile_agent/wireless/monitor.py +332 -0
  968. mobile_agent/wireless/network.py +334 -0
  969. mobile_agent/wireless/onboarding.py +293 -0
  970. mobile_agent/wireless/pair.py +124 -0
  971. mobile_agent/wireless/probe.py +322 -0
  972. mobile_agent/wireless/register.py +8 -0
  973. mobile_agent/wireless/relay.py +179 -0
  974. mobile_agent/wireless/result.py +87 -0
  975. mobile_agent/wireless/store.py +85 -0
  976. mobile_agent/wireless/switch.py +77 -0
  977. mobile_agent/wireless/transport.py +118 -0
  978. mobile_agent/wireless/words.py +81 -0
  979. mobile_agent/wireless/xctestrun.py +83 -0
  980. mobile_agent/workflow_tools.py +59 -0
  981. mobile_agent/workflows/__init__.py +2 -0
  982. mobile_agent/workflows/brief.py +201 -0
  983. mobile_agent/workflows/cron.py +110 -0
  984. mobile_agent/workflows/health.py +257 -0
  985. mobile_agent/workflows/history.py +126 -0
  986. mobile_agent/workflows/records.py +442 -0
  987. mobile_agent/workflows/runs.py +1000 -0
  988. mobile_agent/workflows/scheduler.py +134 -0
  989. mobile_agent/workflows/service.py +973 -0
  990. mobile_agent/workflows/starters.py +285 -0
  991. mobile_agent/workflows/triggers.py +248 -0
  992. mobile_agent/xray/__init__.py +1 -0
  993. mobile_agent/xray/app_cli.py +116 -0
  994. mobile_agent/xray/cli.py +21 -0
  995. mobile_agent/xray/crawl.py +754 -0
  996. mobile_agent/xray/diff.py +171 -0
  997. mobile_agent/xray/diff_cli.py +43 -0
  998. mobile_agent/xray/job.py +184 -0
  999. mobile_agent/xray/paywall.py +188 -0
  1000. mobile_agent/xray/policy.py +305 -0
  1001. mobile_agent/xray/register.py +43 -0
  1002. mobile_agent/xray/report.py +283 -0
  1003. mobile_agent/xray/screens.py +117 -0
  1004. mobster_cli-0.4.3.dist-info/METADATA +93 -0
  1005. mobster_cli-0.4.3.dist-info/RECORD +1009 -0
  1006. mobster_cli-0.4.3.dist-info/WHEEL +5 -0
  1007. mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
  1008. mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
  1009. mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,382 @@
1
+ """Without Xcode, for a paid team: read the team, show the card, and after approval write, sign, install.
2
+
3
+ ``TeamSigning`` is what Setup's row and ``mobster signing`` drive. One run at a time:
4
+
5
+ 1. ``card`` reads App Store Connect (the phones, the App ID, the certificates, Mobster's profiles), makes the
6
+ certificate signing request on this Mac if a certificate is needed, and returns the card: exactly the requests
7
+ Mobster would send (writes.py). It sends nothing.
8
+ 2. ``approve`` takes the digest the person approved. It reads the team again, refuses if the card changed, then makes
9
+ the writes (each journaled first, none retried when the outcome is unknown), keeps the certificate beside its key,
10
+ signs the prebuilt runner with ``codesign`` and the new profile, mounts the developer image if the phone has none,
11
+ installs the signed runner, and records it for the supervisor.
12
+
13
+ Everything is off unless ``MOBSTER_EXPERIMENTAL_NO_XCODE=1`` (team_signing.enabled).
14
+ """
15
+
16
+ import base64
17
+ import datetime
18
+ import shutil
19
+ import tempfile
20
+ import threading
21
+ import time
22
+ from pathlib import Path
23
+
24
+ from .. import signing
25
+ from ..devkit import DevError, asc
26
+ from ..wda import patches as wda_patches
27
+ from . import FLAG, bundle, enabled, prebuilt, resign, tooling, writes
28
+ from . import identity as identity_module
29
+ from . import ledger as ledger_module
30
+
31
+ STAGES = ("idle", "reading", "approval", "writing", "signing", "installing", "ready", "failed")
32
+ BUSY = ("reading", "writing", "signing", "installing")
33
+ KEY_TTL = 30.0
34
+ NO_KEY = (
35
+ "Mobster has no App Store Connect key for your team.",
36
+ "Make one in App Store Connect › Users and Access › Integrations with the Admin or Developer role, then "
37
+ "run: mobster asc key add --key-id KEY_ID --issuer-id ISSUER_ID --p8 AuthKey_KEY_ID.p8",
38
+ )
39
+
40
+
41
+ class TeamSigning:
42
+ def __init__(
43
+ self,
44
+ manager=None,
45
+ *,
46
+ folder=None,
47
+ environ=None,
48
+ make_client=None,
49
+ identity=None,
50
+ ledger=None,
51
+ sign=resign.sign,
52
+ run=None,
53
+ clock=time.time,
54
+ pmd3=tooling.pmd3,
55
+ go_ios=tooling.go_ios,
56
+ runner=None,
57
+ load_key=asc.load_key,
58
+ ):
59
+ self.manager, self.environ, self.clock = manager, environ, clock
60
+ self.folder = Path(folder) if folder is not None else self._folder()
61
+ self.load_key = load_key
62
+ self.make_client = make_client or (lambda: asc.Client(self.load_key()))
63
+ self.identity = identity or identity_module.Identity(self.folder)
64
+ self.ledger = ledger or ledger_module.Ledger(self.folder / "ledger.jsonl")
65
+ self.sign, self.run, self.pmd3, self.go_ios, self.runner = sign, run, pmd3, go_ios, runner
66
+ self.lock = threading.Lock()
67
+ self.job = None
68
+ self.current = {"stage": "idle", "error": None, "fix": None, "card": None, "result": None}
69
+ self._card = None
70
+ self._key = (0.0, None)
71
+
72
+ def refresh(self):
73
+ """Read the key again at the next status ("Check again")."""
74
+ self._key = (0.0, None)
75
+
76
+ def _folder(self):
77
+ from . import state_dir
78
+
79
+ return state_dir(getattr(self.manager, "data_dir", None))
80
+
81
+ # -- what Setup reads ------------------------------------------------------------------------------------
82
+
83
+ def status(self):
84
+ with self.lock:
85
+ current = dict(self.current)
86
+ # A finished run belongs to the phone it was for: another phone starts again from the card.
87
+ result = current.get("result") or {}
88
+ if (
89
+ current["stage"] == "ready"
90
+ and result.get("udid")
91
+ and self.manager is not None
92
+ and self.manager.settings().get("udid") not in (None, result["udid"])
93
+ ):
94
+ current = {**current, "stage": "idle", "card": None, "result": None}
95
+ key = self._public_key()
96
+ driver = None
97
+ try:
98
+ driver = tooling.choose_driver(flag=enabled(self.environ), pmd3_path=self.pmd3(), go_ios_path=self.go_ios())
99
+ except DevError:
100
+ pass
101
+ return {
102
+ "enabled": enabled(self.environ),
103
+ "flag": FLAG,
104
+ "driver": driver,
105
+ "tools": {"pymobiledevice3": bool(self.pmd3()), "go-ios": bool(self.go_ios())},
106
+ "key": key,
107
+ "unresolved": self.ledger.unresolved(),
108
+ **current,
109
+ }
110
+
111
+ def _public_key(self):
112
+ """The key's public part, read from the Keychain at most every 30 s (Setup asks every couple of seconds)."""
113
+ at, key = self._key
114
+ if self.clock() - at < KEY_TTL and at:
115
+ return key
116
+ try:
117
+ key = self.load_key().public()
118
+ except DevError:
119
+ key = None
120
+ self._key = (self.clock(), key)
121
+ return key
122
+
123
+ def _set(self, **changes):
124
+ with self.lock:
125
+ self.current = {**self.current, **changes}
126
+
127
+ def _fail(self, error):
128
+ fix = getattr(error, "fix", "") or None
129
+ message = getattr(error, "message", None) or str(error) or type(error).__name__
130
+ self._set(stage="failed", error=message, fix=fix)
131
+
132
+ # -- the card --------------------------------------------------------------------------------------------
133
+
134
+ def _phones(self, phones=None):
135
+ if phones:
136
+ return [dict(phone) for phone in phones]
137
+ manager = self.manager
138
+ found = [d for d in (manager.devices() if manager else ()) if d.get("trusted")]
139
+ chosen = manager.device(found) if manager else None
140
+ ordered = ([chosen] if chosen else []) + [d for d in found if not chosen or d["udid"] != chosen["udid"]]
141
+ return [{"name": d["name"], "udid": d["udid"]} for d in ordered]
142
+
143
+ def _plan(self, client, phones, bundle_identifier=None, team=None):
144
+ phones = self._phones(phones)
145
+ if not phones:
146
+ raise DevError("Mobster has no iPhone to set up.", "Plug in an iPhone that trusts this Mac.")
147
+ if bundle_identifier is None:
148
+ team = team or (self.manager.settings().get("team") if self.manager else None) or client.team_id()
149
+ if not team:
150
+ raise DevError(
151
+ "Mobster couldn't tell which team the key belongs to.",
152
+ "Pass the team id with --team, or register an App ID on the team first.",
153
+ )
154
+ bundle_identifier = signing.runner_bundle_id(team) + ".xctrunner"
155
+ state = self.identity.state()
156
+ card = writes.plan(
157
+ client,
158
+ bundle_identifier=bundle_identifier,
159
+ phones=phones,
160
+ csr_pem=self.identity.request,
161
+ certificate_serial=state.get("certificate_serial"),
162
+ stamp=datetime.datetime.fromtimestamp(self.clock(), datetime.timezone.utc).strftime("%Y%m%d"),
163
+ )
164
+ return card, bundle_identifier
165
+
166
+ def card(self, phones=None, bundle_identifier=None, team=None):
167
+ """Read the team and return the card (JSON-ready). Sends nothing to Apple but reads."""
168
+ with self.lock:
169
+ if self.current["stage"] in BUSY:
170
+ raise DevError("Mobster is still working on the last card.")
171
+ self.current = {**self.current, "stage": "reading", "error": None, "fix": None}
172
+ try:
173
+ self._require_enabled()
174
+ client = self.make_client()
175
+ card, bundle_identifier = self._plan(client, phones, bundle_identifier, team)
176
+ shown = card.public(key=client.key.public())
177
+ self._card = (card, bundle_identifier, phones, team)
178
+ self._set(stage="approval", card=shown)
179
+ return shown
180
+ except Exception as error: # noqa: BLE001 -- the card shows why, in words
181
+ self._fail(error)
182
+ raise
183
+
184
+ def apply(self, digest, phones=None, bundle_identifier=None, team=None, writes_only=False):
185
+ """``card`` and ``approve`` in one process, for the terminal: reads the team again, refuses unless the card
186
+ is the one ``digest`` names, then runs it and waits."""
187
+ self.card(phones, bundle_identifier, team)
188
+ return self.approve(digest, writes_only=writes_only, background=False)
189
+
190
+ def _require_enabled(self):
191
+ if not enabled(self.environ):
192
+ raise DevError("Without Xcode is off.", f"Turn it on with {FLAG}=1.")
193
+
194
+ # -- approval and the run --------------------------------------------------------------------------------
195
+
196
+ def approve(self, digest, *, writes_only=False, background=True):
197
+ """Run what the card shows. ``digest`` is the card's, as the person approved it."""
198
+ with self.lock:
199
+ if self.current["stage"] != "approval" or self._card is None:
200
+ raise DevError("There is no card waiting for approval.", "Ask for the card again.")
201
+ card = self._card[0]
202
+ if digest != card.digest:
203
+ raise DevError("That isn't the card on screen, so Mobster sent nothing.", "Look at it again.")
204
+ self.current = {**self.current, "stage": "writing", "error": None, "fix": None}
205
+ if background:
206
+ self.job = threading.Thread(
207
+ target=self._job, args=(digest, writes_only), name="mobster-team-signing", daemon=True
208
+ )
209
+ self.job.start()
210
+ else:
211
+ self._job(digest, writes_only)
212
+ return self.status()
213
+
214
+ def _job(self, digest, writes_only):
215
+ try:
216
+ client = self.make_client()
217
+ _, bundle_identifier, phones, team = self._card
218
+ self._recover(client)
219
+ card, _ = self._plan(client, phones, bundle_identifier, team)
220
+ if card.digest != digest:
221
+ raise DevError(
222
+ "Your team changed after you approved the card, so Mobster sent nothing.", "Look at the new card."
223
+ )
224
+ answers = writes.execute(client, card, digest, self.ledger, on_answer=self._keep)
225
+ if writes_only:
226
+ self._set(
227
+ stage="ready", result={"writes": {name: answer.get("id") for name, answer in answers.items()}}
228
+ )
229
+ return
230
+ self._set(stage="signing")
231
+ profile = self._profile(client, card, answers)
232
+ identity = self.identity.signing_identity()
233
+ if identity is None:
234
+ raise DevError(
235
+ "The signing certificate isn't in Mobster's keychain.", "Ask for the card again to make one."
236
+ )
237
+ derived = getattr(self.manager, "derived", None)
238
+ built = self.manager.settings().get("built_patches") if self.manager else None
239
+ source, where = prebuilt.locate(derived, built)
240
+ phone = self._phones(phones)[0]
241
+ signed = self.sign(
242
+ source,
243
+ profile,
244
+ identity["sha1"],
245
+ self.identity.keychain,
246
+ self.folder / "runners" / phone["udid"],
247
+ bundle_id=bundle_identifier,
248
+ )
249
+ self._set(stage="installing")
250
+ self._install(phone["udid"], signed)
251
+ self._record(phone, signed, where)
252
+ self._set(
253
+ stage="ready",
254
+ result={
255
+ "runner": signed["bundle_id"],
256
+ "expires": signed["expires"],
257
+ "phones": signed["udids"],
258
+ "udid": phone["udid"],
259
+ },
260
+ )
261
+ except Exception as error: # noqa: BLE001 -- the stage shows why; the ledger holds what was sent
262
+ self._fail(error)
263
+
264
+ def _keep(self, step, answer):
265
+ if step.produces == "certificate":
266
+ attributes = answer.get("attributes") or {}
267
+ self.identity.add_certificate(attributes.get("certificateContent") or "", attributes.get("serialNumber"))
268
+
269
+ def _recover(self, client):
270
+ """A certificate Apple made whose answer Mobster didn't get to keep (it stopped between the write and the
271
+ keychain): fetch it by the id the ledger recorded, so the work isn't lost and the write isn't repeated."""
272
+ if self.identity.state().get("pending_key") != "imported":
273
+ return
274
+ writes_by_digest, made = {}, None
275
+ for event in self.ledger.events():
276
+ if event["event"] == "intent":
277
+ writes_by_digest[event["digest"]] = event["write"]
278
+ elif (
279
+ event["event"] in ("done", "resolved_done")
280
+ and writes_by_digest.get(event["digest"]) == "create_certificate"
281
+ ):
282
+ made = event
283
+ if made is None or not (made.get("detail") or {}).get("id"):
284
+ return
285
+ content, serial = client.certificate_content(made["detail"]["id"])
286
+ if content:
287
+ self.identity.add_certificate(content, serial)
288
+
289
+ def _profile(self, client, card, answers):
290
+ folder = self.folder / "profiles"
291
+ from .. import env_file
292
+
293
+ env_file.private_dir(folder)
294
+ answer = answers.get("profile")
295
+ profile_id = (answer or card.profile or {}).get("id")
296
+ content = ((answer or {}).get("attributes") or {}).get("profileContent")
297
+ data = base64.b64decode(content) if content else client.profile_content(profile_id)
298
+ if not data:
299
+ raise DevError("Apple's profile came back empty.", "Try again.")
300
+ path = folder / f"{profile_id}.mobileprovision"
301
+ with env_file.open_private(path, "wb") as stream:
302
+ stream.write(data)
303
+ return path
304
+
305
+ def _install(self, udid, signed):
306
+ program = self.pmd3()
307
+ driver = tooling.choose_driver(flag=True, pmd3_path=program, go_ios_path=self.go_ios())
308
+ if driver == "pymobiledevice3":
309
+ tooling.ensure_mounted(program, udid, run=self.runner)
310
+ tooling.run_short(tooling.install_command(program, udid, signed["path"]), run=self.runner)
311
+ return
312
+ scratch = tempfile.mkdtemp(prefix="mobster-install-")
313
+ try:
314
+ ipa = tooling.ipa_of(signed["path"], scratch)
315
+ tooling.run_short(tooling.go_ios_install_command(self.go_ios(), udid, ipa), run=self.runner)
316
+ finally:
317
+ shutil.rmtree(scratch, ignore_errors=True)
318
+
319
+ def _record(self, phone, signed, where):
320
+ driver = tooling.choose_driver(flag=True, pmd3_path=self.pmd3(), go_ios_path=self.go_ios())
321
+ if self.manager is None:
322
+ return
323
+ self.manager.save_settings(
324
+ udid=phone["udid"],
325
+ device_name=phone["name"],
326
+ team=signed["team"],
327
+ built_for=phone["udid"],
328
+ built_patches=wda_patches.patch_digest(),
329
+ expires_at=signed["expires"],
330
+ autostart=True,
331
+ driver=driver,
332
+ signed_runner={
333
+ "path": signed["path"],
334
+ "bundle_id": signed["bundle_id"],
335
+ "profile": signed["profile"],
336
+ "prebuilt": where,
337
+ "installed_at": self.clock(),
338
+ },
339
+ )
340
+
341
+ # -- after an unknown outcome ----------------------------------------------------------------------------
342
+
343
+ def resolve(self, digest, made):
344
+ """The person looked at developer.apple.com and says whether the change was made."""
345
+ self.ledger.resolve(digest, bool(made))
346
+ return self.status()
347
+
348
+ # -- Path C: a signed helper from another Mac ---------------------------------------------------------------
349
+
350
+ def export_bundle(self, destination, udid=None):
351
+ """Write the helper this Mac signed for ``udid`` (default: the chosen phone) as a bundle other Macs can use."""
352
+ self._require_enabled()
353
+ udid = udid or self._phones()[0]["udid"]
354
+ runner = self.folder / "runners" / udid / "Runner.app"
355
+ if not runner.exists():
356
+ raise DevError("This Mac hasn't signed a helper for that iPhone.", "Set the iPhone up first.")
357
+ return bundle.export(runner, destination)
358
+
359
+ def import_bundle(self, path, team, phones=None):
360
+ """Install a bundle's helper on the phone. ``team`` is what the person says the bundle is from."""
361
+ self._require_enabled()
362
+ phone = self._phones(phones)[0]
363
+ self._set(stage="installing", error=None, fix=None)
364
+ try:
365
+ imported = bundle.import_bundle(
366
+ path, self.folder / "runners" / phone["udid"], team=team, udid=phone["udid"]
367
+ )
368
+ self._install(phone["udid"], imported)
369
+ self._record(phone, imported, "an imported bundle")
370
+ except Exception as error: # noqa: BLE001
371
+ self._fail(error)
372
+ raise
373
+ self._set(
374
+ stage="ready",
375
+ result={
376
+ "runner": imported["bundle_id"],
377
+ "expires": imported["expires"],
378
+ "phones": imported["udids"],
379
+ "udid": phone["udid"],
380
+ },
381
+ )
382
+ return imported
@@ -0,0 +1,243 @@
1
+ """Install and launch the signed runner without Xcode: pymobiledevice3 first, go-ios as the fallback.
2
+
3
+ Both are separate programs you install yourself: ``pipx install pymobiledevice3`` (GPL-3.0-or-later), or go-ios (MIT).
4
+ Mobster never imports or bundles either. It builds their command lines and runs them as it runs ``xcodebuild`` today:
5
+ the install is a short command, the launch is the long-running runner that Mobster's supervisor keeps alive and whose
6
+ readiness it checks with the same ``/status`` probe. ``xcodebuild`` and ``xcrun`` are never among them.
7
+
8
+ Unverified on a real iPhone as of 19 Oct 2026: the commands follow pymobiledevice3 11.18.0's and go-ios's own help. The
9
+ install-without-Xcode proof is pending a phone (team_signing/README in docs/device-setup.mdx says so).
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import re
15
+ import shutil
16
+ import zipfile
17
+ from pathlib import Path
18
+
19
+ from ..devkit import DevError
20
+ from ..phone_io import tunnel
21
+
22
+ PMD3 = "pymobiledevice3"
23
+ GO_IOS = "ios"
24
+ DRIVERS = ("xcodebuild", "pymobiledevice3", "go-ios")
25
+ LOOPBACK = "127.0.0.1"
26
+ UDID = re.compile(r"[0-9A-Fa-f-]{24,40}")
27
+ NEVER = frozenset({"xcodebuild", "xcrun", "xcode-select", "devicectl"})
28
+ GO_IOS_DIRS = (
29
+ "/opt/homebrew/bin",
30
+ "/usr/local/bin",
31
+ os.path.expanduser("~/go/bin"),
32
+ os.path.expanduser("~/.local/bin"),
33
+ )
34
+
35
+
36
+ def pmd3():
37
+ """pymobiledevice3 on PATH or in the usual pipx and Homebrew folders; None when it isn't installed."""
38
+ return tunnel.tool()
39
+
40
+
41
+ def go_ios():
42
+ """go-ios's ``ios`` on PATH or in the usual folders; None when it isn't installed."""
43
+ for candidate in (shutil.which(GO_IOS), *(os.path.join(directory, GO_IOS) for directory in GO_IOS_DIRS)):
44
+ if candidate and os.path.isfile(candidate) and os.access(candidate, os.X_OK):
45
+ return candidate
46
+ return None
47
+
48
+
49
+ def choose_driver(*, flag, pmd3_path=None, go_ios_path=None):
50
+ """Which program installs and launches the runner. "xcodebuild" unless Without Xcode is on (__init__.FLAG); then
51
+ pymobiledevice3 if it is installed, else go-ios. DevError, with how to install, when neither is."""
52
+ if not flag:
53
+ return "xcodebuild"
54
+ if pmd3_path:
55
+ return "pymobiledevice3"
56
+ if go_ios_path:
57
+ return "go-ios"
58
+ raise DevError(
59
+ "Mobster needs pymobiledevice3 or go-ios to put its helper on your iPhone without Xcode, and neither "
60
+ "is on this Mac.",
61
+ "Install pymobiledevice3 with `pipx install pymobiledevice3`, then try again.",
62
+ )
63
+
64
+
65
+ def _udid(udid):
66
+ if not isinstance(udid, str) or not UDID.fullmatch(udid):
67
+ raise DevError("That isn't an iPhone's identifier.")
68
+ return udid
69
+
70
+
71
+ def _check(argv):
72
+ if Path(argv[0]).name in NEVER:
73
+ raise DevError(f"Mobster didn't run {Path(argv[0]).name}: this path never starts Xcode's tools.")
74
+ return argv
75
+
76
+
77
+ # -- pymobiledevice3 -----------------------------------------------------------------------------------------------
78
+
79
+
80
+ def developer_mode_command(program, udid):
81
+ return _check([program, "amfi", "developer-mode-status", "--native", "--udid", _udid(udid)])
82
+
83
+
84
+ def reveal_developer_mode_command(program, udid):
85
+ """Makes the Developer Mode switch appear in Settings › Privacy & Security. The person still turns it on."""
86
+ return _check([program, "amfi", "reveal-developer-mode", "--native", "--udid", _udid(udid)])
87
+
88
+
89
+ def mounted_command(program, udid):
90
+ return _check([program, "mounter", "list", "--native", "--udid", _udid(udid)])
91
+
92
+
93
+ def mount_command(program, udid):
94
+ """Mounts Apple's developer disk image for the phone (it downloads the image and asks Apple's signing service to
95
+ personalize it, as Xcode does the first time a phone is prepared)."""
96
+ return _check([program, "mounter", "auto-mount", "--native", "--udid", _udid(udid)])
97
+
98
+
99
+ def install_command(program, udid, app):
100
+ return _check([program, "apps", "install", str(app), "--native", "--udid", _udid(udid)])
101
+
102
+
103
+ def launch_command(program, udid, runner_bundle_id, env):
104
+ """The XCTest session that is Mobster's helper. It runs until stopped. ``env`` is what the runner reads
105
+ (USE_IP, the capture gate)."""
106
+ command = [program, "developer", "dvt", "xcuitest", runner_bundle_id, "--native", "--udid", _udid(udid)]
107
+ for key, value in sorted(env.items()):
108
+ command += ["--env", f"{key}={value}"]
109
+ return _check(command)
110
+
111
+
112
+ def runner_env(kind="phone", environ=None):
113
+ """The environment the runner starts with, as KEY=VALUE for ``--env``: USE_IP on the loopback (the USB relay reaches
114
+ it there) and the capture gate for an iPhone (wda_gate)."""
115
+ from ..wda import gate as wda_gate
116
+
117
+ gate = wda_gate.gate_for(kind, environ)
118
+ return {"USE_IP": LOOPBACK, wda_gate.OVERRIDE: gate}
119
+
120
+
121
+ # -- go-ios --------------------------------------------------------------------------------------------------------
122
+
123
+
124
+ def ipa_of(app, folder):
125
+ """A .ipa (Payload/<name>.app) of the signed runner, for go-ios, which installs packages and not bundles."""
126
+ app = Path(app)
127
+ target = Path(folder) / f"{app.stem}.ipa"
128
+ with zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as archive:
129
+ for path in sorted(app.rglob("*")):
130
+ relative = f"Payload/{app.name}/{path.relative_to(app).as_posix()}"
131
+ info = zipfile.ZipInfo.from_file(path, relative)
132
+ if path.is_symlink():
133
+ info.external_attr = 0o120755 << 16
134
+ archive.writestr(info, os.readlink(path))
135
+ elif path.is_dir():
136
+ continue
137
+ else:
138
+ with open(path, "rb") as stream:
139
+ archive.writestr(info, stream.read(), zipfile.ZIP_DEFLATED)
140
+ return target
141
+
142
+
143
+ def go_ios_install_command(program, udid, ipa):
144
+ return _check([program, "install", f"--path={ipa}", f"--udid={_udid(udid)}"])
145
+
146
+
147
+ def go_ios_launch_command(program, udid, runner_bundle_id, env):
148
+ """go-ios's ``runwda``: the runner's bundle id, its test bundle and the xctest configuration it runs."""
149
+ bundle = runner_bundle_id.removesuffix(".xctrunner")
150
+ command = [
151
+ program,
152
+ "runwda",
153
+ f"--bundleid={bundle}",
154
+ f"--testrunnerbundleid={runner_bundle_id}",
155
+ "--xctestconfig=WebDriverAgentRunner.xctest",
156
+ f"--udid={_udid(udid)}",
157
+ ]
158
+ command += [f"--env={key}={value}" for key, value in sorted(env.items())]
159
+ return _check(command)
160
+
161
+
162
+ # -- one place to run the short commands ---------------------------------------------------------------------------
163
+
164
+
165
+ def run_short(command, *, run=None, timeout=300):
166
+ """Run an install-time command (not the launch). ``run(argv, timeout) -> (code, output)`` replaces the program in
167
+ tests. DevError with the program's last line when it fails."""
168
+ import subprocess
169
+
170
+ if run is None:
171
+
172
+ def run(argv, timeout):
173
+ from ..native_helpers import require_devices
174
+
175
+ require_devices()
176
+ env = dict(os.environ, NO_COLOR="1", TERM="dumb", COLUMNS="200")
177
+ try:
178
+ done = subprocess.run(
179
+ argv,
180
+ capture_output=True,
181
+ text=True,
182
+ errors="replace",
183
+ timeout=timeout,
184
+ stdin=subprocess.DEVNULL,
185
+ env=env,
186
+ )
187
+ except subprocess.TimeoutExpired:
188
+ return 124, f"{Path(argv[0]).name} did not finish in {timeout} seconds"
189
+ except OSError as error:
190
+ return 127, type(error).__name__
191
+ return done.returncode, (done.stdout or "") + (done.stderr or "")
192
+
193
+ code, output = run(command, timeout)
194
+ if code != 0:
195
+ line = next((line for line in reversed(str(output).splitlines()) if line.strip()), "")
196
+ raise DevError(
197
+ f"{Path(command[0]).name} couldn't {command[1]}. {line[:200]}".strip(),
198
+ "Keep the iPhone unlocked and plugged in, then try again.",
199
+ )
200
+ return output
201
+
202
+
203
+ def developer_mode(program, udid, *, run=None):
204
+ """True or False from pymobiledevice3, None when it can't say (the phone doesn't trust this Mac, or the tool is
205
+ missing). Reads the phone's Developer Mode state over USB; changes nothing."""
206
+ try:
207
+ output = run_short(developer_mode_command(program, udid), run=run, timeout=30)
208
+ except DevError:
209
+ return None
210
+ words = output.strip().lower().splitlines()
211
+ last = words[-1] if words else ""
212
+ if re.search(r"\btrue\b|\benabled\b", last):
213
+ return True
214
+ if re.search(r"\bfalse\b|\bdisabled\b", last):
215
+ return False
216
+ return None
217
+
218
+
219
+ def mounted(program, udid, *, run=None):
220
+ """Whether a developer disk image is mounted on the phone: ``mounter list`` prints a JSON list of what is."""
221
+ try:
222
+ output = run_short(mounted_command(program, udid), run=run, timeout=60)
223
+ except DevError:
224
+ return False
225
+ try:
226
+ listed = json.loads(output)
227
+ except ValueError:
228
+ return False
229
+ return bool(listed)
230
+
231
+
232
+ def ensure_mounted(program, udid, *, run=None):
233
+ """Mount the developer disk image unless one is. True when this call mounted it. A phone that Xcode (on any Mac)
234
+ prepared already has it, and nothing is downloaded or asked of Apple's signing service."""
235
+ if mounted(program, udid, run=run):
236
+ return False
237
+ try:
238
+ run_short(mount_command(program, udid), run=run, timeout=900)
239
+ except DevError as error:
240
+ if "already" not in error.message.lower():
241
+ raise
242
+ return False
243
+ return True