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,527 @@
1
+ """The setup checklist the dashboard's onboarding flow renders (docs/setup-api.md)."""
2
+
3
+ import os
4
+ import re
5
+ import subprocess
6
+ import threading
7
+ import time
8
+ from concurrent.futures import ThreadPoolExecutor
9
+
10
+ from . import mac_tools, phone_ready, signing
11
+ from .env_secrets import update_env_file
12
+ from .keys import anthropic_key, openai_key
13
+ from .runner_renewal import RunnerRenewal
14
+
15
+ # The slow reads (Xcode's state, the USB phones and System Information) run side by side.
16
+ READS = ThreadPoolExecutor(max_workers=3, thread_name_prefix="mobster-setup-read")
17
+
18
+ INSTALL_TOOLS = "brew install libimobiledevice"
19
+ # What Install runs in the background when this build doesn't carry the tools itself (a source checkout with Homebrew):
20
+ # libimobiledevice, and ideviceinstaller for the Apps page. The shipped app never needs it (build-iphone-tools.sh).
21
+ INSTALL_FORMULAE = ("libimobiledevice", "ideviceinstaller")
22
+ # brew.sh's own install command, shown only when Homebrew is missing.
23
+ INSTALL_HOMEBREW = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
24
+ BREW_DIRS = ("/opt/homebrew/bin", "/usr/local/bin")
25
+ # What is wrong with Xcode, in the user's words (mac_tools.xcode_status states). The step lists the
26
+ # Terminal commands that fix it, so these say what happened and any way to fix it without Terminal.
27
+ XCODE_DETAIL = {
28
+ "missing": "Install Xcode from the Mac App Store. It's a large download and can take an hour, so start it "
29
+ "now, then open Xcode once when it finishes.",
30
+ "license": "Accept the Xcode license: open Xcode and agree, or use the command below.",
31
+ "first_launch": "Xcode hasn't finished installing. Open it once and let it install its components, or use "
32
+ "the command below.",
33
+ "no_ios": "Xcode doesn't have the iOS platform yet. Open Xcode › Settings › Components and get iOS, or use "
34
+ "the command below.",
35
+ "broken": "Xcode isn't responding. Open it once to finish installing it, then check again.",
36
+ }
37
+ EXPIRED_DETAIL = ("Mobster's helper needs a refresh: a free Apple Account signs it for 7 days at a time. Refresh it to "
38
+ "keep using your iPhone.")
39
+ # Measured 17 Oct 2026 on this repository's M5 Max and an iPhone 15 Pro: 115 s to build, sign and install (a cold
40
+ # clone of the pinned release is 5 s and its unsigned build 52 s). docs/requirements.mdx says the same.
41
+ FIRST_BUILD = "The first install takes about 2 minutes. Keep your iPhone unlocked and plugged in."
42
+ REBUILD = "Installing again is quicker than the first time. Keep your iPhone unlocked and plugged in."
43
+ DEVELOPER_MODE_TTL = (60.0, 5.0) # re-read after this long when on, and when off or unknown
44
+ # The preflight's one-line reading of each Xcode state (mac_tools.xcode_status).
45
+ XCODE_WORD = {"ok": "Ready", "missing": "Not installed", "license": "License",
46
+ "first_launch": "Not finished", "no_ios": "No iOS platform", "broken": "Not responding"}
47
+ XCODE_PREFLIGHT = {
48
+ "missing": "Free from the Mac App Store. It's a large download, so start it now.",
49
+ "license": "Open Xcode once and accept its license.",
50
+ "first_launch": "Open Xcode once so it can finish installing.",
51
+ "no_ios": "Open Xcode › Settings › Components and get iOS.",
52
+ "broken": "Open Xcode once so it can finish installing.",
53
+ }
54
+ def plan_ready():
55
+ """Whether Mobster's agent runs a task typed in Mobster without an OpenAI or Anthropic key."""
56
+ from . import engines
57
+ try:
58
+ return (engines.model_provider(engines.smart_model()) not in ("openai", "anthropic")
59
+ and bool(engines.smart_key()))
60
+ except Exception: # noqa: BLE001 -- a check that fails leaves the step to the keys
61
+ return False
62
+
63
+
64
+ def agents_ready():
65
+ """The coding agents ready on this Mac (installed and signed in: Codex, Claude Code, Gemini CLI), by name."""
66
+ from .ai_plans import agents
67
+ try:
68
+ return [state.get("name") or agent_id for agent_id,
69
+ state in agents.snapshot(max_age=300).items() if state.get("ready")]
70
+ except Exception: # noqa: BLE001 -- a check that fails leaves the step to the keys
71
+ return []
72
+
73
+
74
+ def agent_detail(names):
75
+ """The AI step's line when a coding agent is what typed tasks run on."""
76
+ listed = names[0] if len(names) == 1 else f"{', '.join(names[:-1])} or {names[-1]}"
77
+ return f"Tasks you type run on {listed}, signed in on this Mac."
78
+
79
+
80
+ # The AI account step (MESSAGING §11, Setup): whose account Mobster's agent uses, in the user's words.
81
+ KEY_DETAIL = {
82
+ "plan": "Mobster runs on a plan you already pay for. Settings › Models shows which.",
83
+ "openai": "Mobster's agent uses your OpenAI account. Your key is saved privately on this Mac.",
84
+ "anthropic": "Mobster's agent uses your Claude account. Your key is saved privately on this Mac.",
85
+ "none": "Mobster's agent runs on a plan you already pay for or on your own key. "
86
+ "Choose one to run tasks you type here.",
87
+ }
88
+
89
+
90
+ # The Photos step's line for each answer the helper gives (MOBSTER_QUEUE_PREFLIGHT, phone_ready.py). The person answers
91
+ # iOS's question on the iPhone; Mobster never presses it.
92
+ PHOTOS_DETAIL = {
93
+ "authorized": "Mobster can add the photos and videos you queue to Photos.",
94
+ "limited": "Mobster can add the photos and videos you queue to Photos. Limit Access is enough.",
95
+ "not_determined": "Press Allow, then answer your iPhone's question. Limit Access is enough.",
96
+ "denied": "Photos access is off for Mobster on your iPhone. Turn it on in Settings › Privacy & Security › Photos › "
97
+ "WebDriverAgentRunner-Runner. Limit Access is enough.",
98
+ "restricted": "Photos is restricted on your iPhone (Screen Time or a profile), so Mobster can't add to it.",
99
+ None: "Mobster couldn't read Photos access on your iPhone yet. Check again in a moment.",
100
+ }
101
+
102
+
103
+ class ToolsInstall:
104
+ """Installs the iPhone connection tools with Homebrew in the background, one line of progress at a time."""
105
+
106
+ def __init__(self, popen=subprocess.Popen):
107
+ self.popen = popen
108
+ self.lock = threading.Lock()
109
+ self.current = {"state": "idle", "line": None, "error": None}
110
+
111
+ def status(self):
112
+ with self.lock:
113
+ return dict(self.current)
114
+
115
+ def start(self, brew, on_done=None):
116
+ with self.lock:
117
+ if self.current["state"] == "running":
118
+ return
119
+ self.current = {"state": "running", "line": "Getting ready", "error": None}
120
+ threading.Thread(target=self.run, args=(brew, on_done), daemon=True, name="mobster-tools-install").start()
121
+
122
+ def update(self, **changes):
123
+ with self.lock:
124
+ self.current = {**self.current, **changes}
125
+
126
+ def run(self, brew, on_done=None):
127
+ env = {**os.environ, "HOMEBREW_NO_AUTO_UPDATE": "1", "HOMEBREW_NO_ENV_HINTS": "1", "NONINTERACTIVE": "1"}
128
+ tail = []
129
+ try:
130
+ process = self.popen([brew, "install", *INSTALL_FORMULAE], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
131
+ stdin=subprocess.DEVNULL, text=True, env=env)
132
+ for raw in process.stdout:
133
+ line = raw.strip()
134
+ if line:
135
+ tail = (tail + [line])[-12:]
136
+ words = install_words(line)
137
+ if words:
138
+ self.update(line=words)
139
+ code = process.wait()
140
+ except OSError as error:
141
+ self.update(state="failed", line=None, error=f"Homebrew didn't start ({error.strerror or error}).")
142
+ return
143
+ if code == 0:
144
+ self.update(state="succeeded", line=None, error=None)
145
+ else:
146
+ last = next((line for line in reversed(tail) if line.lower().startswith("error")), tail[-1] if tail else "")
147
+ self.update(state="failed", line=None,
148
+ error="The install didn't finish. Check your internet connection, then try again." +
149
+ (f" Homebrew said: {last[:200]}" if last else ""))
150
+ if on_done is not None:
151
+ on_done()
152
+
153
+
154
+ def install_words(line):
155
+ """Homebrew's progress line in a person's words ("Downloading libplist"),
156
+ or None for a line that says nothing new."""
157
+ match = re.match(r"==> (Fetching|Pouring|Installing|Upgrading)\s+(?:dependencies for\s+)?([\w@.+-]+)", line)
158
+ if not match:
159
+ return None
160
+ verb = {"Fetching": "Downloading", "Pouring": "Installing", "Installing": "Installing",
161
+ "Upgrading": "Updating"}[match[1]]
162
+ name = re.sub(r"(--|\.)[\w.]*bottle.*$", "", match[2]).rstrip(":")
163
+ return f"{verb} {name}"
164
+
165
+
166
+ def task_running(runtime):
167
+ """Whether a task is running now on the runtime's primary phone."""
168
+ try:
169
+ fleet = getattr(runtime, "fleet", None)
170
+ if fleet is not None and callable(getattr(runtime, "run_on", None)):
171
+ return runtime.run_on(fleet.primary) is not None
172
+ return getattr(runtime, "active", None) is not None
173
+ except Exception:
174
+ return True
175
+
176
+
177
+ class SetupService:
178
+ """``video`` and ``busy`` are a phone's own when it is not the runtime's primary phone (fleet.py)."""
179
+
180
+ def __init__(self, runtime, manager, env_file, video=None, busy=None, stop=None, idle=None):
181
+ self.runtime, self.manager, self.env_file = runtime, manager, env_file
182
+ # What the manager's watchdog and presence need from the runtime: whether a task runs on this phone, how to end
183
+ # it with a plain sentence when its helper is dead (wda/watchdog.py), and what to let go of when the phone has
184
+ # been idle (owner_presence.py: live view, prewarmed screen, awake hold).
185
+ if hasattr(manager, "watchdog"):
186
+ manager.busy = busy or (lambda: task_running(runtime))
187
+ manager.end_task = stop or (lambda message: runtime.stop_phone_run(runtime.fleet.primary, message))
188
+ if idle is not None:
189
+ manager.idle_hooks.append(idle)
190
+ elif callable(getattr(runtime, "release_idle", None)):
191
+ manager.idle_hooks.append(lambda: runtime.release_idle(runtime.fleet.primary))
192
+ self.video = video
193
+ self.cache = {}
194
+ self._team_signing = None
195
+ self.tools_install = ToolsInstall()
196
+ # The watch loop renews the runner before its signature runs out (runner_renewal.py).
197
+ self.renewal = RunnerRenewal(manager, busy=busy or (lambda: task_running(runtime))) \
198
+ if hasattr(manager, "start_build") else None
199
+ if self.renewal is not None:
200
+ manager.renewal = self.renewal
201
+
202
+ def team_signing(self):
203
+ """Signing without Xcode for a paid team (team_signing/), or None while MOBSTER_EXPERIMENTAL_NO_XCODE is off."""
204
+ from . import team_signing
205
+ if not team_signing.enabled():
206
+ return None
207
+ if self._team_signing is None:
208
+ from .team_signing.service import TeamSigning
209
+ self._team_signing = TeamSigning(self.manager)
210
+ return self._team_signing
211
+
212
+ def _signing(self):
213
+ signing_service = self.team_signing()
214
+ if signing_service is None:
215
+ raise LookupError("Without Xcode is off in this Mobster")
216
+ return signing_service
217
+
218
+ def signing_card(self):
219
+ self._signing().card()
220
+
221
+ def signing_approve(self, digest):
222
+ if not isinstance(digest, str) or not digest:
223
+ raise ValueError("Approve the card by its digest")
224
+ self._signing().approve(digest)
225
+
226
+ def signing_resolve(self, digest, made):
227
+ if not isinstance(digest, str) or type(made) is not bool:
228
+ raise ValueError("Say which change, and whether it was made")
229
+ self._signing().resolve(digest, made)
230
+
231
+ def signing_reveal_developer_mode(self):
232
+ """Makes the Developer Mode switch appear in Settings on the chosen iPhone. The person still turns it on."""
233
+ from .team_signing import tooling
234
+ device = self.manager.device()
235
+ program = tooling.pmd3()
236
+ if device is None or not device.get("trusted"):
237
+ raise LookupError("Connect your iPhone and tap Trust first")
238
+ if program is None:
239
+ raise LookupError("pymobiledevice3 isn't installed: pipx install pymobiledevice3")
240
+ tooling.run_short(tooling.reveal_developer_mode_command(program, device["udid"]), timeout=60)
241
+ self.cache.pop(("developer_mode", device["udid"]), None)
242
+
243
+ def developer_mode(self, device):
244
+ """Whether Developer Mode is on (True or False), or None when iOS or libimobiledevice can't say."""
245
+ if not device or not device.get("trusted") or not device.get("udid"):
246
+ return None
247
+ cached = self.cache.get(("developer_mode", device["udid"]))
248
+ if cached and time.monotonic() - cached[0] < DEVELOPER_MODE_TTL[cached[1] is not True]:
249
+ return cached[1]
250
+ info = mac_tools.tool("ideviceinfo")
251
+ output = mac_tools.run([info, "-u", device["udid"], "-q", "com.apple.security.mac.amfi", "-k",
252
+ "DeveloperModeStatus"], timeout=5) if info else None
253
+ value = {"true": True, "false": False}.get((output or "").strip().lower())
254
+ self.cache[("developer_mode", device["udid"])] = (time.monotonic(), value)
255
+ return value
256
+
257
+ def tools_step(self, tools):
258
+ """The tools step's detail and commands: Xcode first (it takes longest), then the iPhone connection tools."""
259
+ xcode = tools["xcode"]
260
+ usb_ok = bool(tools["iproxy"] and tools["idevice_id"])
261
+ bundled = (usb_ok and mac_tools.bundled_tool("iproxy") == tools["iproxy"]
262
+ and mac_tools.bundled_tool("idevice_id") == tools["idevice_id"])
263
+ homebrew = any(mac_tools.executable(directory, "brew") for directory in BREW_DIRS)
264
+ commands = [] if usb_ok else ([] if homebrew else [INSTALL_HOMEBREW]) + [INSTALL_TOOLS]
265
+ # Homebrew's installer can switch the Mac to the Command Line Tools, so Xcode's fixes come after it.
266
+ commands += xcode["fixes"]
267
+ if xcode["state"] == "ok" and usb_ok:
268
+ detail = f"{xcode['version'] or 'Xcode'} and the iPhone connection tools are installed."
269
+ elif xcode["state"] != "ok":
270
+ detail = XCODE_DETAIL.get(xcode["state"], XCODE_DETAIL["broken"])
271
+ elif homebrew:
272
+ detail = "Install the iPhone connection tools, which let this Mac talk to your iPhone over the cable."
273
+ else:
274
+ detail = ("Install Homebrew, then the iPhone connection tools, which let this Mac talk to your iPhone over "
275
+ "the cable.")
276
+ return detail, commands, {"xcode": {key: xcode[key] for key in ("state", "version", "app")},
277
+ "libimobiledevice": usb_ok, "homebrew": homebrew, "bundled": bundled,
278
+ "install": self.tools_install.status()}
279
+
280
+ def preflight(self, tools, devices, device, usb, without_xcode=False):
281
+ """Setup's first screen: Xcode, the cable, trust and Developer Mode, each with a word."""
282
+ xcode = tools["xcode"]
283
+ usb_tools = bool(tools["idevice_id"])
284
+ connected = bool(devices) or bool(usb)
285
+
286
+ def check(id_, title, state, word, detail):
287
+ return {"id": id_, "title": title, "state": state, "word": word, "detail": detail}
288
+
289
+ xcode_detail = (xcode["version"] or "Xcode is installed.") if xcode["state"] == "ok" else \
290
+ XCODE_PREFLIGHT.get(xcode["state"], XCODE_PREFLIGHT["broken"])
291
+ checks = [check("xcode", "Xcode", "ok" if xcode["state"] == "ok" else "todo",
292
+ XCODE_WORD.get(xcode["state"], "Not responding"),
293
+ xcode_detail)]
294
+ if without_xcode:
295
+ detail = "Mobster signs its helper on this Mac and installs it without Xcode (a paid Apple Developer team)."
296
+ checks = [check("xcode", "Xcode", "ok", "Not needed", detail)]
297
+ if connected:
298
+ count = len(devices) or usb or 1
299
+ name = device["name"] if device and device.get("trusted") else None
300
+ checks.append(check("cable", "iPhone cable", "ok", "Connected",
301
+ f"“{name}” is connected." if name else
302
+ f"{count} iPhones are connected." if count > 1 else "Your iPhone is connected."))
303
+ elif usb_tools or usb is not None:
304
+ checks.append(check("cable", "iPhone cable", "todo", "Not connected",
305
+ "Plug your iPhone into this Mac with a cable that carries data."))
306
+ else:
307
+ checks.append(check("cable", "iPhone cable", "unknown", "Can't tell yet",
308
+ "Mobster sees the cable once the iPhone tools are installed."))
309
+ trusted = bool(device and device.get("trusted")) or any(item.get("trusted") for item in devices)
310
+ if trusted:
311
+ checks.append(check("trust", "Trust this Mac", "ok", "Trusted", "Your iPhone trusts this Mac."))
312
+ elif devices:
313
+ checks.append(check("trust", "Trust this Mac", "todo", "Tap Trust",
314
+ "Unlock your iPhone, tap Trust and enter your passcode."))
315
+ elif connected and not usb_tools:
316
+ checks.append(check("trust", "Trust this Mac", "unknown", "Can't tell yet",
317
+ "Mobster checks this once the iPhone tools are installed."))
318
+ else:
319
+ checks.append(check("trust", "Trust this Mac", "waiting", "Waiting", "Connect your iPhone first."))
320
+ mode = device.get("developerMode") if device else None
321
+ if not trusted:
322
+ checks.append(check("developer_mode", "Developer Mode", "waiting", "Waiting",
323
+ "Mobster reads it once your iPhone trusts this Mac."))
324
+ elif mode is True:
325
+ checks.append(check("developer_mode", "Developer Mode", "ok", "On", "Developer Mode is on."))
326
+ elif mode is False:
327
+ checks.append(check("developer_mode", "Developer Mode", "todo", "Off",
328
+ "On your iPhone, turn it on in Settings › Privacy & Security › Developer Mode."))
329
+ elif without_xcode:
330
+ checks.append(check("developer_mode", "Developer Mode", "unknown", "Can't tell yet",
331
+ "Mobster reads it over the cable. If Settings › Privacy & Security has no Developer "
332
+ "Mode switch, Setup can show it."))
333
+ else:
334
+ checks.append(check("developer_mode", "Developer Mode", "unknown", "After the first build",
335
+ "The switch appears on your iPhone once Xcode has seen it. Setup asks when it's time."))
336
+ return checks
337
+
338
+ def state(self):
339
+ manager, config = self.manager, self.runtime.config
340
+ # Xcode's checks and the USB phones are independent: read them together. System Information
341
+ # (a second of system_profiler) is asked only when libimobiledevice can't see a phone:
342
+ # it isn't installed, or it lists nothing.
343
+ tools_read = READS.submit(manager.tools)
344
+ devices_read = READS.submit(manager.devices)
345
+ tools = tools_read.result()
346
+ usb_read = None
347
+ if not tools["idevice_id"] and hasattr(manager, "usb_iphones"):
348
+ usb_read = READS.submit(manager.usb_iphones)
349
+ devices = devices_read.result() if tools["idevice_id"] else []
350
+ if usb_read is None and not devices and hasattr(manager, "usb_iphones"):
351
+ usb_read = READS.submit(manager.usb_iphones)
352
+ try:
353
+ usb = usb_read.result() if usb_read is not None else None
354
+ except Exception:
355
+ usb = None
356
+ device = manager.device(devices)
357
+ if device is not None:
358
+ device = {**device, "developerMode": self.developer_mode(device)}
359
+ settings = manager.settings()
360
+ chosen = settings.get("udid")
361
+ chosen_name = f"“{settings['device_name']}”" if settings.get("device_name") else "Your iPhone"
362
+ chosen_in_sentence = chosen_name if settings.get("device_name") else "your iPhone"
363
+ built_for = settings.get("built_for", chosen)
364
+ build = dict(manager.build)
365
+ runner = dict(manager.runner_state())
366
+ stream = self.video() if self.video is not None else self.runtime.video
367
+ video = stream.status() if hasattr(stream, "status") else {}
368
+ has_openai = bool(openai_key()[0])
369
+ has_anthropic = bool(anthropic_key()[0]) # Smart runs on Claude with it (engines.smart_model)
370
+ has_plan = plan_ready() # a plan or provider from Settings › Models, with its sign-in or key
371
+ # Else a coding agent signed in on this Mac: typed tasks go to it (the composer's default, lib/agents).
372
+ agents_on = [] if has_openai or has_anthropic or has_plan else agents_ready()
373
+ # The account Smart plans on (engines.smart_model): with both keys, Claude's unless OpenAI was chosen.
374
+ from . import engines
375
+ claude = has_anthropic and (engines.model_provider(engines.smart_model()) == "anthropic" or not has_openai)
376
+ team_signing = self.team_signing()
377
+ # Without Xcode, the helper is signed here and installed by pymobiledevice3: Xcode and git aren't needed.
378
+ tools_ok = all(tools[name] for name in (("iproxy", "idevice_id") if team_signing else
379
+ ("xcodebuild", "iproxy", "idevice_id", "git")))
380
+ tools_detail, tools_commands, tool_states = self.tools_step(tools)
381
+ phone_ok = bool(device and device["trusted"])
382
+ built = manager.built()
383
+ expired = manager.expired()
384
+ expires_at = manager.signature_expiry()
385
+ running = runner["state"] == "running"
386
+ photos = phone_ready.photos_status(manager.wda_url) if running and phone_ready.enabled() else None
387
+ building = build["state"] == "running"
388
+ first_build = build.get("first", not built and not expires_at)
389
+ build.update(expires_at=round(expires_at * 1000) if expires_at else None, expired=expired,
390
+ started_at=round(build["started_at"] * 1000) if build.get("started_at") else None,
391
+ first=bool(first_build), renewal=bool(build.get("renewal")), problem=build.get("problem"),
392
+ team=build.get("team") or settings.get("team"))
393
+ # The runner's signature and its automatic renewal (runner_renewal.py), for Settings › iPhone.
394
+ runner["expires_at"] = build["expires_at"]
395
+ runner["renewal"] = self.renewal.status() if self.renewal is not None else None
396
+ if runner.get("error") and hasattr(manager, "runner_problem"):
397
+ runner["problem"] = manager.runner_problem()
398
+
399
+ def step(id_, title, done, detail, action=None, blocked_by=None, working=False, **extra):
400
+ # blocked_by names the step this one waits on; None when it can start now.
401
+ waiting = None if done or working else blocked_by
402
+ state = "done" if done else "working" if working else "blocked" if waiting else "todo"
403
+ return {"id": id_, "title": title, "state": state, "detail": detail, "action": action,
404
+ "blocked_by": waiting, **extra}
405
+
406
+ # Tools come first: the Xcode download takes longest, and the key can be added while it runs.
407
+ steps = [
408
+ step("tools", "Get your Mac ready", tools_ok, tools_detail, commands=tools_commands),
409
+ step("api_key", "Connect your AI account", has_openai or has_anthropic or has_plan or bool(agents_on),
410
+ agent_detail(agents_on) if agents_on else
411
+ KEY_DETAIL["anthropic" if claude else "openai" if has_openai else "plan" if has_plan else "none"],
412
+ "save_key"),
413
+ step("phone", "Connect your iPhone", phone_ok,
414
+ f"“{device['name']}” is connected and trusts this Mac." if phone_ok else
415
+ "Unlock your iPhone and tap Trust on it." if device else
416
+ f"{chosen_name} isn't connected. Plug it in with a cable and unlock it." if chosen else
417
+ "Choose which iPhone Mobster should use." if len(devices) > 1 else
418
+ "Plug your iPhone into this Mac with a cable and unlock it.",
419
+ "choose_phone" if len(devices) > 1 or chosen and not device else None,
420
+ blocked_by=None if tools_ok else "tools"),
421
+ step("wda_build", "Install Mobster's helper on your iPhone", built,
422
+ "Mobster's helper is installed on your iPhone." if built else
423
+ (FIRST_BUILD if first_build else REBUILD) if building else
424
+ build["error"] or (EXPIRED_DETAIL if expired else None) or
425
+ (f"Mobster's helper was installed on another iPhone. Install it again on {chosen_in_sentence}."
426
+ if built_for and chosen and built_for != chosen else None) or
427
+ "Xcode signs Mobster's helper with your Apple Account and installs it on your iPhone. "
428
+ "The first install takes about 2 minutes.",
429
+ "build", blocked_by=None if phone_ok else "phone", working=building,
430
+ problem=build.get("problem") if build["state"] == "failed" and not building else None),
431
+ step("wda_running", "Start Mobster's helper", running,
432
+ (runner["error"] or "Mobster is connected to your iPhone.") if running else
433
+ runner["error"] or "Mobster's helper keeps the connection to your iPhone open while Mobster runs.",
434
+ "start", blocked_by="phone" if not phone_ok else None if built else "wda_build",
435
+ working=runner["state"] == "starting",
436
+ problem=(runner.get("problem") if runner["state"] in ("failed", "starting") and runner.get("error")
437
+ else None)),
438
+ step("live", "Allow Mobster to tap and type", bool(config.enable_live),
439
+ "Tasks can tap, scroll and type on your iPhone." if config.enable_live else
440
+ "Turn this on so tasks can tap, scroll and type.", "enable_live"),
441
+ *([step("photos", "Let Mobster add to Photos", photos in phone_ready.GRANTED, PHOTOS_DETAIL[photos],
442
+ "allow_photos" if photos == "not_determined" else None,
443
+ blocked_by=None if running else "wda_running", working=running and photos is None)]
444
+ if phone_ready.enabled() else []),
445
+ step("video", "See your iPhone live", video.get("capturedAt") is not None,
446
+ "Your iPhone's screen is streaming." if video.get("capturedAt") else
447
+ "A live picture of your screen confirms everything works.",
448
+ blocked_by=None if running else "wda_running"),
449
+ ]
450
+ teams = [{**team, "label": signing.team_label(team)} for team in manager.teams()]
451
+ # Photos is for the content queue only: the rest of setup is complete without it.
452
+ return {"complete": all(item["state"] == "done" for item in steps if item["id"] != "photos"), "steps": steps,
453
+ "device": device, "devices": devices, "chosen": chosen, "teams": teams,
454
+ "tools": tool_states, "build": build, "runner": runner,
455
+ "keys": {"openai": has_openai, "anthropic": has_anthropic},
456
+ "preflight": self.preflight(tools, devices, device, usb, without_xcode=team_signing is not None),
457
+ **({"teamSigning": team_signing.status()} if team_signing is not None else {}),
458
+ "wifi": self.wifi_step(device, built, running)}
459
+
460
+ def wifi_step(self, device, built, running):
461
+ """The Wi-Fi step of this phone's setup (wireless/onboarding.py): off unless Wi-Fi for iPhones is on."""
462
+ from .wireless import onboarding
463
+ facts = onboarding.Facts(attached=device is not None, trusted=bool(device and device.get("trusted")),
464
+ built=built, running=running, ios=(device or {}).get("ios"))
465
+ return onboarding.step(self._wifi_udid(device), getattr(self.manager, "data_dir", None), facts)
466
+
467
+ def answer_wifi(self, device, yes):
468
+ """`mobster setup`'s answer to "use this iPhone over Wi-Fi?" (state()'s ``wifi`` is ``asking``). LookupError
469
+ when Mobster isn't asking about it now."""
470
+ from .wireless import onboarding
471
+ onboarding.answer(self._wifi_udid(device), yes)
472
+
473
+ def _wifi_udid(self, device):
474
+ return (getattr(self.manager, "pinned", None) or (device or {}).get("udid")
475
+ or self.manager.settings().get("udid"))
476
+
477
+ def refresh(self):
478
+ """Check the tools, teams and Developer Mode again now ("Check again")."""
479
+ self.cache.clear()
480
+ if self._team_signing is not None:
481
+ self._team_signing.refresh()
482
+ if hasattr(self.manager, "refresh"):
483
+ self.manager.refresh()
484
+
485
+ def install_tools(self):
486
+ """Setup's Install: the iPhone connection tools, with Homebrew, in the background. Status reads its progress."""
487
+ tools = self.manager.tools()
488
+ if tools["iproxy"] and tools["idevice_id"]:
489
+ return
490
+ brew = next((path for directory in BREW_DIRS if (path := mac_tools.executable(directory, "brew"))), None)
491
+ if not brew:
492
+ raise LookupError("Mobster needs Homebrew to install these on this Mac. Help › For developers shows how.")
493
+ self.tools_install.start(brew, on_done=self.refresh)
494
+
495
+ def open_xcode(self, opener=subprocess.Popen):
496
+ """Setup's Open Xcode: Xcode finishes installing its components the first time it opens."""
497
+ app = (self.manager.tools().get("xcode") or {}).get("app")
498
+ if not app:
499
+ raise LookupError("Xcode isn't installed yet. Get it from the App Store first.")
500
+ opener(["/usr/bin/open", app], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
501
+ self.refresh()
502
+
503
+ def allow_photos(self, device=None):
504
+ """Setup's Allow for Photos on ``device`` (None: the first phone): iOS shows its Photos question on the
505
+ iPhone and the person answers there, for up to phone_io.photos.AUTHORIZE_WAIT_S seconds. Nothing is saved or
506
+ pressed.
507
+ LookupError (its sentence is the answer) when the check is off, a task has the phone, or the helper can't."""
508
+ if not phone_ready.enabled():
509
+ raise LookupError("Setup's Photos step is off in this Mobster (MOBSTER_QUEUE_PREFLIGHT).")
510
+ from .phone_io import PhoneIOError
511
+ from .phone_io.photos import authorize
512
+ from .phone_io.transfer import phone_from_runtime
513
+ try:
514
+ phone = phone_from_runtime(self.runtime, device)
515
+ if phone.runner is None:
516
+ raise PhoneIOError("Mobster's helper isn't set up on this iPhone yet.", "no_device")
517
+ with phone.runner() as request:
518
+ authorize(request, name=phone.name, activate=lambda bundle: request(
519
+ "POST", "/wda/apps/activate", {"bundleId": bundle}, 15))
520
+ except PhoneIOError as error:
521
+ raise LookupError(str(error)) from None
522
+
523
+ def set_live(self, enabled):
524
+ if type(enabled) is not bool:
525
+ raise ValueError("enabled must be true or false")
526
+ update_env_file(self.env_file, {"MOBSTER_ENABLE_LIVE": "1" if enabled else "0"})
527
+ self.runtime.config.enable_live = enabled
mobile_agent/share.py ADDED
@@ -0,0 +1,104 @@
1
+ """Share links (docs/results.mdx › Share a result): one task's result as a page anyone with the link can read, at
2
+ mobster.dev/s/<id>, until its owner turns it off or it expires."""
3
+
4
+ import http.client
5
+ import json
6
+ import os
7
+ import socket
8
+ import ssl
9
+ from urllib.parse import urlsplit
10
+
11
+ from . import artifacts, pinning
12
+ from .transport import user_agent
13
+
14
+ ACCOUNT_API = "https://api.mobster.dev"
15
+ TIMEOUT = 20.0
16
+ EXPIRY_DAYS = (1, 7, 30, 90)
17
+ MAX_PAGE_BYTES = 4_000_000
18
+
19
+
20
+ class ShareError(Exception):
21
+ """The account service refused or couldn't be reached."""
22
+
23
+ def __init__(self, code, status, message):
24
+ super().__init__(message)
25
+ self.code, self.status = code, status
26
+
27
+
28
+ def api_base():
29
+ """The account service: MOBSTER_ACCOUNT_API (development: http on this Mac) or https://api.mobster.dev."""
30
+ value = (os.environ.get("MOBSTER_ACCOUNT_API") or ACCOUNT_API).rstrip("/")
31
+ parts = urlsplit(value)
32
+ if parts.scheme == "https" or parts.scheme == "http" and parts.hostname in ("127.0.0.1", "localhost"):
33
+ return value
34
+ return ACCOUNT_API
35
+
36
+
37
+ def page(run, *, frames=None, people="mask", screens=False, title=None):
38
+ """The page as it's shared: see the module docstring."""
39
+ return artifacts.document(run, frames=frames, people=people, screens=screens, share=True, title=title)
40
+
41
+
42
+ def call(method, path, token, body=None, timeout=TIMEOUT, limit=256_000):
43
+ """(HTTP status, the JSON object answered or {}) of one request to the account service, ``token`` its Bearer, with
44
+ ``body`` as JSON (bytes go as they are)."""
45
+ base = api_base()
46
+ parts = urlsplit(base)
47
+ connection = (pinning.PinnedHTTPSConnection(parts.hostname, parts.port or 443, timeout=timeout)
48
+ if parts.scheme == "https"
49
+ else http.client.HTTPConnection(parts.hostname, parts.port or 80, timeout=timeout))
50
+ headers = {"Authorization": f"Bearer {token}", "Accept": "application/json", "User-Agent": user_agent()}
51
+ payload = None
52
+ if body is not None:
53
+ payload = body if isinstance(body, bytes) else json.dumps(body).encode()
54
+ headers["Content-Type"] = "application/json"
55
+ try:
56
+ connection.request(method, f"{parts.path.rstrip('/')}{path}", body=payload, headers=headers)
57
+ response = connection.getresponse()
58
+ data = response.read(limit)
59
+ except pinning.PinMismatch:
60
+ raise ShareError("untrusted_server", 503, "Mobster couldn't verify its server, so it didn't connect.") from None
61
+ except (OSError, socket.timeout, http.client.HTTPException, ssl.SSLError):
62
+ raise ShareError("unreachable", 503, "Mobster couldn't reach its account service. Check your connection and "
63
+ "try again.") from None
64
+ finally:
65
+ headers = None # noqa: F841 -- the token's only other reference goes with this frame
66
+ connection.close()
67
+ try:
68
+ value = json.loads(data) if data else {}
69
+ except ValueError:
70
+ value = {}
71
+ return response.status, value if isinstance(value, dict) else {}
72
+
73
+
74
+ def _request(method, path, token, body=None, timeout=TIMEOUT):
75
+ status, value = call(method, path, token, body, timeout)
76
+ if 200 <= status < 300:
77
+ return value
78
+ message = value.get("message") if isinstance(value.get("message"), str) else None
79
+ code = value.get("error") if isinstance(value.get("error"), str) else "error"
80
+ if status == 401:
81
+ raise ShareError("session", 401, "Sign in to Mobster again to share.")
82
+ if status in (400, 403, 404, 409, 413):
83
+ raise ShareError(code, status, message or "Mobster's account service refused that link.")
84
+ raise ShareError("error", 502,
85
+ message or "Mobster's account service couldn't make the link. Try again in a minute.")
86
+
87
+
88
+ def create(token, *, title, html, expires_days=30):
89
+ """The new link: {id, url, title, createdAt, expiresAt, live, bytes}."""
90
+ if expires_days is not None and expires_days not in EXPIRY_DAYS:
91
+ raise ValueError("expiresDays is 1, 7, 30, 90 or null")
92
+ if len(html.encode("utf-8")) > MAX_PAGE_BYTES:
93
+ raise ShareError("too_large", 413, "That page is over 4 MB. Leave out the screens, or share fewer cards.")
94
+ value = _request("POST", "/api/shares", token, {"title": title, "html": html, "expiresDays": expires_days})
95
+ share = value.get("share")
96
+ if not isinstance(share, dict) or not isinstance(share.get("id"), str) or not isinstance(share.get("url"), str):
97
+ raise ShareError("error", 502, "Mobster's account service answered without a link. Try again.")
98
+ return share
99
+
100
+
101
+ def revoke(token, share_id):
102
+ """Turns a link off; its page is deleted at once. The link as it is now."""
103
+ value = _request("DELETE", f"/api/shares/{share_id}", token)
104
+ return value.get("share") if isinstance(value.get("share"), dict) else {"id": share_id, "live": False}