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