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,196 @@
1
+ """`mobster ci`'s parser and output (ci.py does the work)."""
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ import unicodedata
7
+ from pathlib import Path
8
+
9
+ from . import DevError, main, say, subcommands
10
+ from .ci import HOSTED_HOSTS
11
+
12
+ MARKS = {"ok": "✓", "warn": "!", "fail": "✗", "skip": "-"}
13
+
14
+
15
+ def add_arguments(parser, helpers):
16
+ add = subcommands(parser, "ci_command")
17
+ init = add("init", help="write the CI workflow for this repository (GitHub Actions, GitLab CI or a hosted Mac)",
18
+ description="Write the workflow that runs `mobster ci run` on every pull request or "
19
+ "merge request: on a Mac of yours as a self-hosted runner, or with --hosted "
20
+ "on a GitHub-hosted macOS runner. --host xcode-cloud, codemagic, bitrise or "
21
+ "circleci writes that service's file, which runs on its hosted Macs.")
22
+ init.add_argument("--host", choices=("github", "gitlab", *HOSTED_HOSTS),
23
+ help="where the repository lives, or the CI service to write for (default: from the origin "
24
+ "remote)")
25
+ init.add_argument("--hosted", action="store_true",
26
+ help="run on a GitHub-hosted macOS runner and sign in with a CI token, with no Mac of yours "
27
+ "(no iPhone either)")
28
+ init.add_argument("--device", action="append", metavar="NAME",
29
+ help="also test on this iPhone, plugged into the runner; repeatable (the runner must list each "
30
+ "in MOBSTER_RUNNER_DEVICES)")
31
+ init.add_argument("--testflight", action="append", metavar="BUNDLE",
32
+ help="with --device: your app's TestFlight build on the phone; repeatable")
33
+ init.add_argument("--force", action="store_true", help="replace the workflow file if it exists")
34
+ init.add_argument("--print", dest="print_only", action="store_true",
35
+ help="print the workflow instead of writing it")
36
+
37
+ doctor = add("doctor", help="check that this Mac is ready to be a CI runner",
38
+ description="Check this Mac as a CI runner: Xcode, a simulator runtime, Mobster for "
39
+ "Mac and its plan, system sleep, automatic login, the runner service, "
40
+ "free disk and the phones the runner allows. Reads only.")
41
+ doctor.add_argument("--json", action="store_true", help="print the rows as JSON")
42
+ doctor.add_argument("--hosted", action="store_true",
43
+ help="check for a hosted runner: skip the rows about keeping a Mac running and check the CI "
44
+ "token (automatic inside a hosted job)")
45
+
46
+ run = add("run", help="test the PR or MR this job builds; the report goes to .mobster/ci",
47
+ description="Run `mobster test pr` for the pull request or merge request this CI job "
48
+ "builds, with a screen recording of each simulator run. The suite's report, "
49
+ "JUnit file, frames, GIFs and videos go to .mobster/ci for the job's "
50
+ "artifacts. Works the same in a terminal.",
51
+ epilog="exit codes: 0 passed, 1 failed, 2 needs review, 3 couldn't run\n\n"
52
+ "examples:\n mobster ci run\n mobster ci run --device \"Test iPhone\"\n"
53
+ " mobster ci run --device \"Test iPhone\" --device \"Lab iPhone\" -- --spread\n"
54
+ " mobster ci run -- --retries 2 --parallel 2")
55
+ run.add_argument("--base", metavar="REF",
56
+ help="the branch the PR merges into (default: the job's target branch, else origin/main)")
57
+ run.add_argument("--sim", action="append", metavar="TYPE[@RUNTIME]",
58
+ help="a simulator device type to run on; repeatable (default: each check's own)")
59
+ run.add_argument("--device", action="append", metavar="NAME",
60
+ help="an iPhone plugged into this Mac; repeatable. In a CI job the runner must list each in "
61
+ "MOBSTER_RUNNER_DEVICES, or nothing runs")
62
+ run.add_argument("--testflight", action="append", metavar="BUNDLE",
63
+ help="with --device: your app's TestFlight build on the phone; repeatable")
64
+ run.add_argument("--no-video", action="store_true", help="don't record the simulator's screen")
65
+ run.add_argument("extra", nargs="*", metavar="TEST_FLAG",
66
+ help="more `mobster test pr` flags, after a --: mobster ci run -- --retries 2")
67
+
68
+ comment = add("comment", help="keep one comment on the PR or MR with the verdict and repro steps",
69
+ description="Post the verdict in .mobster/ci as one comment on the pull request "
70
+ "(GITHUB_TOKEN) or merge request (MOBSTER_GITLAB_TOKEN), updated in place "
71
+ "on every push: each failure's repro steps, frame, GIF, video and the "
72
+ "command that runs it again. Never fails the job.")
73
+ comment.add_argument("--artifact-url", metavar="URL",
74
+ help="the uploaded report's link (default: $MOBSTER_ARTIFACT_URL, else the job's page)")
75
+ comment.add_argument("--dry-run", action="store_true", help="print the comment and post nothing")
76
+
77
+
78
+ def run(args, *, cwd=None, environ=None):
79
+ environ = os.environ if environ is None else environ
80
+ handlers = {"init": _init, "doctor": _doctor, "run": _run, "comment": _comment}
81
+ return main("ci", handlers, args, cwd=Path(cwd or Path.cwd()), environ=environ)
82
+
83
+
84
+ def _phones(args):
85
+ """(--device, --testflight) as tuples; --testflight goes with --device (a TestFlight build is on an iPhone)."""
86
+ devices, testflight = tuple(args.device or ()), tuple(args.testflight or ())
87
+ for flag, value in [*(("--device", name) for name in devices), *(("--testflight", app) for app in testflight)]:
88
+ # a line break would end the run line of the workflow ci init writes
89
+ if any(unicodedata.category(char) in ("Cc", "Zl", "Zp") for char in value):
90
+ raise DevError(f"{flag} {value!r} holds a line break, a tab or another control character, so Mobster did "
91
+ "nothing.", "Remove it: no iPhone's name or app's bundle ID has one.")
92
+ if testflight and not devices:
93
+ raise DevError("--testflight goes with --device: a TestFlight build is on an iPhone, not a simulator.")
94
+ return devices, testflight
95
+
96
+
97
+ def _init(args, cwd, environ):
98
+ from . import ci
99
+ root = ci.repo_root(cwd)
100
+ host = args.host
101
+ if host is None:
102
+ code, url = ci._git(root, "remote", "get-url", "origin")
103
+ host = ci.remote_host(url) if code == 0 else None
104
+ devices, testflight = _phones(args)
105
+ if args.print_only:
106
+ say(ci.workflow(host, devices=devices, testflight=testflight, hosted=args.hosted)[1], sys.stdout)
107
+ return 0
108
+ hosted = args.hosted or host in HOSTED_HOSTS
109
+ target, notes = ci.init(root, host=host, devices=devices, testflight=testflight, force=args.force,
110
+ hosted=args.hosted)
111
+ if hosted:
112
+ from . import ci_hosts
113
+ lines = [f"Wrote {target.relative_to(root)}.", *notes, "", "Next:",
114
+ *(f" {number}. {step}" for number, step in enumerate(ci_hosts.NEXT[host], 1)),
115
+ "", "On the runner, `mobster ci doctor` checks this runner before the run."]
116
+ say("\n".join(lines))
117
+ return 0
118
+ github = host == "github"
119
+ steps = ["On the Mac that runs the checks: mobster ci doctor",
120
+ ("Register it as a self-hosted runner with the label mobster" if github else
121
+ "Register it as a GitLab runner (shell executor) with the tag mobster") + f": {ci.DOCS}/ci/mac-runner",
122
+ f"Add ANTHROPIC_API_KEY as a {'repository secret' if github else 'masked CI/CD variable'}, for checks "
123
+ "with steps."]
124
+ if not github:
125
+ steps.append("Add MOBSTER_GITLAB_TOKEN, a project access token with the api scope, for the MR comment.")
126
+ if devices:
127
+ steps.append(f"On the runner, allow the phone{'' if len(devices) == 1 else 's'}: "
128
+ f"MOBSTER_RUNNER_DEVICES={','.join(devices)}")
129
+ steps.append(f"Commit the file and open a {'pull' if github else 'merge'} request.")
130
+ lines = [f"Wrote {target.relative_to(root)}.", *notes, "", "Next:",
131
+ *(f" {number}. {step}" for number, step in enumerate(steps, 1))]
132
+ if not (root / ".mobster" / "checks").is_dir():
133
+ lines += ["", "This project has no checks yet. `mobster test pr` drafts one for each screen a PR changes, "
134
+ "and `mobster test record` turns a finished task into one."]
135
+ say("\n".join(lines))
136
+ return 0
137
+
138
+
139
+ def _doctor(args, cwd, environ):
140
+ from . import ci
141
+ rows = ci.doctor(environ=environ, hosted=True if args.hosted else None)
142
+ failed = any(row.status == "fail" for row in rows)
143
+ if args.json:
144
+ say(json.dumps({"ok": not failed, "rows": [row.public() for row in rows]}, ensure_ascii=False))
145
+ return 1 if failed else 0
146
+ width = max(len(row.name) for row in rows)
147
+ lines = []
148
+ for row in rows:
149
+ lines.append(f"{MARKS[row.status]} {row.name.ljust(width)} {row.detail}")
150
+ if row.fix and row.status != "ok":
151
+ lines.append(f" {' ' * width} {row.fix}")
152
+ if failed:
153
+ lines += ["", "Fix the rows marked ✗, then run `mobster ci doctor` again."]
154
+ else:
155
+ lines += ["", "This Mac is ready to run Mobster's checks. Next, `mobster sim doctor --fix` prepares a "
156
+ "simulator, so the first job doesn't wait for one."]
157
+ if args.hosted or ci.is_hosted(environ):
158
+ lines[-1:] = ["This runner is ready to run Mobster's checks."]
159
+ say("\n".join(lines))
160
+ return 1 if failed else 0
161
+
162
+
163
+ def _run(args, cwd, environ):
164
+ from ..devtools import ProgressLines
165
+ from . import ci
166
+ extra = list(args.extra or ())
167
+ devices, testflight = _phones(args)
168
+ root = ci.repo_root(cwd)
169
+ progress = ProgressLines(sys.stderr)
170
+ code, doc = ci.run_checks(root, devices=devices, testflight=testflight, sims=tuple(args.sim or ()),
171
+ video=not args.no_video, base=args.base, extra=extra, environ=environ,
172
+ progress=progress)
173
+ progress.end()
174
+ say(f"{doc['headline']}. Report: {ci.CI_DIR}/")
175
+ return code
176
+
177
+
178
+ def _comment(args, cwd, environ):
179
+ from . import ci
180
+ root = ci.repo_root(cwd)
181
+ body = ci.comment_markdown(root, environ, args.artifact_url)
182
+ target = root / ci.CI_DIR / "comment.md"
183
+ try:
184
+ target.write_text(body, encoding="utf-8")
185
+ except OSError:
186
+ pass
187
+ if args.dry_run:
188
+ say(body)
189
+ return 0
190
+ ci.job_summary(body, environ)
191
+ if ci.host_of(environ) is None:
192
+ say(body)
193
+ say(f"Not in a CI job, so Mobster posted nothing. The comment is in {ci.CI_DIR}/comment.md.", sys.stderr)
194
+ return 0
195
+ say(ci.post(body, environ), sys.stderr)
196
+ return 0
@@ -0,0 +1,194 @@
1
+ """The files `mobster ci init --hosted` and `--host xcode-cloud|codemagic|bitrise|circleci` write.
2
+
3
+ Every file installs uv, prepares a simulator and runs the pinned standalone Mobster CLI.
4
+ None of them asks for a phone: a hosted runner has none."""
5
+
6
+ from .. import __version__
7
+
8
+ DOCS = "https://docs.mobster.dev"
9
+ INSTALL = "brew install uv"
10
+ COMMAND = f"uvx --python 3.12 --from mobster-cli=={__version__} mobster"
11
+
12
+ GITHUB_HOSTED = """# Mobster checks on every pull request, on a GitHub-hosted Mac (@DOCS@/ci/github-hosted).
13
+ # Add ANTHROPIC_API_KEY as a repository secret for checks with steps.
14
+ name: mobster
15
+ on: pull_request
16
+ permissions:
17
+ contents: read
18
+ pull-requests: write # the one comment Mobster keeps up to date
19
+ concurrency:
20
+ group: mobster-${{ github.event.pull_request.number }}
21
+ cancel-in-progress: true
22
+ jobs:
23
+ checks:
24
+ runs-on: macos-15
25
+ timeout-minutes: 60
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ with:
29
+ fetch-depth: 0 # the diff needs the base branch
30
+ - name: Install Mobster
31
+ id: mobster
32
+ run: |
33
+ @INSTALL@
34
+ echo "version=@VERSION@" >> "$GITHUB_OUTPUT"
35
+ echo "xcode=$(xcodebuild -version | head -1 | tr -d ' ')" >> "$GITHUB_OUTPUT"
36
+ - name: Cache WebDriverAgent
37
+ uses: actions/cache@v4
38
+ with:
39
+ path: ~/Library/Application Support/app.mobster.desktop/wda
40
+ key: mobster-wda-${{ runner.arch }}-${{ steps.mobster.outputs.xcode }}-${{ steps.mobster.outputs.version }}
41
+ restore-keys: |
42
+ mobster-wda-${{ runner.arch }}-${{ steps.mobster.outputs.xcode }}-
43
+ - name: Prepare a simulator
44
+ run: |
45
+ @COMMAND@ sim doctor --fix
46
+ - name: Test what this PR changed
47
+ run: |
48
+ @COMMAND@ ci run
49
+ env:
50
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # only checks with steps use it
51
+ - name: Upload the report
52
+ id: upload
53
+ if: always()
54
+ uses: actions/upload-artifact@v4
55
+ with:
56
+ name: mobster-report
57
+ path: .mobster/ci/
58
+ include-hidden-files: true
59
+ retention-days: 14
60
+ if-no-files-found: ignore
61
+ - name: Comment on the PR
62
+ if: always()
63
+ run: |
64
+ @COMMAND@ ci comment
65
+ env:
66
+ GITHUB_TOKEN: ${{ github.token }}
67
+ MOBSTER_ARTIFACT_URL: ${{ steps.upload.outputs.artifact-url }}
68
+ """
69
+
70
+ XCODE_CLOUD = """#!/bin/sh
71
+ # Mobster checks after Xcode Cloud builds a pull request (@DOCS@/ci/xcode-cloud).
72
+ # Add ANTHROPIC_API_KEY as a secret environment variable for checks with steps.
73
+ set -eu
74
+ if [ -z "${CI_PULL_REQUEST_NUMBER:-}" ]; then
75
+ echo "This build isn't for a pull request, so Mobster tested nothing."
76
+ exit 0
77
+ fi
78
+ @INSTALL@
79
+ @COMMAND@ sim doctor --fix
80
+ status=0
81
+ @COMMAND@ ci run || status=$?
82
+ @COMMAND@ ci comment || true # Xcode Cloud has no comment token; the verdict is in this log
83
+ exit "$status"
84
+ """
85
+
86
+ CODEMAGIC = """# Mobster checks on every pull request, on a Codemagic Mac (@DOCS@/ci/codemagic).
87
+ # Put ANTHROPIC_API_KEY in a variable group named mobster for checks with steps.
88
+ workflows:
89
+ mobster:
90
+ name: Mobster checks
91
+ instance_type: mac_mini_m2
92
+ max_build_duration: 60
93
+ triggering:
94
+ events:
95
+ - pull_request
96
+ environment:
97
+ groups:
98
+ - mobster
99
+ vars:
100
+ MOBSTER_BASE_BRANCH: main # the branch your pull requests merge into
101
+ scripts:
102
+ - name: Install Mobster
103
+ script: @INSTALL@
104
+ - name: Prepare a simulator
105
+ script: @COMMAND@ sim doctor --fix
106
+ - name: Test what this PR changed
107
+ script: @COMMAND@ ci run
108
+ artifacts:
109
+ - .mobster/ci/**
110
+ """
111
+
112
+ BITRISE = """# Mobster checks on every pull request, on a Bitrise Mac (@DOCS@/ci/bitrise).
113
+ # Add ANTHROPIC_API_KEY as a Secret for checks with steps. Trigger the workflow on pull requests.
114
+ format_version: "13"
115
+ default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
116
+ workflows:
117
+ mobster:
118
+ steps:
119
+ - git-clone@8: {}
120
+ - script@1:
121
+ title: Install Mobster
122
+ inputs:
123
+ - content: |-
124
+ #!/usr/bin/env bash
125
+ set -euo pipefail
126
+ @INSTALL@
127
+ @COMMAND@ sim doctor --fix
128
+ - script@1:
129
+ title: Test what this PR changed
130
+ inputs:
131
+ - content: |-
132
+ #!/usr/bin/env bash
133
+ set -uo pipefail
134
+ @COMMAND@ ci run
135
+ - deploy-to-bitrise-io@2:
136
+ is_always_run: true
137
+ inputs:
138
+ - deploy_path: .mobster/ci
139
+ """
140
+
141
+ CIRCLECI = """# Mobster checks on every pull request, on a CircleCI Mac (@DOCS@/ci/circleci).
142
+ # Add ANTHROPIC_API_KEY for checks with steps. Pick the Xcode version from CircleCI's macOS list.
143
+ version: 2.1
144
+ jobs:
145
+ mobster:
146
+ macos:
147
+ xcode: "16.4.0"
148
+ resource_class: m4pro.medium
149
+ environment:
150
+ MOBSTER_BASE_BRANCH: main # the branch your pull requests merge into
151
+ steps:
152
+ - checkout
153
+ - run:
154
+ name: Install Mobster
155
+ command: |
156
+ @INSTALL@
157
+ @COMMAND@ sim doctor --fix
158
+ - run:
159
+ name: Test what this PR changed
160
+ command: |
161
+ if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then echo "This build isn't for a pull request."; exit 0; fi
162
+ @COMMAND@ ci run
163
+ - store_artifacts:
164
+ path: .mobster/ci
165
+ workflows:
166
+ mobster:
167
+ jobs:
168
+ - mobster
169
+ """
170
+
171
+ TEMPLATES = {"github": GITHUB_HOSTED, "xcode-cloud": XCODE_CLOUD, "codemagic": CODEMAGIC, "bitrise": BITRISE,
172
+ "circleci": CIRCLECI}
173
+
174
+
175
+ def render(host):
176
+ """The file for ``host`` ("github" means GitHub's hosted macOS runner)."""
177
+ return (TEMPLATES[host].replace("@DOCS@", DOCS).replace("@COMMAND@", COMMAND)
178
+ .replace("@INSTALL@", INSTALL).replace("@VERSION@", __version__))
179
+
180
+
181
+ NEXT = {
182
+ "github": ["Add ANTHROPIC_API_KEY as a repository secret, for checks with steps.",
183
+ "Commit the file and open a pull request."],
184
+ "xcode-cloud": ["Add ANTHROPIC_API_KEY as a secret environment variable for checks with steps.",
185
+ "Run the script after the build: it is `ci_scripts/ci_post_xcodebuild.sh`, and Xcode Cloud finds "
186
+ "it by name.",
187
+ "Commit the file and open a pull request."],
188
+ "codemagic": ["Put ANTHROPIC_API_KEY in a Codemagic variable group named mobster for checks with steps.",
189
+ "Commit the file and open a pull request."],
190
+ "bitrise": ["Add ANTHROPIC_API_KEY as a Secret for checks with steps, and trigger the mobster workflow on pull "
191
+ "requests.", "Commit the file and open a pull request."],
192
+ "circleci": ["Add ANTHROPIC_API_KEY as a project environment variable for checks with steps.",
193
+ "Commit the file and open a pull request."],
194
+ }
@@ -0,0 +1,197 @@
1
+ """ES256 signing for App Store Connect's JSON Web Tokens in plain Python: the agent ships no crypto library."""
2
+
3
+ import base64
4
+ import hashlib
5
+ import hmac
6
+ import json
7
+
8
+ P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
9
+ A = P - 3
10
+ B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
11
+ N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
12
+ G = (0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,
13
+ 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5)
14
+ EC_PUBLIC_KEY = bytes.fromhex("2a8648ce3d0201") # 1.2.840.10045.2.1
15
+ PRIME256V1 = bytes.fromhex("2a8648ce3d030107") # 1.2.840.10045.3.1.7
16
+ # The PEM lines around a .p8 key, built from the label so a secret scanner never reads the source as a key.
17
+ P8_LABEL = "PRIVATE KEY"
18
+ P8_BEGIN = f"-----BEGIN {P8_LABEL}-----"
19
+ P8_END = f"-----END {P8_LABEL}-----"
20
+
21
+
22
+ class P8Error(ValueError):
23
+ """The text isn't a P-256 private key in the .p8 format App Store Connect gives."""
24
+
25
+
26
+ # -- the curve, in Jacobian coordinates ------------------------------------------------------------------------------
27
+
28
+ def _double(point):
29
+ x, y, z = point
30
+ if y == 0 or z == 0:
31
+ return (0, 1, 0)
32
+ ysq = y * y % P
33
+ s = 4 * x * ysq % P
34
+ m = (3 * (x - z * z) * (x + z * z)) % P # a = -3
35
+ nx = (m * m - 2 * s) % P
36
+ ny = (m * (s - nx) - 8 * ysq * ysq) % P
37
+ nz = 2 * y * z % P
38
+ return (nx, ny, nz)
39
+
40
+
41
+ def _add(p, q):
42
+ if p[2] == 0:
43
+ return q
44
+ if q[2] == 0:
45
+ return p
46
+ z1z1, z2z2 = p[2] * p[2] % P, q[2] * q[2] % P
47
+ u1, u2 = p[0] * z2z2 % P, q[0] * z1z1 % P
48
+ s1, s2 = p[1] * q[2] * z2z2 % P, q[1] * p[2] * z1z1 % P
49
+ if u1 == u2:
50
+ return _double(p) if s1 == s2 else (0, 1, 0)
51
+ h, r = (u2 - u1) % P, (s2 - s1) % P
52
+ hh = h * h % P
53
+ hhh = h * hh % P
54
+ v = u1 * hh % P
55
+ nx = (r * r - hhh - 2 * v) % P
56
+ ny = (r * (v - nx) - s1 * hhh) % P
57
+ nz = h * p[2] * q[2] % P
58
+ return (nx, ny, nz)
59
+
60
+
61
+ # Not constant-time: the key and every signature stay in this process, and only the token goes to Apple.
62
+ def _multiply(k, point=G):
63
+ result, addend = (0, 1, 0), (point[0], point[1], 1)
64
+ while k:
65
+ if k & 1:
66
+ result = _add(result, addend)
67
+ addend = _double(addend)
68
+ k >>= 1
69
+ return result
70
+
71
+
72
+ def _affine(point):
73
+ if point[2] == 0:
74
+ return None
75
+ inverse = pow(point[2], P - 2, P)
76
+ return point[0] * inverse * inverse % P, point[1] * inverse ** 3 % P
77
+
78
+
79
+ def public_key(d):
80
+ return _affine(_multiply(d))
81
+
82
+
83
+ # -- RFC 6979 nonces ----------------------------------------------------------------------------------------------
84
+
85
+ def _bits2int(data):
86
+ return int.from_bytes(data, "big") # qlen = hlen = 256
87
+
88
+
89
+ def _int2octets(value):
90
+ return value.to_bytes(32, "big")
91
+
92
+
93
+ def _nonces(d, digest):
94
+ h1 = _int2octets(_bits2int(digest) % N)
95
+ x = _int2octets(d)
96
+ v, k = b"\x01" * 32, b"\x00" * 32
97
+ k = hmac.new(k, v + b"\x00" + x + h1, hashlib.sha256).digest()
98
+ v = hmac.new(k, v, hashlib.sha256).digest()
99
+ k = hmac.new(k, v + b"\x01" + x + h1, hashlib.sha256).digest()
100
+ v = hmac.new(k, v, hashlib.sha256).digest()
101
+ while True:
102
+ v = hmac.new(k, v, hashlib.sha256).digest()
103
+ candidate = _bits2int(v)
104
+ if 1 <= candidate < N:
105
+ yield candidate
106
+ k = hmac.new(k, v + b"\x00", hashlib.sha256).digest()
107
+ v = hmac.new(k, v, hashlib.sha256).digest()
108
+
109
+
110
+ def sign(d, message):
111
+ """(r, s) over SHA-256(``message``) with the private scalar ``d``."""
112
+ if not 1 <= d < N:
113
+ raise P8Error("The private key is out of range.")
114
+ digest = hashlib.sha256(message).digest()
115
+ e = _bits2int(digest) % N
116
+ for k in _nonces(d, digest):
117
+ point = _affine(_multiply(k))
118
+ r = point[0] % N
119
+ if r == 0:
120
+ continue
121
+ s = pow(k, N - 2, N) * (e + r * d) % N
122
+ if s:
123
+ return r, s
124
+
125
+
126
+ def verify(public, message, signature):
127
+ """Whether ``signature`` (r, s) is ``public``'s (x, y) over SHA-256(``message``)."""
128
+ r, s = signature
129
+ if not (1 <= r < N and 1 <= s < N):
130
+ return False
131
+ e = _bits2int(hashlib.sha256(message).digest()) % N
132
+ w = pow(s, N - 2, N)
133
+ point = _affine(_add(_multiply(e * w % N), _multiply(r * w % N, public)))
134
+ return point is not None and point[0] % N == r
135
+
136
+
137
+ # -- the .p8 file (PKCS #8, RFC 5208, wrapping an RFC 5915 ECPrivateKey) ------------------------------------------
138
+
139
+ def _der(data, at=0):
140
+ """(tag, value, next offset) of the DER element at ``at``."""
141
+ if at + 2 > len(data):
142
+ raise P8Error("The key is cut short.")
143
+ tag, length, at = data[at], data[at + 1], at + 2
144
+ if length & 0x80:
145
+ count = length & 0x7F
146
+ if not 1 <= count <= 3 or at + count > len(data):
147
+ raise P8Error("The key isn't valid DER.")
148
+ length, at = int.from_bytes(data[at:at + count], "big"), at + count
149
+ if at + length > len(data):
150
+ raise P8Error("The key is cut short.")
151
+ return tag, data[at:at + length], at + length
152
+
153
+
154
+ def _children(data):
155
+ out, at = [], 0
156
+ while at < len(data):
157
+ tag, value, at = _der(data, at)
158
+ out.append((tag, value))
159
+ return out
160
+
161
+
162
+ def private_key_from_p8(text):
163
+ """The private scalar of a .p8 key: a PKCS #8 PEM between ``P8_BEGIN`` and ``P8_END``."""
164
+ lines = [line.strip() for line in str(text or "").strip().splitlines() if line.strip()]
165
+ if not lines or lines[0] != P8_BEGIN or lines[-1] != P8_END:
166
+ raise P8Error(f"That isn't a .p8 key: it starts with {P8_BEGIN}.")
167
+ try:
168
+ data = base64.b64decode("".join(lines[1:-1]), validate=True)
169
+ except ValueError:
170
+ raise P8Error("The key's text isn't base64.") from None
171
+ tag, body, _ = _der(data)
172
+ parts = _children(body) if tag == 0x30 else []
173
+ if len(parts) < 3 or parts[0] != (0x02, b"\x00") or parts[1][0] != 0x30 or parts[2][0] != 0x04:
174
+ raise P8Error("That isn't a PKCS #8 private key.")
175
+ algorithm = _children(parts[1][1])
176
+ if len(algorithm) < 2 or algorithm[0] != (0x06, EC_PUBLIC_KEY) or algorithm[1] != (0x06, PRIME256V1):
177
+ raise P8Error("That key isn't on the P-256 curve App Store Connect uses.")
178
+ tag, inner, _ = _der(parts[2][1])
179
+ fields = _children(inner) if tag == 0x30 else []
180
+ if len(fields) < 2 or fields[0] != (0x02, b"\x01") or fields[1][0] != 0x04 or len(fields[1][1]) != 32:
181
+ raise P8Error("That isn't an EC private key.")
182
+ d = int.from_bytes(fields[1][1], "big")
183
+ if not 1 <= d < N:
184
+ raise P8Error("The private key is out of range.")
185
+ return d
186
+
187
+
188
+ def _b64url(data):
189
+ return base64.urlsafe_b64encode(data).rstrip(b"=").decode()
190
+
191
+
192
+ def jwt(header, claims, d):
193
+ """A signed JWT: base64url(header).base64url(claims).base64url(r || s)."""
194
+ signing_input = (_b64url(json.dumps(header, separators=(",", ":")).encode()) + "." +
195
+ _b64url(json.dumps(claims, separators=(",", ":")).encode()))
196
+ r, s = sign(d, signing_input.encode())
197
+ return signing_input + "." + _b64url(r.to_bytes(32, "big") + s.to_bytes(32, "big"))