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,193 @@
1
+ """Argument definitions and usage errors for ``mobster test``."""
2
+
3
+ import sys
4
+
5
+ USAGE_EXIT = 3
6
+
7
+
8
+ def add_arguments(parser, helpers):
9
+ """`mobster test`'s flags. ``helpers``: env_option, path_type and bounded from __main__."""
10
+ from . import repro, snapshots_cli
11
+ bounded = helpers["bounded"]
12
+ parser.add_argument("paths", nargs="*", metavar="PATH",
13
+ help="check files or folders (default: .mobster/checks); `record` saves a task; "
14
+ "`capsule` inspects evidence; `compare` reads suites; `scenario` runs ordered checks; "
15
+ "`minimize` tries a paired local reproducer")
16
+ where = parser.add_argument_group("where checks run")
17
+ where.add_argument("--sim", action="append", metavar="TYPE[@RUNTIME]",
18
+ help="a simulator device type, with an optional runtime, such as \"iPhone 17 Pro@iOS 26.4\"; "
19
+ "repeatable (default: each check's own, or .mobster/matrix.yaml)")
20
+ where.add_argument("--device", action="append", metavar="NAME",
21
+ help="a device from `mobster devices`, or all: every iPhone ready on this Mac, side by side "
22
+ "in one report; repeatable. On a real iPhone, checks drive only builds you installed, "
23
+ "never App Store apps")
24
+ from .. import android
25
+ android.add_test_flag(where)
26
+ where.add_argument("--testflight", action="append", metavar="BUNDLE",
27
+ help="with --device: your app's bundle ID, installed from TestFlight, which checks may "
28
+ "drive as your own build; repeatable")
29
+ where.add_argument("--matrix", metavar="FILE",
30
+ help="a YAML file of simulators to run every check on (simulators only)")
31
+ where.add_argument("--locale", action="append", metavar="LANG[,LANG]",
32
+ help="run every check once in each language, such as de,ja,ar: the app launches in that "
33
+ "language and region, and the screens are read for truncated text, untranslated "
34
+ "strings and, in a right-to-left language, a layout that didn't mirror")
35
+ where.add_argument("--spread", action="store_true",
36
+ help="with two or more devices: run each check once, on whichever device is free, instead of "
37
+ "on every one (the report groups checks by device)")
38
+ where.add_argument("--parallel", type=bounded(int, 1, 8), default=1, metavar="N",
39
+ help="simulators running checks at once (default: 1); each iPhone runs one check at a time")
40
+ how = parser.add_argument_group("retries and flakes")
41
+ how.add_argument("--retries", type=bounded(int, 0, 5), metavar="N",
42
+ help="attempts after a failure, from a fresh app (default: 1, or 0 with --repeat)")
43
+ how.add_argument("--repeat", type=bounded(int, 1, 20), default=1, metavar="K",
44
+ help="run each check K times and report its pass rate (flake hunting)")
45
+ how.add_argument("--strict", action="store_true", help="a flaky check fails the run")
46
+ how.add_argument("--quarantine", metavar="FILE",
47
+ help="checks that run and are reported but never fail the run (default: "
48
+ ".mobster/quarantine.yaml)")
49
+ how.add_argument("--auto-quarantine", action="store_true",
50
+ help="quarantine for this run the checks whose last 20 results say flaky; without it Mobster "
51
+ "only suggests them")
52
+ which = parser.add_argument_group("which checks")
53
+ which.add_argument("--tag", action="append", metavar="TAG", help="only checks with this tag; repeatable")
54
+ which.add_argument("--audit", action="append", choices=("a11y",), metavar="NAME",
55
+ help="also audit what each check reads: a11y finds controls with no label, tap targets "
56
+ "under 44 pt, images with no label and text clipped at a large text size; no model "
57
+ "is called")
58
+ which.add_argument("--a11y", nargs="?", const="warn", choices=("warn", "strict"), metavar="LEVEL",
59
+ help="audit the screens each check reads for accessibility: warn (the default) reports "
60
+ "findings and passes, strict fails on a new one; findings in "
61
+ ".mobster/a11y-baseline.json never fail")
62
+ which.add_argument("--update-a11y-baseline", action="store_true",
63
+ help="write this run's accessibility findings to .mobster/a11y-baseline.json, so later runs "
64
+ "fail only on new ones")
65
+ snapshots_cli.add_arguments(which)
66
+ which.add_argument("--shard", metavar="I/N", help="run the I-th of N even shards, such as 2/4")
67
+ which.add_argument("--keyless", action="store_true",
68
+ help="never call a model: checks with steps couldn't run")
69
+ replay = parser.add_argument_group("replay")
70
+ replay.add_argument("--replay", action="store_true",
71
+ help="replay each check's recorded path with no model, and heal a step that changed (the "
72
+ "default)")
73
+ replay.add_argument("--fresh", action="store_true",
74
+ help="run every step with Smart and record the path again")
75
+ replay.add_argument("--no-heal", action="store_true",
76
+ help="replay, and fail a check whose screen changed instead of healing it (strict CI)")
77
+ unit = parser.add_argument_group("your XCTest targets")
78
+ unit.add_argument("--xctest", action="store_true",
79
+ help="also build your Xcode project once and run its XCTest and Swift Testing targets on a "
80
+ "simulator beside the checks; failures join the report, the JUnit file, the history and "
81
+ "the PR comment. Pass --scheme and --project to name them")
82
+ unit.add_argument("--test-plan", metavar="NAME", help="with --xctest: the test plan to run (default: the "
83
+ "scheme's)")
84
+ unit.add_argument("--only-testing", action="append", metavar="ID",
85
+ help="with --xctest: run only this Target, Target/Class or Target/Class/test; repeatable")
86
+ out = parser.add_argument_group("reports")
87
+ out.add_argument("--junit", metavar="FILE", help="also write the JUnit XML here")
88
+ out.add_argument("--html", metavar="DIR", help="also write the HTML report (with each run's report) here")
89
+ out.add_argument("--video", action="store_true",
90
+ help="record each run's screen, on a simulator or an iPhone, into the report")
91
+ out.add_argument("--history", action="store_true",
92
+ help="print each check's last 20 results (pass rate, duration trend, flake score) and run "
93
+ "nothing")
94
+ out.add_argument("--json", action="store_true", help="print JSON on stdout instead of the summary")
95
+ out.add_argument("--include", action="append", dest="capsule_include", choices=("junit", "report", "visual"),
96
+ metavar="KIND", help="with capsule export: copy selected raw junit, report or visual evidence "
97
+ "locally; repeatable")
98
+ out.add_argument("--share", action="store_true",
99
+ help="also upload the report, sealed, to your team's hosted reports and print its link (Team "
100
+ "plan: one of the team's Macs, or MOBSTER_TEAM_KEY in CI)")
101
+ out.add_argument("--share-days", type=int, choices=(7, 30, 90), default=30, metavar="DAYS",
102
+ help="how long a shared report is kept: 7, 30 or 90 (default: 30)")
103
+ out.add_argument("--assert-timeout", type=bounded(float, 0, 60), default=5, metavar="S",
104
+ help="how long expectations may wait for the screen to settle (default: 5)")
105
+ record = parser.add_argument_group("mobster test record")
106
+ record.add_argument("--run", dest="record_run", metavar="RUN_ID", help="the finished task to make a check from")
107
+ record.add_argument("--name", dest="record_name", metavar="TEXT", help="the check's name (default: the task)")
108
+ record.add_argument("-o", "--output", dest="record_output", metavar="PATH",
109
+ help="with record: write the check here; with import: checks folder; with capsule export: "
110
+ "new output directory")
111
+ imp = parser.add_argument_group("mobster test import")
112
+ imp.add_argument("--bundle", metavar="ID",
113
+ help="the app's bundle ID for flows with no appId, and over the appId of the others")
114
+ imp.add_argument("--app", metavar="PATH", help="write this .app (built for the iOS Simulator) into each check")
115
+ imp.add_argument("--skip-unsupported", action="store_true",
116
+ help="drop commands Mobster can't run instead of turning each into a step marked TODO, and list "
117
+ "each one; an assertion is never dropped")
118
+ imp.add_argument("--dry-run", action="store_true", help="say what would be written, and write nothing")
119
+ imp.add_argument("--force", action="store_true", help="replace checks that already exist")
120
+ repro.add_arguments(parser)
121
+ add = parser.add_argument_group("mobster test add")
122
+ add.add_argument("--list", dest="add_list", action="store_true", help="list the recipes, one line each")
123
+ add.add_argument("--set", dest="add_set", action="append", metavar="KEY=VALUE",
124
+ help="a recipe param; repeatable (`mobster test add --list` shows each recipe's)")
125
+ pr = parser.add_argument_group("mobster test pr")
126
+ pr.add_argument("--base", metavar="REF",
127
+ help="the branch the PR merges into (default: $GITHUB_BASE_REF, else origin/main)")
128
+ pr.add_argument("--no-build", action="store_true",
129
+ help="don't build the app: test the builds the checks name")
130
+ pr.add_argument("--scheme", metavar="NAME", help="the Xcode scheme to build (default: the only one); also the "
131
+ "scheme --xctest builds")
132
+ pr.add_argument("--project", metavar="PATH",
133
+ help="the .xcodeproj or .xcworkspace to build (default: the one in the project); also the one "
134
+ "--xctest builds")
135
+ pr.add_argument("--drafts", choices=("run", "write", "off"), default="run",
136
+ help="checks for changed screens no check covers: write and run them (default), only write "
137
+ "them, or neither. Drafts never fail the PR")
138
+ pr.add_argument("--no-gifs", action="store_true", help="don't make a GIF of each failed check")
139
+ pr.add_argument("--tested-with", action="store_true",
140
+ help="end the PR comment with a \"Tested with Mobster\" line that links to mobster.dev")
141
+ pr.add_argument("--comment", metavar="FILE", help="also write the PR comment's Markdown here")
142
+ pr.add_argument("--github", choices=("check", "comment", "off"), default="check",
143
+ help="put the verdict on the pull request with Mobster Checks, the GitHub App: a check run "
144
+ "(check, the default), the check run and one comment kept up to date (comment), or nothing "
145
+ "(off)")
146
+ nightly = parser.add_argument_group("mobster test nightly")
147
+ nightly.add_argument("--at", metavar="HH:MM",
148
+ help="with nightly on: the time to run every night, on a 24-hour clock, such as 02:00")
149
+ heal = parser.add_argument_group("mobster test heal")
150
+ heal.add_argument("--accept", action="store_true", help="keep the healed steps of these checks (default: all)")
151
+ heal.add_argument("--reject", action="store_true",
152
+ help="keep failing: put the recorded steps back and fail when they change again")
153
+ reduce = parser.add_argument_group("mobster test minimize")
154
+ reduce.add_argument("--bad-app", dest="minimize_bad_app", metavar="PATH",
155
+ help="with minimize: the exact saved bad simulator .app build")
156
+ reduce.add_argument("--control-app", dest="minimize_control_app", metavar="PATH",
157
+ help="with minimize: the exact saved clean-control simulator .app build")
158
+ reduce.add_argument("--isolated", dest="minimize_isolated", action="store_true",
159
+ help="with minimize: confirm the external fixture is isolated for every trial")
160
+ reduce.add_argument("--max-attempts", dest="minimize_max_attempts", type=bounded(int, 5, 8), metavar="N",
161
+ help="with minimize: total bad and control suite dispatches, 5–8 (default: 8)")
162
+ helpers["env_option"](parser)
163
+ parser._option_string_actions["--env-file"].help = ("load KEY=VALUE lines, such as OPENAI_API_KEY or "
164
+ "ANTHROPIC_API_KEY for Smart steps; variables already set "
165
+ "win; default: $MOBSTER_ENV_FILE")
166
+ _usage_exits_3(parser)
167
+
168
+
169
+ def _usage_exits_3(parser):
170
+ """argparse's usage errors exit 2, which `test` uses for needs review: they exit 3 here."""
171
+ parse_known = parser.parse_known_args
172
+
173
+ def parse_known_args(args=None, namespace=None):
174
+ namespace, extras = parse_known(args, namespace)
175
+ stray = [item for item in extras if item.startswith("-")]
176
+ if stray:
177
+ parser.error(f"unrecognized arguments: {' '.join(stray)}")
178
+ if extras:
179
+ namespace.paths = list(getattr(namespace, "paths", None) or []) + extras
180
+ return namespace, []
181
+
182
+ def error(message):
183
+ parser.print_usage(sys.stderr)
184
+ hint = ""
185
+ try:
186
+ from ..devtools import option_hint
187
+ hint = option_hint(parser, message)
188
+ except ImportError:
189
+ pass
190
+ print(f"{parser.prog}: error: {message}{hint}", file=sys.stderr, flush=True)
191
+ raise SystemExit(USAGE_EXIT)
192
+ parser.parse_known_args = parse_known_args
193
+ parser.error = error
@@ -0,0 +1,288 @@
1
+ """Compare exact saved suites, distinguishing transitions from controlled app-state proof."""
2
+
3
+ import hashlib
4
+ import math
5
+ import re
6
+ from pathlib import Path
7
+
8
+ from . import capsule, reports
9
+
10
+ SCHEMA = "mobster.test-compare/1"
11
+ DIGEST = re.compile(r"[a-f0-9]{64}\Z")
12
+ REASON_CLASS = re.compile(r"[a-z][a-z0-9_]{0,39}\Z")
13
+ FIELD = re.compile(r"[a-z][a-z0-9_.-]{0,79}\Z")
14
+ STATUSES = frozenset({"passed", "failed", "needs_review", "couldnt_run"})
15
+
16
+
17
+ class ComparisonError(ValueError):
18
+ """A named suite has no intact completed local report."""
19
+
20
+
21
+ def _read(results_dir, suite_id):
22
+ if not isinstance(suite_id, str) or not capsule.SUITE_ID.fullmatch(suite_id):
23
+ raise ComparisonError("A suite ID must name a saved Mobster test result.")
24
+ try:
25
+ return capsule.read_completed_suite(Path(results_dir) / suite_id)
26
+ except (capsule.CapsuleError, OSError) as error:
27
+ raise ComparisonError(f"Suite {suite_id} has no intact completed result.") from error
28
+
29
+
30
+ def compare_suites(results_dir, base_suite_id, candidate_suite_id):
31
+ """Read the two exact IDs under one local results directory."""
32
+ return compare(_read(results_dir, base_suite_id), _read(results_dir, candidate_suite_id))
33
+
34
+
35
+ def _ref(text):
36
+ return hashlib.sha256(text.encode("utf-8", errors="surrogatepass")).hexdigest()[:12]
37
+
38
+
39
+ def _digest(value):
40
+ return isinstance(value, str) and bool(DIGEST.fullmatch(value))
41
+
42
+
43
+ def _frozen(value):
44
+ return value == "none" or _digest(value)
45
+
46
+
47
+ def _status(cell):
48
+ value = cell.get("status")
49
+ return value if isinstance(value, str) and value in STATUSES else None
50
+
51
+
52
+ def _reason(cell):
53
+ source = cell.get("reason")
54
+ value = source.get("class") if isinstance(source, dict) else None
55
+ return value if isinstance(value, str) and REASON_CLASS.fullmatch(value) else None
56
+
57
+
58
+ def _environment(cell):
59
+ source = cell.get("environment")
60
+ if not isinstance(source, dict) or source.get("kind") not in ("simulator", "iphone") or any(
61
+ not isinstance(source.get(key), str) or not 1 <= len(source[key]) <= 80
62
+ for key in ("model", "runtime")):
63
+ return None
64
+ return {key: source[key] for key in ("kind", "model", "runtime")}
65
+
66
+
67
+ def _policy(doc):
68
+ value = doc.get("executionPolicy")
69
+ if not isinstance(value, dict) or set(value) != {"retries", "repeat", "replay", "keyless",
70
+ "assertTimeout", "mobster"}:
71
+ return None
72
+ if (type(value["retries"]) is not int or not 0 <= value["retries"] <= 5 or
73
+ type(value["repeat"]) is not int or not 1 <= value["repeat"] <= 20 or
74
+ value["replay"] not in ("off", "fresh", "strict", "auto") or
75
+ type(value["keyless"]) is not bool or
76
+ type(value["assertTimeout"]) not in (int, float) or
77
+ not math.isfinite(value["assertTimeout"]) or not 0 < value["assertTimeout"] <= 600 or
78
+ not isinstance(value["mobster"], str) or not 1 <= len(value["mobster"]) <= 40):
79
+ return None
80
+ return value
81
+
82
+
83
+ def _verdict(doc):
84
+ value = doc.get("exitCode")
85
+ return value if type(value) is int and 0 <= value <= 255 else None
86
+
87
+
88
+ def _cells(doc):
89
+ found, errors = {}, []
90
+ checks = doc.get("checks")
91
+ if not isinstance(checks, list):
92
+ return found, ["checks_missing"]
93
+ for check in checks:
94
+ if not isinstance(check, dict) or not isinstance(check.get("results"), list):
95
+ errors.append("check_malformed")
96
+ continue
97
+ identity = check.get("caseId") or check.get("file")
98
+ if not isinstance(identity, str) or not 1 <= len(identity) <= 4096:
99
+ errors.append("check_identity_missing")
100
+ continue
101
+ for cell in check["results"]:
102
+ if not isinstance(cell, dict) or not isinstance(cell.get("deviceKey"), str) or \
103
+ not 1 <= len(cell["deviceKey"]) <= 256:
104
+ errors.append("cell_malformed")
105
+ continue
106
+ key = identity, cell["deviceKey"]
107
+ if key in found:
108
+ errors.append("duplicate_case_target")
109
+ found[key] = None
110
+ else:
111
+ found[key] = (check, cell)
112
+ return found, sorted(set(errors))
113
+
114
+
115
+ def _clean(cell):
116
+ reason = cell.get("reason")
117
+ return (type(cell.get("quarantined")) is bool and cell["quarantined"] is False and
118
+ type(cell.get("flaky")) is bool and cell["flaky"] is False and
119
+ type(cell.get("replayed")) is bool and cell["replayed"] is False and
120
+ cell.get("healed") == [] and isinstance(cell.get("assertions"), list) and
121
+ (reason is None or isinstance(reason, dict)) and not cell.get("safetyStop") and
122
+ _status(cell) in ("passed", "failed"))
123
+
124
+
125
+ def _business_fields(check, cell):
126
+ """Validate one selected run against its admitted binding and complete field inventory."""
127
+ binding = check.get("businessBindingSha256")
128
+ proof = cell.get("business")
129
+ runs = cell.get("runs")
130
+ if (not _digest(binding) or not isinstance(proof, dict) or proof.get("bindingSha256") != binding or
131
+ proof.get("setup") != "seeded" or proof.get("status") != "observed" or
132
+ proof.get("cleanup") != "removed" or type(proof.get("generation")) is not int or
133
+ proof["generation"] < 1 or not _digest(proof.get("sourceSha256")) or
134
+ type(cell.get("attempts")) is not int or cell["attempts"] != 1 or
135
+ not isinstance(runs, list) or len(runs) != 1 or not isinstance(runs[0], dict) or
136
+ runs[0].get("business") != proof or runs[0].get("verdict") != cell.get("status")):
137
+ return None
138
+ fields, passed, failed = (proof.get(key) for key in ("assertionFields", "passedFields", "failedFields"))
139
+ if (not isinstance(fields, list) or not 1 <= len(fields) <= 20 or
140
+ not all(isinstance(item, str) and FIELD.fullmatch(item) for item in fields) or
141
+ len(set(fields)) != len(fields) or not isinstance(passed, list) or not isinstance(failed, list) or
142
+ any(not isinstance(item, str) for item in passed + failed) or
143
+ len(passed) + len(failed) != len(fields) or set(passed + failed) != set(fields)):
144
+ return None
145
+ return fields, failed, passed
146
+
147
+
148
+ def _supporting_evidence(cell, visual_required):
149
+ if cell.get("findings", []) != []:
150
+ return False
151
+ conditions = cell.get("conditions")
152
+ if conditions is not None and (not isinstance(conditions, dict) or
153
+ conditions.get("cleanup") not in ("restored", "not_needed") or
154
+ not isinstance(conditions.get("applied"), list) or
155
+ conditions.get("skipped") != []):
156
+ return False
157
+ assertions = cell.get("assertions")
158
+ if not isinstance(assertions, list) or any(
159
+ not isinstance(item, dict) or type(item.get("index")) is not int or item["index"] < 0 or
160
+ not isinstance(item.get("text"), str) or item.get("ok") is not True for item in assertions):
161
+ return False
162
+ if len({item["index"] for item in assertions}) != len(assertions):
163
+ return False
164
+ visual = cell.get("visual")
165
+ return (visual is None and not visual_required or
166
+ isinstance(visual, dict) and visual.get("ok") is True)
167
+
168
+
169
+ def _case(base, candidate, key, single_repeat):
170
+ before_check, before = base
171
+ after_check, after = candidate
172
+ reasons = []
173
+ if before_check.get("provenanceDrift") or after_check.get("provenanceDrift"):
174
+ reasons.append("provenance_drift")
175
+ for field, missing, changed in (("checkSpecDigest", "check_unknown", "check_changed"),
176
+ ("baselineDigest", "baseline_unknown", "baseline_changed"),
177
+ ("fixtureDigest", "fixture_unknown", "fixture_changed")):
178
+ valid = _digest if field == "checkSpecDigest" else _frozen
179
+ if not valid(before_check.get(field)) or not valid(after_check.get(field)):
180
+ reasons.append(missing)
181
+ elif before_check[field] != after_check[field]:
182
+ reasons.append(changed)
183
+ old_env, new_env = _environment(before), _environment(after)
184
+ if old_env is None or new_env is None:
185
+ reasons.append("environment_unknown")
186
+ elif old_env != new_env:
187
+ reasons.append("environment_changed")
188
+ if not _clean(before) or not _clean(after):
189
+ reasons.append("verdict_not_stable")
190
+ comparable = not reasons
191
+ before_status, after_status = _status(before), _status(after)
192
+ transition = f"{before_status}_to_{after_status}" if comparable else None
193
+ proof_reasons = []
194
+ verified = False
195
+ if transition == "failed_to_passed":
196
+ binding = before_check.get("businessBindingSha256")
197
+ prior, later = _business_fields(before_check, before), _business_fields(after_check, after)
198
+ if (not single_repeat or _reason(before) != "business" or after.get("reason") is not None or
199
+ before.get("business") is None or after.get("business") is None or
200
+ before_check.get("fixtureDigest") == "none" or
201
+ before_check.get("businessBindingSha256") != after_check.get("businessBindingSha256") or
202
+ not _digest(binding) or not prior or not later or prior[0] != later[0] or
203
+ not prior[1] or later[1] or not set(prior[1]).issubset(later[2]) or
204
+ before["runs"][0].get("class") != "business" or after["runs"][0].get("class") is not None or
205
+ old_env["kind"] != "simulator" or
206
+ before["business"]["sourceSha256"] == after["business"]["sourceSha256"] or
207
+ "visualBaselineSha256" not in before_check or "visualBaselineSha256" not in after_check or
208
+ before_check["visualBaselineSha256"] != after_check["visualBaselineSha256"] or
209
+ (before_check["visualBaselineSha256"] is not None and
210
+ not _digest(before_check["visualBaselineSha256"])) or
211
+ not _supporting_evidence(before, before_check.get("visualBaselineSha256") is not None) or
212
+ not _supporting_evidence(after, after_check.get("visualBaselineSha256") is not None)):
213
+ proof_reasons.append("controlled_fixture_unproven")
214
+ if not _digest(before.get("appDigest")) or not _digest(after.get("appDigest")):
215
+ proof_reasons.append("app_artifact_unknown")
216
+ elif before["appDigest"] == after["appDigest"]:
217
+ proof_reasons.append("app_artifact_unchanged")
218
+ verified = not proof_reasons
219
+ elif transition is not None:
220
+ proof_reasons.append("no_failed_to_passed_transition")
221
+ return {"caseRef": _ref(key[0]), "targetRef": _ref(key[1]),
222
+ "base": {"status": before_status, "reasonClass": _reason(before)},
223
+ "candidate": {"status": after_status, "reasonClass": _reason(after)},
224
+ "comparable": comparable, "observedTransition": transition,
225
+ "verifiedFix": verified, "proof": {"scope": "controlled_app_state" if verified else None},
226
+ "reasons": sorted(set(reasons)), "proofReasons": proof_reasons}
227
+
228
+
229
+ def compare(base, candidate):
230
+ """Keep both actual verdicts and every incompatibility; no green-baseline search."""
231
+ if not isinstance(base, dict) or not isinstance(candidate, dict):
232
+ raise ComparisonError("Both saved suite reports must be objects.")
233
+ before, before_errors = _cells(base)
234
+ after, after_errors = _cells(candidate)
235
+ old_policy, new_policy = _policy(base), _policy(candidate)
236
+ rows = []
237
+ for key in sorted(before.keys() | after.keys()):
238
+ if key in before and key in after and before[key] is not None and after[key] is not None:
239
+ rows.append(_case(before[key], after[key], key, old_policy is not None and
240
+ new_policy is not None and old_policy["repeat"] == new_policy["repeat"] == 1))
241
+ continue
242
+ prior = before.get(key)
243
+ later = after.get(key)
244
+ ambiguous = key in before and prior is None or key in after and later is None
245
+ rows.append({"caseRef": _ref(key[0]), "targetRef": _ref(key[1]),
246
+ "base": {"status": _status(prior[1]) if prior else None, "reasonClass": None},
247
+ "candidate": {"status": _status(later[1]) if later else None, "reasonClass": None},
248
+ "comparable": False, "observedTransition": None, "verifiedFix": False,
249
+ "proof": {"scope": None}, "reasons": ["duplicate_case_target" if ambiguous else
250
+ "case_missing_from_candidate" if key in before else "case_missing_from_base"],
251
+ "proofReasons": []})
252
+ reasons = before_errors + after_errors
253
+ if base.get("scenario") is not None or candidate.get("scenario") is not None:
254
+ reasons.append("scenario_not_comparable")
255
+ if base.get("schema") != reports.SCHEMA or candidate.get("schema") != reports.SCHEMA:
256
+ reasons.append("report_schema_unknown")
257
+ if type(base.get("finishedAt")) is not int or type(candidate.get("finishedAt")) is not int:
258
+ reasons.append("suite_not_terminal")
259
+ if base.get("stopReason") or candidate.get("stopReason") or base.get("stopped") or candidate.get("stopped"):
260
+ reasons.append("suite_stopped")
261
+ if _verdict(base) is None or _verdict(candidate) is None:
262
+ reasons.append("suite_verdict_missing")
263
+ if old_policy is None or new_policy is None:
264
+ reasons.append("execution_policy_unknown")
265
+ elif old_policy != new_policy:
266
+ reasons.append("execution_policy_changed")
267
+ if not rows:
268
+ reasons.append("no_cases")
269
+ if any(not row["comparable"] for row in rows):
270
+ reasons.append("cases_incomparable")
271
+ invalid = {"scenario_not_comparable", "suite_not_terminal", "suite_stopped", "report_schema_unknown",
272
+ "suite_verdict_missing", "execution_policy_unknown", "execution_policy_changed"}
273
+ if before_errors or after_errors or invalid.intersection(reasons):
274
+ for row in rows:
275
+ row.update(comparable=False, observedTransition=None, verifiedFix=False, proof={"scope": None})
276
+ row["reasons"] = sorted(set(row["reasons"] + ["suite_not_comparable"]))
277
+ verified = (not reasons and _verdict(base) == 1 and _verdict(candidate) == 0 and
278
+ any(row["verifiedFix"] for row in rows) and all(
279
+ row["candidate"]["status"] == "passed" and
280
+ (row["base"]["status"] != "failed" or row["verifiedFix"]) for row in rows))
281
+ return {"schema": SCHEMA, "baseSuite": base.get("suite") if isinstance(base.get("suite"), str) and
282
+ capsule.SUITE_ID.fullmatch(base["suite"]) else None,
283
+ "candidateSuite": candidate.get("suite") if isinstance(candidate.get("suite"), str) and
284
+ capsule.SUITE_ID.fullmatch(candidate["suite"]) else None,
285
+ "baseVerdict": _verdict(base), "candidateVerdict": _verdict(candidate),
286
+ "comparable": not reasons, "verifiedFix": verified,
287
+ "proof": {"scope": "controlled_app_state" if verified else None},
288
+ "reasons": sorted(set(reasons)), "cases": rows}
@@ -0,0 +1,41 @@
1
+ """Compare two explicitly named local terminal suite reports."""
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from ..verify.checks import project_root
8
+ from .comparison import ComparisonError, compare_suites
9
+
10
+
11
+ def run(args, operands, *, out=None, err=None, compare=compare_suites):
12
+ out, err = out or sys.stdout, err or sys.stderr
13
+
14
+ def refuse(message):
15
+ if getattr(args, "json", False):
16
+ print(json.dumps({"ok": False, "error": message}), file=out)
17
+ else:
18
+ print(f"mobster test compare: {message}", file=err)
19
+ return 3
20
+
21
+ if len(operands) != 2:
22
+ return refuse("name a base and candidate: mobster test compare BASE_SUITE_ID CANDIDATE_SUITE_ID")
23
+ private = project_root(Path.cwd()) / ".mobster"
24
+ results = private / "test-results"
25
+ if private.is_symlink() or results.is_symlink():
26
+ return refuse("the project's test-results folder must not be a symlink")
27
+ try:
28
+ result = compare(results, *operands)
29
+ except (ComparisonError, OSError) as error:
30
+ return refuse(str(error))
31
+ if getattr(args, "json", False):
32
+ print(json.dumps(result, ensure_ascii=False), file=out)
33
+ else:
34
+ print(f"Base {result['baseSuite']}: exit {result['baseVerdict']}; "
35
+ f"candidate {result['candidateSuite']}: exit {result['candidateVerdict']}.", file=out)
36
+ proof = "yes, for controlled app state" if result["verifiedFix"] else "no"
37
+ print(f"Comparable: {'yes' if result['comparable'] else 'no'}; verified fix: {proof}.", file=out)
38
+ if result["reasons"]:
39
+ print("Reasons: " + ", ".join(result["reasons"]), file=out)
40
+ print(f"Compared {len(result['cases'])} case-target pairs. Use --json for each transition.", file=out)
41
+ return 0