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,312 @@
1
+ """Static audits of a built app's bundle: they read files only, never launch anything and never change the bundle."""
2
+
3
+ import os
4
+ import plistlib
5
+ import re
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ __all__ = ["Finding", "AUDITS", "run_audit", "bundle_symbols", "USAGE_KEYS", "REQUIRED_REASON"]
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class Finding:
14
+ id: str # stable, such as "usage-description-missing"
15
+ guideline: str # "5.1.1(i)"
16
+ level: str # "fail" | "review"
17
+ message: str # what is wrong, then what to do
18
+ evidence: str # a path inside the bundle, with the key
19
+
20
+ def to_dict(self):
21
+ return {"id": self.id, "guideline": self.guideline, "level": self.level, "message": self.message,
22
+ "evidence": self.evidence}
23
+
24
+
25
+ MACHO_MAGICS = (b"\xcf\xfa\xed\xfe", b"\xce\xfa\xed\xfe", b"\xca\xfe\xba\xbe", b"\xbe\xba\xfe\xca")
26
+ READ_LIMIT = 200 * 1024 * 1024
27
+
28
+
29
+ def _machos(app):
30
+ """Every Mach-O file in the bundle: the executable, frameworks and plug-ins, as (relative path, path)."""
31
+ found = []
32
+ for folder, _, names in os.walk(app):
33
+ for name in names:
34
+ path = Path(folder) / name
35
+ if path.is_symlink() or not path.is_file():
36
+ continue
37
+ try:
38
+ with open(path, "rb") as handle:
39
+ head = handle.read(4)
40
+ except OSError:
41
+ continue
42
+ if head in MACHO_MAGICS:
43
+ found.append((path.relative_to(app).as_posix(), path))
44
+ return sorted(found)
45
+
46
+
47
+ def _present(name, data):
48
+ # Swift mangles class and constant names inside longer ones; a C function is a whole NUL-delimited string.
49
+ word = name.encode()
50
+ if name[:1].isupper():
51
+ return word in data
52
+ return b"\0_" + word + b"\0" in data or b"\0" + word + b"\0" in data
53
+
54
+
55
+ def bundle_symbols(app, names):
56
+ """{relative path of a Mach-O file: the set of ``names`` it holds} for the whole bundle."""
57
+ out = {}
58
+ for relative, path in _machos(Path(app)):
59
+ try:
60
+ if path.stat().st_size > READ_LIMIT:
61
+ continue
62
+ data = path.read_bytes()
63
+ except OSError:
64
+ continue
65
+ found = {name for name in names if _present(name, data)}
66
+ if found:
67
+ out[relative] = found
68
+ return out
69
+
70
+
71
+ def uses(symbols, names):
72
+ """{Mach-O file: the first of ``names`` it holds} over the scan ``symbols`` (bundle_symbols)."""
73
+ hits = {}
74
+ for relative, found in sorted(symbols.items()):
75
+ for name in names:
76
+ if name in found:
77
+ hits[relative] = name
78
+ break
79
+ return hits
80
+
81
+
82
+ # The Info.plist key each API needs, and the names whose presence in a binary means the app can reach it.
83
+ USAGE_KEYS = (
84
+ ("NSCameraUsageDescription", ("AVCaptureDeviceInput", "AVCaptureSession", "VNDocumentCameraViewController")),
85
+ ("NSMicrophoneUsageDescription", ("AVAudioRecorder", "AVAudioApplication")),
86
+ ("NSPhotoLibraryUsageDescription", ("PHPhotoLibrary",)),
87
+ ("NSPhotoLibraryAddUsageDescription", ("UIImageWriteToSavedPhotosAlbum",)),
88
+ ("NSLocationWhenInUseUsageDescription", ("CLLocationManager",)),
89
+ ("NSContactsUsageDescription", ("CNContactStore",)),
90
+ ("NSCalendarsUsageDescription", ("EKEventStore",)),
91
+ ("NSFaceIDUsageDescription", ("LAContext",)),
92
+ ("NSBluetoothAlwaysUsageDescription", ("CBCentralManager", "CBPeripheralManager")),
93
+ ("NSMotionUsageDescription", ("CMMotionActivityManager", "CMPedometer")),
94
+ ("NSSpeechRecognitionUsageDescription", ("SFSpeechRecognizer",)),
95
+ ("NSUserTrackingUsageDescription", ("ATTrackingManager",)),
96
+ ("NSHealthShareUsageDescription", ("HKHealthStore",)),
97
+ )
98
+ ALSO_ACCEPTED = {
99
+ "NSLocationWhenInUseUsageDescription": ("NSLocationAlwaysAndWhenInUseUsageDescription",
100
+ "NSLocationAlwaysUsageDescription"),
101
+ "NSCalendarsUsageDescription": ("NSCalendarsFullAccessUsageDescription",
102
+ "NSCalendarsWriteOnlyAccessUsageDescription",
103
+ "NSRemindersUsageDescription", "NSRemindersFullAccessUsageDescription"),
104
+ "NSPhotoLibraryAddUsageDescription": ("NSPhotoLibraryUsageDescription",),
105
+ "NSHealthShareUsageDescription": ("NSHealthUpdateUsageDescription",),
106
+ }
107
+ PLACEHOLDER = re.compile(
108
+ r"lorem ipsum|\btodo\b|\btbd\b|\bfixme\b|placeholder|\bxxx+\b|usage description|your (?:text|description) here|"
109
+ r"enter (?:a |your )?(?:description|text|reason) here|^(?:test|testing|foo|bar|asdf|description|text|none|n/a|"
110
+ r"camera|microphone|location|photos|contacts|calendar)\.?$", re.I)
111
+ MIN_WORDS = 3
112
+
113
+
114
+ def read_plist(path):
115
+ """A property list as a dict (None when the file is missing, unreadable or not a dict)."""
116
+ try:
117
+ value = plistlib.loads(Path(path).read_bytes())
118
+ except Exception:
119
+ return None
120
+ return value if isinstance(value, dict) else None
121
+
122
+
123
+ def placeholder_reason(text):
124
+ """Why a usage string reads as filler (None when it reads as a real reason)."""
125
+ value = (text or "").strip()
126
+ if not value:
127
+ return "it is empty"
128
+ if PLACEHOLDER.search(value):
129
+ return "it reads as placeholder text"
130
+ if len(value.split()) < MIN_WORDS:
131
+ return f"it is {len(value.split())} word{'' if len(value.split()) == 1 else 's'}, too short to say why"
132
+ return None
133
+
134
+
135
+ def audit_usage_descriptions(app, ctx):
136
+ info = read_plist(Path(app) / "Info.plist")
137
+ if info is None:
138
+ return [Finding("info-plist-unreadable", "2.1", "review",
139
+ "The app's Info.plist can't be read. Build the app again and point the check at it.",
140
+ f"{Path(app).name}/Info.plist")]
141
+ symbols = ctx.symbols()
142
+ findings, plist = [], f"{Path(app).name}/Info.plist"
143
+ for key, names in USAGE_KEYS:
144
+ hits = uses(symbols, names)
145
+ if not hits:
146
+ continue
147
+ accepted = (key, *ALSO_ACCEPTED.get(key, ()))
148
+ present = [name for name in accepted if name in info]
149
+ where = next(iter(hits.items()))
150
+ if not present:
151
+ findings.append(Finding(
152
+ "usage-description-missing", "5.1.1", "fail",
153
+ f"{where[0]} uses {where[1]} and Info.plist has no {key}. Add {key} with the reason you ask.",
154
+ f"{plist} has no {key}; {where[1]} is in {where[0]}"))
155
+ continue
156
+ for name in present:
157
+ why = placeholder_reason(info.get(name) if isinstance(info.get(name), str) else "")
158
+ if why:
159
+ findings.append(Finding(
160
+ "usage-description-placeholder", "5.1.1", "fail",
161
+ f"{name} is {info.get(name)!r}: {why}. Write the reason a person would accept.",
162
+ f"{plist} {name}"))
163
+ # A key with filler even when no symbol showed the API: a framework may load it at run time.
164
+ for name, value in sorted(info.items()):
165
+ if name.endswith("UsageDescription") and isinstance(value, str):
166
+ why = placeholder_reason(value)
167
+ if why and not any(f.evidence == f"{plist} {name}" for f in findings):
168
+ findings.append(Finding(
169
+ "usage-description-placeholder", "5.1.1", "fail",
170
+ f"{name} is {value!r}: {why}. Write the reason a person would accept.", f"{plist} {name}"))
171
+ return findings
172
+
173
+
174
+ # "weak" names are libc calls a binary can carry for other reasons: an undeclared one needs review, not a failure.
175
+ REQUIRED_REASON = {
176
+ "NSPrivacyAccessedAPICategoryFileTimestamp": {
177
+ "sure": ("NSFileCreationDate", "NSFileModificationDate", "NSURLContentModificationDateKey",
178
+ "NSURLCreationDateKey", "NSURLContentAccessDateKey", "getattrlist", "getattrlistbulk",
179
+ "fgetattrlist"),
180
+ "weak": ("stat", "fstat", "lstat", "fstatat")},
181
+ "NSPrivacyAccessedAPICategorySystemBootTime": {
182
+ "sure": ("systemUptime", "mach_absolute_time"), "weak": ()},
183
+ "NSPrivacyAccessedAPICategoryDiskSpace": {
184
+ "sure": ("NSURLVolumeAvailableCapacityKey", "NSURLVolumeAvailableCapacityForImportantUsageKey",
185
+ "NSURLVolumeTotalCapacityKey", "NSFileSystemFreeSize", "NSFileSystemSize", "statfs", "statvfs",
186
+ "fstatfs", "fstatvfs"), "weak": ()},
187
+ "NSPrivacyAccessedAPICategoryActiveKeyboards": {"sure": ("activeInputModes",), "weak": ()},
188
+ "NSPrivacyAccessedAPICategoryUserDefaults": {"sure": ("NSUserDefaults",), "weak": ()},
189
+ }
190
+ MANIFEST = "PrivacyInfo.xcprivacy"
191
+
192
+
193
+ def declared_categories(manifest):
194
+ """({category: [reasons]} the manifest declares, a problem sentence or None)."""
195
+ types = manifest.get("NSPrivacyAccessedAPITypes")
196
+ if types is None:
197
+ return {}, None
198
+ if not isinstance(types, list):
199
+ return {}, "NSPrivacyAccessedAPITypes is not a list"
200
+ out = {}
201
+ for item in types:
202
+ if isinstance(item, dict) and isinstance(item.get("NSPrivacyAccessedAPIType"), str):
203
+ reasons = item.get("NSPrivacyAccessedAPITypeReasons")
204
+ out[item["NSPrivacyAccessedAPIType"]] = [r for r in reasons if isinstance(r, str)] \
205
+ if isinstance(reasons, list) else []
206
+ return out, None
207
+
208
+
209
+ def audit_privacy_manifest(app, ctx):
210
+ app = Path(app)
211
+ symbols = ctx.symbols()
212
+ needed = {}
213
+ for category, groups in REQUIRED_REASON.items():
214
+ for strength in ("sure", "weak"):
215
+ hits = uses(symbols, groups[strength])
216
+ if hits:
217
+ needed.setdefault(category, {})[strength] = hits
218
+ top = app / MANIFEST
219
+ findings = []
220
+ if not top.is_file():
221
+ if needed:
222
+ first = ", ".join(f"{category} ({next(iter((hits.get('sure') or hits['weak']).items()))[1]} in "
223
+ f"{next(iter(sorted((hits.get('sure') or hits['weak']))))})"
224
+ for category, hits in sorted(needed.items()))
225
+ findings.append(Finding(
226
+ "privacy-manifest-missing", "5.1.1", "fail",
227
+ f"{app.name} has no {MANIFEST} and its binaries use {len(needed)} required-reason API "
228
+ f"categor{'y' if len(needed) == 1 else 'ies'}. Add the file to the app target and declare each "
229
+ "with a reason.", f"{app.name}/{MANIFEST} is missing; uses {first}"))
230
+ else:
231
+ findings.append(Finding(
232
+ "privacy-manifest-missing", "5.1.1", "review",
233
+ f"{app.name} has no {MANIFEST}. No required-reason API shows in the binaries, so App Review "
234
+ "may accept it; add an empty manifest to be sure.", f"{app.name}/{MANIFEST} is missing"))
235
+ return findings
236
+ manifest = read_plist(top)
237
+ if manifest is None:
238
+ return [Finding("privacy-manifest-invalid", "5.1.1", "fail",
239
+ f"{MANIFEST} is not a valid property list. Open it in Xcode and save it again.",
240
+ f"{app.name}/{MANIFEST}")]
241
+ declared, problem = declared_categories(manifest)
242
+ if problem:
243
+ findings.append(Finding("privacy-manifest-invalid", "5.1.1", "fail",
244
+ f"{problem}. Open {MANIFEST} in Xcode and fix the key.", f"{app.name}/{MANIFEST}"))
245
+ for category, reasons in sorted(declared.items()):
246
+ if not reasons:
247
+ findings.append(Finding(
248
+ "privacy-reason-missing", "5.1.1", "fail",
249
+ f"{category} is declared with no reason code. Add NSPrivacyAccessedAPITypeReasons.",
250
+ f"{app.name}/{MANIFEST} {category}"))
251
+ for category, strengths in sorted(needed.items()):
252
+ if category in declared:
253
+ continue
254
+ for strength, level in (("sure", "fail"), ("weak", "review")):
255
+ hits = strengths.get(strength) or {}
256
+ own = {f: n for f, n in hits.items() if not f.startswith(("Frameworks/", "PlugIns/"))}
257
+ if own:
258
+ file, name = next(iter(sorted(own.items())))
259
+ findings.append(Finding(
260
+ "required-reason-undeclared", "5.1.1", level,
261
+ f"{file} uses {name} and {MANIFEST} does not declare {category}. Add it with the reason "
262
+ "that fits how the app uses it.", f"{app.name}/{MANIFEST} lacks {category}; {name} is in {file}"))
263
+ break
264
+ embedded = {f: n for f, n in hits.items() if f not in own}
265
+ if embedded:
266
+ file, name = next(iter(sorted(embedded.items())))
267
+ folder = "/".join(file.split("/")[:2])
268
+ own_manifest = read_plist(app / folder / MANIFEST) or {}
269
+ if category in declared_categories(own_manifest)[0]:
270
+ continue
271
+ state = f"lacks {category}" if own_manifest else "is missing"
272
+ findings.append(Finding(
273
+ "required-reason-sdk", "5.1.1", "review",
274
+ f"{file} uses {name} and neither it nor {MANIFEST} declares {category}. Update the SDK "
275
+ "to a version with its own privacy manifest, or declare the category yourself.",
276
+ f"{folder}/{MANIFEST} {state}; {name} is in {file}"))
277
+ break
278
+ return findings
279
+
280
+
281
+ class Context:
282
+ """What the audits of one run share: the bundle's symbols, read once."""
283
+
284
+ def __init__(self, app):
285
+ self.app = Path(app)
286
+ self._symbols = None
287
+
288
+ def symbols(self):
289
+ if self._symbols is None:
290
+ names = {name for _, group in USAGE_KEYS for name in group}
291
+ for groups in REQUIRED_REASON.values():
292
+ names.update(groups["sure"])
293
+ names.update(groups["weak"])
294
+ self._symbols = bundle_symbols(self.app, sorted(names))
295
+ return self._symbols
296
+
297
+
298
+ AUDITS = {
299
+ "usage-descriptions": audit_usage_descriptions,
300
+ "privacy-manifest": audit_privacy_manifest,
301
+ }
302
+
303
+
304
+ def run_audit(name, app, *, context=None):
305
+ """The findings of audit ``name`` on the built app ``app``. A bundle that isn't there is one review finding,
306
+ never a crash."""
307
+ app = Path(app)
308
+ if not app.is_dir():
309
+ return [Finding("app-not-found", "2.1", "review", f"{app} isn't a built app. Build it, then run again.",
310
+ str(app))]
311
+ ctx = context or Context(app)
312
+ return AUDITS[name](app, ctx)
@@ -0,0 +1,221 @@
1
+ """A bounded, locally enrolled app-state oracle for simulator checks.
2
+
3
+ The check names an adapter and fixture; only a project-owned manifest can define
4
+ their fields and seed values. This is persisted app state, not a backend oracle.
5
+ """
6
+
7
+ import hashlib
8
+ import json
9
+ import math
10
+ import os
11
+ import re
12
+ import stat
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+
16
+ from ..state import validate_bundle_id
17
+ from .assertions import CheckError
18
+
19
+ CONTRACT = "mobster.business/1"
20
+ FILENAME = "mobster-business.json"
21
+ MANIFEST = ".mobster/fixtures.json"
22
+ _ID = re.compile(r"[a-z][a-z0-9_-]{0,63}\Z")
23
+ _FIELD = re.compile(r"[a-z][a-z0-9_.-]{0,79}\Z")
24
+ _MAX_BYTES = 64_000
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class BusinessExpectation:
29
+ field: str
30
+ equals: object
31
+
32
+ def to_dict(self):
33
+ return {"field": self.field, "equals": self.equals}
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class BusinessSpec:
38
+ adapter: str
39
+ fixture: str
40
+ expect: tuple[BusinessExpectation, ...]
41
+
42
+ def to_dict(self):
43
+ return {"adapter": self.adapter, "fixture": self.fixture,
44
+ "expect": [item.to_dict() for item in self.expect]}
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class BusinessBinding:
49
+ spec: BusinessSpec
50
+ bundle: str
51
+ fields: tuple[str, ...]
52
+ seed: tuple[tuple[str, object], ...]
53
+ digest: str
54
+
55
+ def seed_state(self):
56
+ return dict(self.seed)
57
+
58
+ @property
59
+ def action_digest(self):
60
+ """Replay starting state; expected assertions do not change the action path."""
61
+ encoded = json.dumps({"contract": CONTRACT, "adapter": self.spec.adapter, "fixture": self.spec.fixture,
62
+ "bundle": self.bundle, "fields": self.fields, "seed": self.seed_state()},
63
+ sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
64
+ return hashlib.sha256(encoded).hexdigest()
65
+
66
+
67
+ def _id(value, where, pattern=_ID):
68
+ if not isinstance(value, str) or not pattern.fullmatch(value):
69
+ raise CheckError(f"{where} must be a lowercase symbolic ID")
70
+ return value
71
+
72
+
73
+ def _scalar(value, where):
74
+ if value is None or isinstance(value, bool):
75
+ return value
76
+ if isinstance(value, str) and len(value) <= 256 and all(
77
+ ord(c) >= 32 and not 127 <= ord(c) <= 159 and not 0xD800 <= ord(c) <= 0xDFFF
78
+ and not 0xFDD0 <= ord(c) <= 0xFDEF and ord(c) & 0xFFFF not in (0xFFFE, 0xFFFF)
79
+ for c in value):
80
+ return value
81
+ if isinstance(value, int) and abs(value) <= 1_000_000_000:
82
+ return value
83
+ if isinstance(value, float) and math.isfinite(value) and abs(value) <= 1_000_000_000:
84
+ return value
85
+ raise CheckError(f"{where} must be a bounded JSON scalar")
86
+
87
+
88
+ def valid_scalar(value):
89
+ try:
90
+ _scalar(value, "app-state value")
91
+ except CheckError:
92
+ return False
93
+ return True
94
+
95
+
96
+ def parse_business(value):
97
+ if value is None:
98
+ return None
99
+ if not isinstance(value, dict) or set(value) != {"adapter", "fixture", "expect"}:
100
+ raise CheckError("business needs adapter, fixture and expect; no other keys")
101
+ adapter = _id(value["adapter"], "business.adapter")
102
+ fixture = _id(value["fixture"], "business.fixture")
103
+ items = value["expect"]
104
+ if not isinstance(items, list) or len(items) > 20:
105
+ raise CheckError("business.expect must be a list of at most 20 field comparisons")
106
+ seen = set()
107
+ parsed = []
108
+ for index, item in enumerate(items):
109
+ if not isinstance(item, dict) or set(item) != {"field", "equals"}:
110
+ raise CheckError(f"business.expect[{index}] needs field and equals")
111
+ field = _id(item["field"], f"business.expect[{index}].field", _FIELD)
112
+ if field in seen:
113
+ raise CheckError(f"business.expect repeats field {field!r}")
114
+ seen.add(field)
115
+ parsed.append(BusinessExpectation(field, _scalar(item["equals"], f"business.expect[{index}].equals")))
116
+ return BusinessSpec(adapter, fixture, tuple(parsed))
117
+
118
+
119
+ def _pairs(pairs):
120
+ result = {}
121
+ for key, value in pairs:
122
+ if key in result:
123
+ raise CheckError(f"duplicate registration key {key!r}")
124
+ result[key] = value
125
+ return result
126
+
127
+
128
+ def _invalid_constant(value):
129
+ raise CheckError(f"non-finite JSON value {value!r}")
130
+
131
+
132
+ def strict_json(raw):
133
+ return json.loads(raw, object_pairs_hook=_pairs, parse_constant=_invalid_constant)
134
+
135
+
136
+ def _manifest_bytes(project_root):
137
+ root = os.open(Path(project_root).resolve(), os.O_RDONLY | os.O_DIRECTORY)
138
+ try:
139
+ folder = os.open(".mobster", os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=root)
140
+ try:
141
+ descriptor = os.open("fixtures.json", os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW, dir_fd=folder)
142
+ with os.fdopen(descriptor, "rb") as source:
143
+ info = os.fstat(source.fileno())
144
+ if not stat.S_ISREG(info.st_mode) or info.st_size > _MAX_BYTES:
145
+ raise CheckError(f"{MANIFEST} must be a regular file of at most {_MAX_BYTES:,} bytes")
146
+ raw = source.read(_MAX_BYTES + 1)
147
+ if len(raw) > _MAX_BYTES:
148
+ raise CheckError(f"{MANIFEST} is larger than {_MAX_BYTES:,} bytes")
149
+ return raw
150
+ finally:
151
+ os.close(folder)
152
+ finally:
153
+ os.close(root)
154
+
155
+
156
+ def load_business_binding(check, project_root):
157
+ """Resolve a check against the project-owned, versioned local registration."""
158
+ spec = check.business
159
+ if spec is None:
160
+ raise CheckError("the check has no business binding")
161
+ try:
162
+ data = strict_json(_manifest_bytes(project_root))
163
+ except CheckError:
164
+ raise
165
+ except (OSError, UnicodeError, ValueError, RecursionError) as error:
166
+ raise CheckError(f"{MANIFEST} is missing or invalid ({type(error).__name__})") from None
167
+ if not isinstance(data, dict) or set(data) != {"version", "adapters"} or type(data["version"]) is not int \
168
+ or data["version"] != 1 or not isinstance(data["adapters"], dict) \
169
+ or len(data["adapters"]) > 16:
170
+ raise CheckError(f"{MANIFEST} needs version 1 and at most 16 adapters")
171
+ for adapter_id in data["adapters"]:
172
+ _id(adapter_id, "adapter registration")
173
+ registration = data["adapters"].get(spec.adapter)
174
+ if not isinstance(registration, dict) or set(registration) != {"kind", "bundle", "fields", "fixtures"} \
175
+ or registration["kind"] != "app_container_json":
176
+ raise CheckError(f"business adapter {spec.adapter!r} is not locally registered for app_container_json")
177
+ bundle = registration["bundle"]
178
+ try:
179
+ bundle = validate_bundle_id(bundle)
180
+ except (TypeError, ValueError):
181
+ raise CheckError("the registered app-state bundle is not a bundle ID") from None
182
+ if check.bundle_id and bundle != check.bundle_id:
183
+ raise CheckError("the registered app-state bundle does not match the check")
184
+ fields = registration["fields"]
185
+ if not isinstance(fields, list) or not 1 <= len(fields) <= 20:
186
+ raise CheckError("business adapter fields must be 1–20 distinct keys")
187
+ for field in fields:
188
+ _id(field, "business adapter field", _FIELD)
189
+ if len(set(fields)) != len(fields):
190
+ raise CheckError("business adapter fields must be 1–20 distinct keys")
191
+ fixtures = registration["fixtures"]
192
+ if not isinstance(fixtures, dict) or not 1 <= len(fixtures) <= 20:
193
+ raise CheckError("business adapter needs 1–20 named fixtures")
194
+ for fixture_id in fixtures:
195
+ _id(fixture_id, "business fixture")
196
+ seed = fixtures.get(spec.fixture)
197
+ if not isinstance(seed, dict) or set(seed) != set(fields):
198
+ raise CheckError(f"business fixture {spec.fixture!r} is missing or has unregistered fields")
199
+ seed = tuple((field, _scalar(seed[field], f"business fixture {spec.fixture}.{field}")) for field in fields)
200
+ if any(item.field not in fields for item in spec.expect):
201
+ raise CheckError("business.expect names a field this adapter did not register")
202
+ canonical = json.dumps({"contract": CONTRACT, "adapter": spec.adapter, "fixture": spec.fixture,
203
+ "registration": registration, "expect": [item.to_dict() for item in spec.expect]},
204
+ sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
205
+ return BusinessBinding(spec, bundle, tuple(fields), seed, hashlib.sha256(canonical).hexdigest())
206
+
207
+
208
+ def evaluate_business(binding, state):
209
+ """All comparisons use one app-produced snapshot; JSON types must match exactly."""
210
+ def equal(actual, expected):
211
+ if type(actual) is bool or type(expected) is bool:
212
+ return type(actual) is type(expected) and actual == expected
213
+ if type(actual) in (int, float) and type(expected) in (int, float):
214
+ return actual == expected
215
+ return type(actual) is type(expected) and actual == expected
216
+
217
+ return [{"field": item.field, "ok": item.field in state and equal(state[item.field], item.equals),
218
+ "observed": (state[item.field] if type(state[item.field]) is bool or state[item.field] is None
219
+ else f"[{type(state[item.field]).__name__} redacted]") if item.field in state else None,
220
+ "present": item.field in state}
221
+ for item in binding.spec.expect]
@@ -0,0 +1,131 @@
1
+ """One VerifyRun's registered app-state fixture and evidence lifecycle."""
2
+
3
+ import secrets
4
+ from pathlib import Path
5
+
6
+ from .assertions import CheckError
7
+ from .business import FILENAME, evaluate_business, load_business_binding
8
+ from .checks import project_root
9
+ from .verdict import CouldntRun
10
+
11
+
12
+ def _seeds_reserved_file(seed):
13
+ reserved = ("documents", FILENAME.casefold())
14
+ for source, destination in seed.files:
15
+ parts = tuple(part.casefold() for part in Path(destination).parts)
16
+ if parts[:2] == reserved:
17
+ return True
18
+ if not source.is_dir() or parts != reserved[:len(parts)]:
19
+ continue
20
+ current = source
21
+ for component in reserved[len(parts):]:
22
+ current = next((child for child in current.iterdir() if child.name.casefold() == component), None)
23
+ if current is None:
24
+ break
25
+ if current is not None:
26
+ return True
27
+ return False
28
+
29
+
30
+ class BusinessRun:
31
+ def _load_business(self):
32
+ if self.check.business is None:
33
+ return
34
+ if self.check.reset_level() == "none":
35
+ raise CouldntRun("business", "App-state fixtures need reset: data or reinstall.",
36
+ "Set reset: data or reset: reinstall in the saved check.")
37
+ if self.check.source is None:
38
+ raise CouldntRun("business", "An app-state check must be a saved project check file.",
39
+ "Save the check under a project with .mobster/fixtures.json, then run again.")
40
+ try:
41
+ collision = _seeds_reserved_file(self.check.seed)
42
+ except OSError:
43
+ raise CouldntRun("business", "A YAML fixture's seed files could not be inspected before reset.",
44
+ "Fix the YAML fixture, then run the check again.") from None
45
+ if collision:
46
+ raise CouldntRun("business", "A YAML fixture seeds the reserved app-state file.",
47
+ "Remove that file from the YAML fixture before running this check.")
48
+ try:
49
+ current = load_business_binding(self.check, project_root(self.check.source))
50
+ except CheckError as error:
51
+ raise CouldntRun("business", str(error), "Fix .mobster/fixtures.json, then run again.") from None
52
+ if self.business_binding is not None and current.digest != self.business_binding.digest:
53
+ raise CouldntRun("business", "The enrolled app-state fixture changed before the run.",
54
+ "Re-enroll the project fixture, then run again.")
55
+ self.business_binding = current
56
+ self.business_result = {"adapter": current.spec.adapter, "fixture": current.spec.fixture,
57
+ "status": "unavailable", "schemaVersion": "mobster.business/1",
58
+ "bindingSha256": current.digest, "assertions": [],
59
+ "setup": "not_started", "cleanup": "not_needed"}
60
+
61
+ def _setup_business(self, manager):
62
+ if self.business_binding is None:
63
+ return
64
+ from .business_state import AppContainerFixture, FixtureError
65
+ self.business_fixture = AppContainerFixture(self.business_binding, manager, self.target, self.actions,
66
+ self.run_id, secrets.token_hex(16))
67
+ with self.actions.action("BUSINESS_QUIESCE", operation_id=f"{self.run_id}:business:preseed"):
68
+ self.business_fixture.quiesce()
69
+ self.business_result["setup"] = "pending"
70
+ try:
71
+ self.business_fixture.setup()
72
+ except (FixtureError, OSError) as error:
73
+ self.business_result["setup"] = "needs_reconciliation" if self.actions.blocked else "failed"
74
+ raise CouldntRun("business", f"The app-state fixture could not be seeded ({type(error).__name__}).",
75
+ "Inspect the fixture file and simulator, then start a new run.") from None
76
+ self.business_result["setup"] = "seeded"
77
+
78
+ def _take_business_baseline(self):
79
+ if self.business_fixture is None:
80
+ return
81
+ from .business_state import FixtureError
82
+ try:
83
+ state, generation, digest = self.business_fixture.observe()
84
+ except (FixtureError, OSError) as error:
85
+ raise CouldntRun("business_evidence", f"The app-state baseline is unavailable ({type(error).__name__}).",
86
+ "Check that the app publishes its persisted state after launch.") from None
87
+ self._business_baseline = (generation, digest)
88
+ self.baseline["businessHeld"] = [item["ok"] for item in evaluate_business(self.business_binding, state)]
89
+
90
+ def _observe_business(self, decided):
91
+ if self.business_fixture is None:
92
+ return None
93
+ from .business_state import FixtureError
94
+ try:
95
+ state, generation, digest = self.business_fixture.observe()
96
+ except (FixtureError, OSError) as error:
97
+ self.business_result["error"] = "App-produced state was missing, stale or malformed"
98
+ return {**decided, "verdict": "couldnt_run", "klass": "business_evidence",
99
+ "message": f"The app-state observation is unavailable ({type(error).__name__}).",
100
+ "fix": "Check the app's fixture producer and run a new check."}
101
+ if self._business_baseline is not None:
102
+ before_generation, before_digest = self._business_baseline
103
+ if generation < before_generation or generation == before_generation and digest != before_digest:
104
+ self.business_result["error"] = "The app-state generation moved backward or changed in place"
105
+ return {**decided, "verdict": "couldnt_run", "klass": "business_evidence",
106
+ "message": "The app-state observation has a stale generation.",
107
+ "fix": "Make the app increment generation for every persisted snapshot."}
108
+ self.business_result.update(status="observed", generation=generation, sourceSha256=digest,
109
+ assertions=evaluate_business(self.business_binding, state))
110
+ return None
111
+
112
+ def _cleanup_business(self):
113
+ if self.business_fixture is None or self._business_cleanup_done:
114
+ return None
115
+ self._business_cleanup_done = True
116
+ if self.actions.blocked or self.error is not None and self.error["klass"] == "unknown_outcome":
117
+ self.business_result["cleanup"] = "needs_reconciliation"
118
+ return None
119
+ try:
120
+ status = self.business_fixture.remove()
121
+ except BaseException as error:
122
+ self.business_result["cleanup"] = "needs_reconciliation"
123
+ failure = self._action_failure()
124
+ return failure or CouldntRun("business_cleanup",
125
+ f"The app-state fixture was not confirmed removed ({type(error).__name__}).",
126
+ "Inspect the simulator before another run.")
127
+ self.business_result["cleanup"] = status
128
+ if status == "needs_reconciliation":
129
+ return CouldntRun("business_cleanup", "The app-state fixture was not confirmed removed.",
130
+ "Inspect the simulator before another run.")
131
+ return None