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,316 @@
1
+ """A webhook destination's address, the JSON a run sends it, the signature, and one POST."""
2
+
3
+ import base64
4
+ import datetime
5
+ import hashlib
6
+ import hmac
7
+ import io
8
+ import ipaddress
9
+ import json
10
+ import time
11
+ from urllib.parse import urlsplit
12
+
13
+ from .. import secret_filter
14
+ from . import net
15
+
16
+ TIMEOUT = 10.0
17
+ URL_MAX = 2000
18
+ SECRET_MAX = 200
19
+ NAME_MAX = 60
20
+ ANSWER_MAX = 20_000
21
+ TEXT_MAX = 3000
22
+ SCREENSHOT_SIDE = 1280
23
+ USER_AGENT = "Mobster-Webhooks/1"
24
+ # The run statuses that finished the task: no reason goes with them.
25
+ DONE = frozenset({"completed", "completed_unverified", "user_condition_met"})
26
+ LOCAL_SUFFIXES = (".local", ".localhost", ".lan", ".home.arpa", ".internal")
27
+
28
+
29
+ class AddressError(ValueError):
30
+ """Why an address can't take results, in one sentence that never repeats the address."""
31
+
32
+
33
+ def _literal(host):
34
+ try:
35
+ return ipaddress.ip_address(host.strip("[]"))
36
+ except ValueError:
37
+ return None
38
+
39
+
40
+ def _private(host):
41
+ """This Mac or a private network: localhost, a name with no dot or a .local one, or a loopback, private or
42
+ link-local address."""
43
+ if host == "localhost" or host.endswith(LOCAL_SUFFIXES) or ("." not in host and ":" not in host):
44
+ return True
45
+ address = _literal(host)
46
+ if address is None:
47
+ return False
48
+ if isinstance(address, ipaddress.IPv6Address) and address.ipv4_mapped:
49
+ address = address.ipv4_mapped
50
+ return address.is_loopback or address.is_private or address.is_link_local
51
+
52
+
53
+ def validate_url(value):
54
+ """``value`` trimmed, when results may go there; AddressError otherwise."""
55
+ text = value.strip() if isinstance(value, str) else ""
56
+ if not text:
57
+ raise AddressError("Paste the webhook's address.")
58
+ if len(text) > URL_MAX or any(ord(c) < 33 or ord(c) == 127 for c in text):
59
+ raise AddressError("That address isn't a web address.")
60
+ parts = urlsplit(text)
61
+ scheme = parts.scheme.lower()
62
+ if scheme not in ("https", "http"):
63
+ raise AddressError("Use an address that starts with https://.")
64
+ if parts.username or parts.password:
65
+ raise AddressError("Put the secret in Secret, not in the address.")
66
+ host = (parts.hostname or "").rstrip(".").lower()
67
+ if not host:
68
+ raise AddressError("That address has no host.")
69
+ try:
70
+ parts.port
71
+ except ValueError:
72
+ raise AddressError("That address has a port Mobster can't use.") from None
73
+ if scheme == "http" and not _private(host):
74
+ raise AddressError("Use https:// for an address on the internet. http:// works for this Mac and your own "
75
+ "network.")
76
+ return text
77
+
78
+
79
+ def hint(url):
80
+ """What the app shows of an address: its scheme, host and first path segment ("https://hooks.zapier.com/hooks/…").
81
+ The rest often carries the address's own key."""
82
+ parts = urlsplit(url)
83
+ host = parts.hostname or ""
84
+ if ":" in host:
85
+ host = f"[{host}]"
86
+ if parts.port:
87
+ host += f":{parts.port}"
88
+ segments = [s for s in parts.path.split("/") if s]
89
+ shown = f"{parts.scheme.lower()}://{host}"
90
+ if segments:
91
+ first = segments[0] if len(segments[0]) <= 24 else segments[0][:24] + "…"
92
+ shown += "/" + first + ("/…" if len(segments) > 1 or parts.query else "")
93
+ elif parts.query:
94
+ shown += "/…"
95
+ return shown
96
+
97
+
98
+ host_of = net.host_of
99
+
100
+
101
+ def validate_secret(value):
102
+ if value is None or value == "":
103
+ return None
104
+ if (not isinstance(value, str) or not 8 <= len(value) <= SECRET_MAX
105
+ or any(ord(c) < 33 or ord(c) == 127 for c in value)):
106
+ raise ValueError("A secret is 8 to 200 characters with no spaces.")
107
+ return value
108
+
109
+
110
+ def validate_name(value, example="My server"):
111
+ """The destination's name, spaces folded. ``example``: a name for its kind, for the error (kinds.Kind.example)."""
112
+ text = " ".join(value.split()) if isinstance(value, str) else ""
113
+ if not 1 <= len(text) <= NAME_MAX:
114
+ raise ValueError(f"Name the destination in 1 to {NAME_MAX} characters, like {example}.")
115
+ return text
116
+
117
+
118
+ def iso(seconds):
119
+ if not isinstance(seconds, (int, float)):
120
+ return None
121
+ return datetime.datetime.fromtimestamp(seconds, datetime.timezone.utc).isoformat(timespec="seconds").replace(
122
+ "+00:00", "Z")
123
+
124
+
125
+ def masked(value, depth=0):
126
+ """``value`` with secret_filter.redact over every string inside it (keys too). Depth past 20 becomes None."""
127
+ if depth > 20:
128
+ return None
129
+ if isinstance(value, str):
130
+ return secret_filter.redact(value)
131
+ if isinstance(value, dict):
132
+ return {secret_filter.redact(str(k)): masked(v, depth + 1) for k, v in value.items()}
133
+ if isinstance(value, (list, tuple)):
134
+ return [masked(v, depth + 1) for v in value]
135
+ if isinstance(value, bool) or value is None or isinstance(value, (int, float)):
136
+ return value
137
+ return secret_filter.redact(str(value))
138
+
139
+
140
+ def answer_of(summary):
141
+ """The run's whole answer as text: the answer it wrote in full, else its text data, else its headline."""
142
+ for key in ("full_answer", "data", "answer"):
143
+ value = summary.get(key)
144
+ if isinstance(value, str) and value.strip():
145
+ return value.strip()[:ANSWER_MAX]
146
+ return None
147
+
148
+
149
+ def data_of(summary):
150
+ """The structured data: an output schema's (or a format's) data once it was validated; None for a text answer."""
151
+ data = summary.get("data")
152
+ if summary.get("data_status") == "validated" and data is not None and not isinstance(data, str):
153
+ return data
154
+ return None
155
+
156
+
157
+ def line_of(title, status, answer, reason):
158
+ """``text``: the answer; without one, Done or why it couldn't finish."""
159
+ if answer:
160
+ said = answer
161
+ elif status in DONE:
162
+ said = "Done."
163
+ else:
164
+ said = f"Couldn't finish. {reason}" if reason else "Couldn't finish."
165
+ text = f"{title}: {' '.join(said.split())}"
166
+ return text if len(text) <= TEXT_MAX else text[:TEXT_MAX - 1] + "…"
167
+
168
+
169
+ def payload(run, workflow=None, screenshot=None):
170
+ """The body for ``run``: an object as the module docstring shows."""
171
+ summary = run.summary if isinstance(getattr(run, "summary", None), dict) else {}
172
+ status = getattr(run, "status", None) or summary.get("status") or "error"
173
+ answer = masked(answer_of(summary))
174
+ reason = summary.get("reason") if status not in DONE else None
175
+ reason = masked(" ".join(reason.split())) if isinstance(reason, str) and reason.strip() else None
176
+ flow = None
177
+ if isinstance(workflow, dict) and workflow.get("name"):
178
+ blanks = workflow.get("blanks") if isinstance(workflow.get("blanks"), dict) else {}
179
+ flow = {"id": workflow.get("id"), "name": masked(workflow["name"]), "blanks": masked(blanks)}
180
+ body = {"type": "run.finished", "runId": run.id, "status": status, "answer": answer,
181
+ "data": masked(data_of(summary)), "reason": reason, "startedAt": iso(getattr(run, "created_at", None)),
182
+ "finishedAt": iso(getattr(run, "finished_at", None)), "workflow": flow,
183
+ "text": line_of(f"Mobster › {flow['name']}" if flow else "Mobster", status, answer, reason)}
184
+ if screenshot:
185
+ body["screenshot"] = {"contentType": "image/jpeg", "base64": base64.b64encode(screenshot).decode()}
186
+ return body
187
+
188
+
189
+ def started_payload(run, workflow=None):
190
+ """run.started: which run, when, and its workflow when it came from one. No task words."""
191
+ flow = _flow(workflow)
192
+ title = f"Mobster › {flow['name']}" if flow else "Mobster"
193
+ return {"type": "run.started", "runId": run.id, "startedAt": iso(getattr(run, "created_at", None)),
194
+ "workflow": flow, "text": f"{title}: a task started."}
195
+
196
+
197
+ # approval_requested kinds (server.request_approval) and what a needs_you event calls them.
198
+ NEED_KINDS = {"action": "approval", "commit": "approval", "proposals": "approval", "clarify": "question",
199
+ "loop": "question", "question": "question", "plan": "plan"}
200
+ QUESTION_MAX = 300
201
+
202
+
203
+ def needs_payload(run, event, workflow=None, now=None):
204
+ """needs_you: the run is waiting for an answer in the app. ``event`` is its approval_requested event."""
205
+ now = time.time() if now is None else now
206
+ flow = _flow(workflow)
207
+ # What the card asks, masked: never the message, a typed answer or anything the card doesn't say.
208
+ asked = " ".join(str(event.get("title") or event.get("label") or "").split())[:QUESTION_MAX]
209
+ question = secret_filter.redact(asked) or "Mobster needs your answer in the app."
210
+ title = f"Mobster › {flow['name']}" if flow else "Mobster"
211
+ return {"type": "needs_you", "runId": run.id, "kind": NEED_KINDS.get(event.get("kind") or "action", "approval"),
212
+ "question": question, "askedAt": iso(now), "workflow": flow,
213
+ "text": f"{title} needs you: {question}"[:TEXT_MAX]}
214
+
215
+
216
+ def _flow(workflow):
217
+ if isinstance(workflow, dict) and workflow.get("name"):
218
+ blanks = workflow.get("blanks") if isinstance(workflow.get("blanks"), dict) else {}
219
+ return {"id": workflow.get("id"), "name": masked(workflow["name"]), "blanks": masked(blanks)}
220
+ return None
221
+
222
+
223
+ def test_payload(now=None):
224
+ now = time.time() if now is None else now
225
+ return {"type": "test", "runId": None, "status": "completed",
226
+ "answer": "This is a test from Mobster. A finished task's answer goes here.", "data": None, "reason": None,
227
+ "startedAt": iso(now), "finishedAt": iso(now), "workflow": None,
228
+ "text": "Mobster: this is a test. Results from your tasks will arrive here."}
229
+
230
+
231
+ def slack_text(text):
232
+ """Slack's escaping for its text field: &, < and > only."""
233
+ return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
234
+
235
+
236
+ def encode_body(url, body):
237
+ """The bytes to send. Slack's incoming webhooks read ``text`` as their own markup, so it's escaped for them."""
238
+ host = host_of(url)
239
+ if host == "hooks.slack.com" or host.endswith(".slack.com"):
240
+ body = {**body, "text": slack_text(body.get("text") or "")}
241
+ return json.dumps(body, ensure_ascii=False, allow_nan=False, separators=(",", ":")).encode("utf-8")
242
+
243
+
244
+ def signature(secret, timestamp, data):
245
+ """v1: HMAC-SHA256 with ``secret`` over ``<timestamp>.<body>``, in hex."""
246
+ message = str(int(timestamp)).encode() + b"." + data
247
+ return hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
248
+
249
+
250
+ def headers(event, delivery_id, data, secret=None, timestamp=None):
251
+ timestamp = int(time.time() if timestamp is None else timestamp)
252
+ out = {"Content-Type": "application/json", "User-Agent": USER_AGENT, "Mobster-Event": event,
253
+ "Mobster-Delivery": delivery_id, "Mobster-Timestamp": str(timestamp)}
254
+ if secret:
255
+ out["Mobster-Signature"] = f"t={timestamp},v1={signature(secret, timestamp, data)}"
256
+ return out
257
+
258
+
259
+ def screenshot_of(run, frames=None):
260
+ """The run's last screen as JPEG bytes (at most SCREENSHOT_SIDE px), or None: the final full screenshot while the
261
+ app still has it, else the last step's saved frame."""
262
+ image = getattr(run, "image", None)
263
+ if isinstance(image, str) and image.startswith("data:image/") and "," in image:
264
+ try:
265
+ raw = base64.b64decode(image.split(",", 1)[1])
266
+ from PIL import Image
267
+ picture = Image.open(io.BytesIO(raw)).convert("RGB")
268
+ picture.thumbnail((SCREENSHOT_SIDE, SCREENSHOT_SIDE))
269
+ out = io.BytesIO()
270
+ picture.save(out, "JPEG", quality=80)
271
+ return out.getvalue()
272
+ except Exception: # noqa: BLE001 -- the step frame below, or none
273
+ pass
274
+ if frames is None:
275
+ return None
276
+ for event in reversed(list(getattr(run, "events", None) or ())):
277
+ frame = event.get("frameId") if isinstance(event, dict) else None
278
+ if isinstance(frame, str):
279
+ try:
280
+ data = frames.get(run.id, frame)
281
+ except Exception: # noqa: BLE001
282
+ data = None
283
+ if data:
284
+ return data
285
+ return None
286
+
287
+
288
+ class Outcome:
289
+ """What one try came to: ``ok``, ``retry`` (worth another try), the HTTP ``status`` and a ``message``. ``unsent``:
290
+ the try surely never reached the destination, so another can't make a duplicate."""
291
+
292
+ def __init__(self, ok, retry, status=None, message=None, unsent=False):
293
+ self.ok, self.retry, self.status, self.message, self.unsent = ok, retry, status, message, unsent
294
+
295
+ def __repr__(self):
296
+ return f"Outcome(ok={self.ok}, retry={self.retry}, status={self.status}, message={self.message!r})"
297
+
298
+
299
+ def post(url, data, request_headers, timeout=TIMEOUT, connect=net.connection):
300
+ """POST ``data`` to ``url`` once. Never raises; never names the address beyond its host."""
301
+ try:
302
+ reply = net.call("POST", url, data, request_headers, timeout, connect, read_limit=4096)
303
+ except net.NetError as error:
304
+ return Outcome(False, error.retry, None, error.message, unsent=error.unsent)
305
+ return judge(reply.status)
306
+
307
+
308
+ def judge(status):
309
+ if 200 <= status < 300:
310
+ return Outcome(True, False, status, None)
311
+ if 300 <= status < 400:
312
+ return Outcome(False, False, status, f"The address answered {status}, a redirect. Use the address it points "
313
+ "to.")
314
+ if status in (408, 425, 429) or status >= 500:
315
+ return Outcome(False, True, status, f"The address answered {status}.")
316
+ return Outcome(False, False, status, f"The address answered {status}. Check it in Settings › Connectors.")
@@ -0,0 +1,141 @@
1
+ """Result presentation contracts, and the bounded JSON Schema subset a task's output may declare.
2
+
3
+ The schema subset is safe on purpose: it never fetches references, executes regular expressions or infers missing
4
+ values.
5
+ """
6
+
7
+ import json
8
+
9
+ from .transport import decode_json
10
+
11
+ KEYWORDS = frozenset({"$schema", "type", "properties", "required", "additionalProperties",
12
+ "items", "minItems", "maxItems", "uniqueItems", "minLength", "maxLength", "minimum",
13
+ "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf", "enum", "const",
14
+ "title", "description"})
15
+ TYPES = frozenset({"object", "array", "string", "number", "integer", "boolean", "null"})
16
+
17
+
18
+ def bounded_json(value, *, max_bytes=32000, max_nodes=2000, max_depth=12):
19
+ pending, count = [(value, 0)], 0
20
+ while pending:
21
+ node, depth = pending.pop()
22
+ count += 1
23
+ if count > max_nodes or depth > max_depth:
24
+ raise ValueError("JSON exceeds structural limits")
25
+ if type(node) is dict:
26
+ if any(not isinstance(key, str) for key in node):
27
+ raise ValueError("JSON object keys must be strings")
28
+ pending.extend((child, depth + 1) for child in node.values())
29
+ elif type(node) is list:
30
+ pending.extend((child, depth + 1) for child in node)
31
+ elif node is not None and type(node) not in (str, int, float, bool):
32
+ raise ValueError("Result contains a non-JSON value")
33
+ try:
34
+ wire = json.dumps(value, allow_nan=False, sort_keys=True, separators=(",", ":"))
35
+ if len(wire.encode()) > max_bytes:
36
+ raise ValueError("JSON exceeds byte limit")
37
+ return decode_json(wire)
38
+ except (TypeError, OverflowError, UnicodeError):
39
+ raise ValueError("Invalid JSON value") from None
40
+
41
+
42
+ def validate_schema(schema):
43
+ schema = bounded_json(schema, max_bytes=16000, max_nodes=500, max_depth=12)
44
+ if not isinstance(schema, dict):
45
+ raise ValueError("Output schema must be a JSON Schema object")
46
+ try:
47
+ from jsonschema import Draft202012Validator
48
+ except ImportError:
49
+ raise ValueError("Structured output requires mobile_agent/requirements.txt") from None
50
+
51
+ def inspect(node, depth=0):
52
+ if not isinstance(node, dict) or depth > 6 or set(node) - KEYWORDS:
53
+ raise ValueError("Unsupported output schema keyword or nesting depth")
54
+ declared = node.get("type")
55
+ kinds = declared if isinstance(declared, list) else [declared]
56
+ if not kinds or any(not isinstance(kind, str) or kind not in TYPES for kind in kinds):
57
+ raise ValueError("Every output schema node requires an explicit supported type")
58
+ if "$schema" in node and node["$schema"] != "https://json-schema.org/draft/2020-12/schema":
59
+ raise ValueError("Only JSON Schema Draft 2020-12 is supported")
60
+ if "object" in kinds:
61
+ properties = node.get("properties", {})
62
+ if (not isinstance(properties, dict) or len(properties) > 40
63
+ or node.get("additionalProperties") is not False):
64
+ raise ValueError("Objects require bounded properties and additionalProperties:false")
65
+ required = node.get("required", [])
66
+ if (not isinstance(required, list)
67
+ or any(not isinstance(key, str) or key not in properties for key in required)):
68
+ raise ValueError("Required properties must have a defined schema")
69
+ for child in properties.values():
70
+ inspect(child, depth + 1)
71
+ elif "properties" in node or "required" in node or "additionalProperties" in node:
72
+ raise ValueError("Object keywords require object type")
73
+ if "array" in kinds:
74
+ if "items" not in node:
75
+ raise ValueError("Arrays require a bounded item schema")
76
+ inspect(node["items"], depth + 1)
77
+ if type(node.get("maxItems")) is not int or not 0 <= node["maxItems"] <= 100:
78
+ raise ValueError("Arrays require maxItems between 0 and 100")
79
+ elif "items" in node:
80
+ raise ValueError("Array items require array type")
81
+ if "enum" in node and (not isinstance(node["enum"], list) or not 1 <= len(node["enum"]) <= 100):
82
+ raise ValueError("Enum exceeds supported bounds")
83
+
84
+ inspect(schema)
85
+ try:
86
+ Draft202012Validator.check_schema(schema)
87
+ except Exception:
88
+ raise ValueError("Invalid JSON Schema") from None
89
+ return schema
90
+
91
+
92
+
93
+ FORMATS = frozenset({"text", "json", "yaml", "csv", "markdown"})
94
+ SCALAR_TYPES = frozenset({"string", "number", "integer", "boolean", "null"})
95
+
96
+
97
+ def validate_format(output_format):
98
+ """An actual result format; Auto is a request policy, never a serializer."""
99
+ if not isinstance(output_format, str) or output_format not in FORMATS:
100
+ raise ValueError("Choose Text, JSON, YAML, CSV, or Markdown output")
101
+
102
+
103
+ def validate_output(output_format, schema):
104
+ if output_format == "auto":
105
+ if schema is not None:
106
+ raise ValueError("Choose a structured format to supply a custom schema")
107
+ return None
108
+ validate_format(output_format)
109
+ if output_format == "text" and schema is not None:
110
+ raise ValueError("Choose a structured format to supply a custom schema")
111
+ schema = validate_schema(schema) if schema is not None else None
112
+ if output_format != "csv" or schema is None:
113
+ return schema
114
+ return _validate_csv_shape(schema)
115
+
116
+
117
+ def validate_automatic_schema(output_format, schema):
118
+ """A model-selected schema is mandatory and bounded, including plain-text answers."""
119
+ validate_format(output_format)
120
+ schema = validate_schema(schema)
121
+ if output_format == "csv":
122
+ return _validate_csv_shape(schema)
123
+ if output_format == "text" and schema["type"] != "string":
124
+ # Automatic text is one string or one flat object; a table is CSV-only.
125
+ if schema["type"] == "array":
126
+ raise ValueError("Text requires a string or a flat object with named columns")
127
+ return _validate_csv_shape(schema)
128
+ return schema
129
+
130
+
131
+ def _validate_csv_shape(schema):
132
+ row = schema.get("items") if schema.get("type") == "array" else schema
133
+ if (not isinstance(row, dict) or row.get("type") != "object"
134
+ or row.get("additionalProperties") is not False or not row.get("properties")):
135
+ raise ValueError("CSV requires a flat object or an array of flat objects with named columns")
136
+ for column in row["properties"].values():
137
+ declared = column["type"]
138
+ kinds = declared if isinstance(declared, list) else [declared]
139
+ if any(kind not in SCALAR_TYPES for kind in kinds):
140
+ raise ValueError("CSV columns must be scalar values, not nested objects or arrays")
141
+ return schema
@@ -0,0 +1,211 @@
1
+ """Whether the person who owns an iPhone is using it, so Mobster never grabs it from them."""
2
+
3
+ import threading
4
+ import time
5
+
6
+ POLL_INTERVAL_S = 15.0 # between read-only polls of one phone
7
+ READ_TIMEOUT_S = 3.0
8
+ ACTION_QUIET_S = 30.0 # a change this soon after a Mobster action is Mobster's own
9
+ OWNER_QUIET_S = 120.0 # owner_active ends after this long with no change
10
+ IDLE_AFTER_S = 300.0 # a phone first seen, or unchanged, is idle after this long
11
+ IDLE_RELEASE_S = 120.0 # idle for this long: stop live view, prewarm and the awake hold
12
+
13
+ OWNER_ACTIVE_REFUSAL = ("You're using your iPhone right now, so Mobster didn't start this task. "
14
+ "No action was taken. It can start once you put the phone down.")
15
+
16
+
17
+ class OwnerPresence:
18
+ """One phone's presence. ``observe`` is the only input besides ``note_mobster_action``/``run_started``."""
19
+
20
+ def __init__(self, clock=time.time):
21
+ self.clock = clock
22
+ self.lock = threading.Lock()
23
+ self.state, self.since = "unknown", self._ms(clock())
24
+ self.baseline = None # (locked, front) at the last read
25
+ self.first_seen = None
26
+ self.last_change = None
27
+ self.last_action = None
28
+ self.holding = False
29
+ self.idle_since = None
30
+ self.released = False # idle release already done for this idle stretch
31
+
32
+ @staticmethod
33
+ def _ms(seconds):
34
+ return int(seconds * 1000)
35
+
36
+ def _set(self, state, now):
37
+ if state != self.state:
38
+ self.state, self.since = state, self._ms(now)
39
+ self.released = False
40
+ self.idle_since = now if state == "idle" else None
41
+
42
+ # -- inputs -----------------------------------------------------------------------------------
43
+
44
+ def note_mobster_action(self, now=None):
45
+ """Mobster acted on the phone (a run's step, an MCP tap, manual control): a change soon after is its own."""
46
+ with self.lock:
47
+ self.last_action = self.clock() if now is None else now
48
+
49
+ def run_started(self, now=None):
50
+ with self.lock:
51
+ now = self.clock() if now is None else now
52
+ self.holding, self.last_action = True, now
53
+ self._set("mobster", now)
54
+
55
+ def run_finished(self, locked=None, front=None, now=None):
56
+ """The run ended. What the phone shows now is Mobster's doing, not the owner's: it becomes the baseline."""
57
+ with self.lock:
58
+ now = self.clock() if now is None else now
59
+ self.holding, self.last_action = False, now
60
+ if locked is not None or front is not None:
61
+ self.baseline = (locked, front)
62
+ self.last_change = now
63
+ self._set("idle", now)
64
+
65
+ def observe(self, *, held=False, ok=True, locked=None, front=None, now=None):
66
+ with self.lock:
67
+ now = self.clock() if now is None else now
68
+ if held:
69
+ self.holding, self.last_action = True, now
70
+ self._set("mobster", now)
71
+ return self.state
72
+ if self.holding:
73
+ # A hold just ended without run_finished (another process's lease): treat it as a finished run.
74
+ self.holding, self.last_action = False, now
75
+ if ok:
76
+ self.baseline, self.last_change = (locked, front), now
77
+ self._set("idle", now)
78
+ return self.state
79
+ if not ok or not isinstance(locked, bool):
80
+ self._set("unknown", now)
81
+ return self.state
82
+ reading = (locked, front)
83
+ if self.first_seen is None:
84
+ self.first_seen = now
85
+ if self.baseline is None:
86
+ self.baseline, self.last_change = reading, now
87
+ elif reading != self.baseline:
88
+ self.baseline = reading
89
+ # Only a person unlocks a phone or switches its app. The phone locking itself (auto-lock) is not use.
90
+ if locked is False:
91
+ self.last_change = now
92
+ own = self.last_action is not None and now - self.last_action <= ACTION_QUIET_S
93
+ if not own:
94
+ self._set("owner_active", now)
95
+ return self.state
96
+ quiet = now - (self.last_change if self.last_change is not None else self.first_seen)
97
+ if self.state == "owner_active":
98
+ if quiet >= OWNER_QUIET_S:
99
+ self._set("idle", now)
100
+ elif quiet >= IDLE_AFTER_S or self.state == "idle":
101
+ self._set("idle", now)
102
+ else:
103
+ self._set("unknown", now)
104
+ return self.state
105
+
106
+ # -- outputs ----------------------------------------------------------------------------------
107
+
108
+ def is_owner_active(self):
109
+ with self.lock:
110
+ return self.state == "owner_active"
111
+
112
+ def should_release(self, now=None):
113
+ with self.lock:
114
+ now = self.clock() if now is None else now
115
+ if self.state != "idle" or self.released or self.idle_since is None:
116
+ return False
117
+ if now - self.idle_since < IDLE_RELEASE_S:
118
+ return False
119
+ self.released = True
120
+ return True
121
+
122
+ def snapshot(self):
123
+ with self.lock:
124
+ return {"state": self.state, "since": self.since}
125
+
126
+
127
+ # -- the reads ---------------------------------------------------------------------------------------
128
+
129
+
130
+ def read_phone(wda_url, timeout=READ_TIMEOUT_S):
131
+ """(ok, locked, front): the two read-only sessionless reads. Never raises."""
132
+ from .transport import read_kept # the helper's kept connection: a connection a poll ran WDA out of descriptors
133
+ try:
134
+ locked = read_kept(wda_url, "/wda/locked", timeout=timeout).get("value")
135
+ front = None
136
+ if locked is False:
137
+ value = read_kept(wda_url, "/wda/activeAppInfo", timeout=timeout).get("value")
138
+ bundle = value.get("bundleId") if isinstance(value, dict) else None
139
+ front = bundle if isinstance(bundle, str) and bundle else None
140
+ return isinstance(locked, bool), locked, front
141
+ except Exception: # noqa: BLE001 -- an unanswered phone is "unknown", never an error
142
+ return False, None, None
143
+
144
+
145
+ def lease_held(wda_url):
146
+ """Whether a Mobster process holds this phone's lease now (taken and let go at once). Fails to True."""
147
+ from .journal import Lease
148
+ try:
149
+ lease = Lease.device(wda_url.rstrip("/"), wait=0)
150
+ except Exception: # noqa: BLE001 -- can't tell: treat it as held, never as free
151
+ return True
152
+ lease.close()
153
+ return False
154
+
155
+
156
+ class Poller:
157
+ """Polls one phone's presence lazily: at most every POLL_INTERVAL_S, and never reads while a run holds it."""
158
+
159
+ def __init__(self, presence, wda_url, busy=lambda: False, reader=read_phone, lease=lease_held,
160
+ monotonic=time.monotonic):
161
+ self.presence, self.wda_url, self.busy = presence, wda_url, busy
162
+ self.reader, self.lease, self.monotonic = reader, lease, monotonic
163
+ self.polled_at = None
164
+
165
+ def poll(self, force=False):
166
+ now = self.monotonic()
167
+ if not force and self.polled_at is not None and now - self.polled_at < POLL_INTERVAL_S:
168
+ return self.presence.snapshot()
169
+ self.polled_at = now
170
+ try:
171
+ held = bool(self.busy()) or self.lease(self.wda_url)
172
+ except Exception: # noqa: BLE001
173
+ held = True
174
+ from .wda.watchdog import tracker
175
+ # an MCP tap or a manual-control gesture is Mobster's, not the owner's
176
+ mine = tracker.last_action_at(self.wda_url)
177
+ if mine is not None:
178
+ self.presence.note_mobster_action(mine)
179
+ if held:
180
+ self.presence.observe(held=True)
181
+ else:
182
+ ok, locked, front = self.reader(self.wda_url)
183
+ self.presence.observe(ok=ok, locked=locked, front=front)
184
+ return self.presence.snapshot()
185
+
186
+
187
+ # -- a registry, so a guard or an MCP tool finds a phone's presence from its WDA address --------------
188
+
189
+ _registry = {}
190
+ _registry_lock = threading.Lock()
191
+
192
+
193
+ def register(wda_url, presence):
194
+ with _registry_lock:
195
+ _registry[(wda_url or "").rstrip("/")] = presence
196
+
197
+
198
+ def presence_for(wda_url):
199
+ with _registry_lock:
200
+ return _registry.get((wda_url or "").rstrip("/"))
201
+
202
+
203
+ def is_owner_active(wda_url):
204
+ """Whether the owner is using the phone at ``wda_url``. False when nothing tracks it."""
205
+ presence = presence_for(wda_url)
206
+ return bool(presence is not None and presence.is_owner_active())
207
+
208
+
209
+ def forget(wda_url):
210
+ with _registry_lock:
211
+ _registry.pop((wda_url or "").rstrip("/"), None)