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,212 @@
1
+ """The profiles API (Settings › Profiles), registered under /api/accounts (api_routes, seam S3)."""
2
+
3
+ import re
4
+
5
+ from .. import harness_api
6
+ from ..api_errors import APIError
7
+ from ..api_routes import Request, Response, Route, register
8
+ from .store import AccountNotFound, AmbiguousAccount, display, resolve, store_for
9
+
10
+ KEY = re.compile(r"[A-Za-z0-9_.:-]{1,100}")
11
+ MAX_RUN_ACCOUNTS = 20
12
+ RUN_FIELDS = frozenset({"accounts", "goal", "appId", "engine", "outputFormat"})
13
+ NOT_HERE = "Change your profiles in Mobster on your Mac, in Settings › Profiles."
14
+
15
+
16
+ def _writable(request):
17
+ if request.origin in ("remote", "mcp"):
18
+ raise APIError(NOT_HERE, 403, "not_here")
19
+
20
+
21
+ def _device(runtime, value):
22
+ """(id, name) of the phone ``value`` names, or None for null. ValueError when no phone has that name."""
23
+ if value is None:
24
+ return None
25
+ fleet = getattr(runtime, "fleet", None)
26
+ if fleet is None:
27
+ raise ValueError("This Mobster runs every task on one phone. Leave the profile's phone out.")
28
+ try:
29
+ phone = fleet.find(value)
30
+ except LookupError as error:
31
+ raise ValueError(str(error)) from None
32
+ return phone.id, phone.name
33
+
34
+
35
+ def public(account):
36
+ return {**account, "display": display(account)}
37
+
38
+
39
+ def list_accounts(request: Request):
40
+ return Response.json({"accounts": [public(a) for a in store_for(request.runtime).all()]})
41
+
42
+
43
+ def _remember(account):
44
+ """A saved profile's voice and signature are memory facts, edited where the profile is (memory/writer.py)."""
45
+ from ..memory import writer
46
+ writer.from_profile(account)
47
+
48
+
49
+ def add_account(request: Request):
50
+ _writable(request)
51
+ body = request.body or {}
52
+ device = _device(request.runtime, body.get("device")) if isinstance(body, dict) else None
53
+ account = store_for(request.runtime).add(body, device)
54
+ _remember(account)
55
+ return Response.json({"account": public(account)}, 201)
56
+
57
+
58
+ def edit_account(request: Request):
59
+ _writable(request)
60
+ body = request.body or {}
61
+ device = _device(request.runtime, body.get("device")) if isinstance(body, dict) and "device" in body else None
62
+ try:
63
+ account = store_for(request.runtime).update(request.params["id"], body, device)
64
+ except AccountNotFound as error:
65
+ raise APIError(str(error), 404, "account_not_found") from None
66
+ _remember(account)
67
+ return Response.json({"account": public(account)})
68
+
69
+
70
+ def delete_account(request: Request):
71
+ _writable(request)
72
+ try:
73
+ store_for(request.runtime).remove(request.params["id"])
74
+ except AccountNotFound as error:
75
+ raise APIError(str(error), 404, "account_not_found") from None
76
+ from ..memory import writer
77
+ writer.profile_removed(request.params["id"])
78
+ return Response.json({"deleted": True})
79
+
80
+
81
+ def _key(request):
82
+ headers = request.headers
83
+ values = headers.get_all("Idempotency-Key", []) if hasattr(headers, "get_all") else (
84
+ [headers["Idempotency-Key"]] if headers.get("Idempotency-Key") else [])
85
+ if not values:
86
+ return None
87
+ if len(values) > 1 or not KEY.fullmatch(values[0]):
88
+ raise ValueError("Invalid Idempotency-Key")
89
+ return values[0]
90
+
91
+
92
+ def run_accounts(request: Request):
93
+ """One task per account."""
94
+ runtime, body = request.runtime, request.body or {}
95
+ if not isinstance(body, dict) or set(body) - RUN_FIELDS:
96
+ raise ValueError("A task for several profiles has accounts, goal, and optionally appId, engine and "
97
+ "outputFormat")
98
+ names = body.get("accounts")
99
+ if not isinstance(names, list) or not 1 <= len(names) <= MAX_RUN_ACCOUNTS:
100
+ raise ValueError(f"accounts is a list of 1 to {MAX_RUN_ACCOUNTS} profiles")
101
+ stored = store_for(runtime).all()
102
+ chosen = []
103
+ for name in names:
104
+ try:
105
+ account = resolve(stored, name)
106
+ except (AccountNotFound, AmbiguousAccount) as error:
107
+ raise APIError(str(error), 404 if isinstance(error, AccountNotFound) else 400,
108
+ "account_not_found" if isinstance(error, AccountNotFound) else "account_ambiguous") from None
109
+ if all(account["id"] != other["id"] for other in chosen):
110
+ chosen.append(account)
111
+ by_phone = {}
112
+ for account in chosen:
113
+ by_phone.setdefault(account.get("device") or "", []).append(account)
114
+ for together in by_phone.values():
115
+ if len(together) > 1:
116
+ where = together[0].get("deviceName") or "your default phone"
117
+ listed = " and ".join(display(a) for a in together)
118
+ raise APIError(f"{listed} are both on {where}, and a phone runs one task at a time. Run them one after "
119
+ "the other, or move one profile to another phone.", 409, "same_phone")
120
+ key = _key(request)
121
+ origin = request.origin if request.origin in harness_api.ORIGINS else "api"
122
+ rows = []
123
+ for account in chosen:
124
+ try:
125
+ created = runtime.create(body.get("appId"), body.get("goal"), "live",
126
+ f"{key}:{account['id']}" if key else None,
127
+ output_format=body.get("outputFormat", "auto"), engine=body.get("engine"),
128
+ extras={"account": account["id"]}, origin=origin)
129
+ except APIError as error:
130
+ rows.append({"account": account["id"], "display": display(account), "error": str(error),
131
+ "code": error.code})
132
+ continue
133
+ except ValueError as error:
134
+ if len(chosen) == 1:
135
+ raise
136
+ rows.append({"account": account["id"], "display": display(account), "error": str(error),
137
+ "code": "invalid"})
138
+ continue
139
+ run = created[0] if isinstance(created, tuple) else created
140
+ rows.append({"account": account["id"], "display": display(account), "run": run.public(include_events=False)})
141
+ started = any("run" in row for row in rows)
142
+ return Response.json({"runs": rows}, 201 if started else 409)
143
+
144
+
145
+ def _pacing_answer(request, profile):
146
+ from .pacing import pacing_for
147
+ pacing = pacing_for(request.runtime)
148
+ return {**pacing.settings(profile), "today": pacing.today(profile)}
149
+
150
+
151
+ def _known(request):
152
+ profile = request.params["id"]
153
+ if store_for(request.runtime).get(profile) is None:
154
+ raise APIError("No profile has that id. Add it in Settings › Profiles.", 404, "account_not_found")
155
+ return profile
156
+
157
+
158
+ def get_pacing(request):
159
+ return Response.json({"pacing": _pacing_answer(request, _known(request))})
160
+
161
+
162
+ def save_pacing(request):
163
+ from .pacing import PacingError, pacing_for
164
+ _writable(request)
165
+ profile = _known(request)
166
+ try:
167
+ pacing_for(request.runtime).save(profile, request.body or {})
168
+ except PacingError as error:
169
+ raise APIError(str(error), 400, "invalid") from None
170
+ return Response.json({"pacing": _pacing_answer(request, profile)})
171
+
172
+
173
+ def resume_profile(request):
174
+ from .pacing import pacing_for
175
+ _writable(request)
176
+ profile = _known(request)
177
+ pacing_for(request.runtime).resume(profile)
178
+ return Response.json({"pacing": _pacing_answer(request, profile)})
179
+
180
+
181
+ def get_stats(request):
182
+ from ..packs.register import all_packs
183
+ from . import stats
184
+ app = request.params["app"]
185
+ if app not in {pack.key for pack in all_packs()}:
186
+ raise APIError(f"There is no {app} pack. Use one of: " + ", ".join(p.key for p in all_packs()) + ".", 404,
187
+ "stats_unknown_app")
188
+ try:
189
+ days = int((request.query.get("days") or ["30"])[0])
190
+ except ValueError:
191
+ days = 0
192
+ if not 1 <= days <= 365:
193
+ raise APIError("days is a number from 1 to 365.", 400, "invalid")
194
+ return Response.json(stats.series(store_for(request.runtime).data_dir, app, request.params["handle"], days))
195
+
196
+
197
+ ROUTES = (
198
+ Route("accounts", "GET", "/api/accounts", list_accounts, body="none"),
199
+ Route("accounts", "POST", "/api/accounts", add_account),
200
+ Route("accounts", "POST", "/api/accounts/runs", run_accounts, idempotency=True),
201
+ Route("accounts", "POST", "/api/accounts/{id}", edit_account),
202
+ Route("accounts", "GET", "/api/accounts/{id}/pacing", get_pacing, body="none"),
203
+ Route("accounts", "POST", "/api/accounts/{id}/pacing", save_pacing),
204
+ Route("accounts", "POST", "/api/accounts/{id}/resume", resume_profile, body="none"),
205
+ Route("accounts", "GET", "/api/accounts/{app}/{handle}/stats", get_stats, body="none"),
206
+ Route("accounts", "DELETE", "/api/accounts/{id}", delete_account, body="none"),
207
+ )
208
+
209
+
210
+ def register_routes():
211
+ for route in ROUTES:
212
+ register(route)
@@ -0,0 +1,69 @@
1
+ """A task that names a profile (an account in the code): its run field, and where it runs."""
2
+
3
+ import hashlib
4
+ import json
5
+ from types import MappingProxyType
6
+
7
+ from .context import fill
8
+ from .store import AccountNotFound, AmbiguousAccount, display, resolve, store_for
9
+
10
+ SMART_ONLY = ("A task for a profile runs on Mobster's agent, which confirms the profile on screen before it posts. "
11
+ "Choose Mobster, or leave the profile out.")
12
+
13
+
14
+ def validate(value, runtime):
15
+ if isinstance(value, dict):
16
+ value = value.get("id")
17
+ try:
18
+ account = resolve(store_for(runtime).all(), value)
19
+ except (AccountNotFound, AmbiguousAccount) as error:
20
+ raise ValueError(str(error)) from None
21
+ return {"id": account["id"], "handle": account["handle"], "app": dict(account["app"]),
22
+ "device": account.get("device"), "deviceName": account.get("deviceName")}
23
+
24
+
25
+ def capture(runtime, account):
26
+ """Detach the selected writing context from the editable profile at admission."""
27
+ stored = store_for(runtime).get(account["id"])
28
+ if stored is None:
29
+ raise ValueError("This profile was removed. Choose a saved profile again.")
30
+ identity = ("id", "handle", "app", "device", "deviceName")
31
+ if any(stored.get(key) != account.get(key) for key in identity):
32
+ raise ValueError("This profile changed while the task was starting. Choose it again.")
33
+ selected = {key: stored.get(key) for key in identity}
34
+ selected.update(notes=stored.get("notes") or "", signature=stored.get("signature") or "",
35
+ links=stored.get("links") or [], variables=stored.get("variables") or {})
36
+ fingerprint = digest(selected)
37
+ frozen = MappingProxyType({**selected, "app": MappingProxyType(dict(selected["app"])),
38
+ "links": tuple(MappingProxyType(dict(link)) for link in selected["links"]),
39
+ "variables": MappingProxyType(dict(selected["variables"]))})
40
+ return frozen, fingerprint
41
+
42
+
43
+ def digest(account):
44
+ plain = {**account, "app": dict(account["app"]), "links": [dict(link) for link in account["links"]],
45
+ "variables": dict(account["variables"])}
46
+ return hashlib.sha256(json.dumps(plain, sort_keys=True, ensure_ascii=True,
47
+ allow_nan=False).encode()).hexdigest()
48
+
49
+
50
+ def route(runtime, account, goal, device):
51
+ """(goal, device) for a task as ``account``. ValueError when the task names another phone."""
52
+ stored = account
53
+ wanted = stored.get("device")
54
+ if wanted and device is not None:
55
+ fleet = getattr(runtime, "fleet", None)
56
+ named = None
57
+ if fleet is not None:
58
+ try:
59
+ named = fleet.find(device).id
60
+ except LookupError:
61
+ named = None
62
+ if named != wanted:
63
+ where = stored.get("deviceName") or "another phone"
64
+ raise ValueError(f"{display(stored)} is on {where}. Leave the phone out and Mobster uses that one, or "
65
+ "change the profile's phone in Settings › Profiles.")
66
+ if (wanted and device is None and getattr(runtime, "fleet", None) is not None
67
+ and getattr(runtime, "pool", 1) is None):
68
+ device = wanted
69
+ return fill(goal, stored), device
@@ -0,0 +1,273 @@
1
+ """Which profile is signed in, read off the screen, and the check before every commit as that profile."""
2
+
3
+ import re
4
+ import threading
5
+ import time
6
+
7
+ from .store import display
8
+
9
+ TOP = .45 # the top part of the screen a profile's handle sits in (screen fractions)
10
+ MAX_REFUSALS = 3
11
+ # Own-profile controls in the apps' own words (English UI): "Edit profile", "Edit Profile", "Share profile".
12
+ OWN_PROFILE = re.compile(r"^\s*(?:edit|share)\s+profile\b", re.I)
13
+ # Controls only someone else's profile shows.
14
+ OTHER_PROFILE = re.compile(r"^\s*(?:follow|following|follow back|requested|message|subscribe)\s*$", re.I)
15
+ HANDLE_TOKEN = re.compile(r"(?<![A-Za-z0-9_.@-])@([A-Za-z0-9._-]{1,64}?)(?=[^A-Za-z0-9_.-]|\.(?![A-Za-z0-9_-])|$)")
16
+ TITLE_TOKEN = re.compile(r"[A-Za-z0-9._-]{1,64}")
17
+
18
+ VERBS = {"post": ("post", "posting"), "comment": ("comment", "commenting"), "reply": ("reply", "replying"),
19
+ "send_message": ("send", "sending"), "react": ("react", "reacting"), "follow": ("follow", "following"),
20
+ "unfollow": ("unfollow", "unfollowing"), "share": ("share", "sharing"), "delete": ("delete", "deleting")}
21
+
22
+
23
+ def _texts(element):
24
+ return [t for t in (getattr(element, "label", "") or "", getattr(element, "value", "") or "") if t]
25
+
26
+
27
+ def _top(element):
28
+ x, y, w, h = element.rect
29
+ return y + h / 2 <= TOP
30
+
31
+
32
+ def shows(text, handle):
33
+ """Whether ``text`` names ``handle`` as a whole token, with or without its @ ("acme", "@acme", "acme ✓")."""
34
+ pattern = r"(?<![A-Za-z0-9_.@-])@?" + re.escape(handle) + r"(?![A-Za-z0-9_-]|\.[A-Za-z0-9_-])"
35
+ return re.search(pattern, text or "", re.I) is not None
36
+
37
+
38
+ def own_profile(snapshot):
39
+ """Whether ``snapshot`` is the signed-in profile's own page: an own-profile control, and no Follow or Message."""
40
+ elements = list(getattr(snapshot, "elements", ()) or ())
41
+ labels = [(e.label or "").strip() for e in elements]
42
+ return any(OWN_PROFILE.search(l) for l in labels) and not any(OTHER_PROFILE.fullmatch(l) for l in labels if l)
43
+
44
+
45
+ def read(snapshot, handle, known=()):
46
+ """("confirmed", "@acme") | ("mismatch", "@other" or None) | ("unknown", None) for ``handle`` on ``snapshot``."""
47
+ if not own_profile(snapshot):
48
+ return "unknown", None
49
+ top = [e for e in getattr(snapshot, "elements", ()) or () if _top(e)]
50
+ if any(shows(text, handle) for e in top for text in _texts(e)):
51
+ return "confirmed", "@" + handle
52
+ for other in known:
53
+ if other.casefold() != handle.casefold() and any(shows(t, other) for e in top for t in _texts(e)):
54
+ return "mismatch", "@" + other
55
+ for e in top:
56
+ for text in _texts(e):
57
+ found = HANDLE_TOKEN.search(text)
58
+ if found and found.group(1).casefold() != handle.casefold():
59
+ return "mismatch", "@" + found.group(1)
60
+ # The navigation bar's title on an own profile is the username (Instagram, Threads).
61
+ bar = next((e for e in top if e.role == "NavigationBar" and (e.label or "").strip()), None)
62
+ if bar is not None:
63
+ title = " ".join(bar.label.split()).split(",")[0].strip()
64
+ if TITLE_TOKEN.fullmatch(title):
65
+ return "mismatch", "@" + title
66
+ return "mismatch", None
67
+
68
+
69
+ class AccountCheck:
70
+ """One run's profile: what was read in its app, and the commit check."""
71
+
72
+ def __init__(self, account, known=(), device_name=None, emit=None):
73
+ self.account = account
74
+ self.handle = account["handle"]
75
+ self.bundle = account["app"]["bundleId"]
76
+ self.app_name = account["app"]["name"]
77
+ self.known = tuple(known)
78
+ self.device_name = device_name
79
+ self.emit = emit or (lambda event: None)
80
+ self.state, self.shown = "unknown", None
81
+ # Pacing (accounts/pacing.py) and the run's stop, set by check_for; None in a run without them.
82
+ self.pacing = None
83
+ self.cancelled = lambda: False
84
+ self.on_signal = None # called with "challenge" or "signed_out" when the screen shows one
85
+ self.media = None # content_queue.media_check.MediaCheck for a queued post's task (the queue checks on)
86
+ self.refusals = 0
87
+ self.lock = threading.Lock()
88
+
89
+ @property
90
+ def acting_as(self):
91
+ return display(self.account)
92
+
93
+ def look(self, snapshot):
94
+ """Read ``snapshot`` (in the profile's app only) and keep what it says. Returns (state, shown)."""
95
+ if getattr(snapshot, "bundle_id", None) != self.bundle:
96
+ return "unknown", None
97
+ state, shown = read(snapshot, self.handle, self.known)
98
+ if state != "confirmed":
99
+ # A sign-in page or a security check in the profile's app, on a screen that doesn't show the profile: kept
100
+ # at once for the Phones page and the pre-run check (fleet_health.py), and as an event. The screen's text is
101
+ # only matched, never followed.
102
+ from ..fleet_health import classify_screen, snapshot_labels
103
+ signal = classify_screen(snapshot_labels(snapshot))
104
+ if signal is not None:
105
+ self.emit({"event": "account_signal", "account": self.account["id"], "signal": signal})
106
+ if self.on_signal is not None:
107
+ try:
108
+ self.on_signal(signal)
109
+ except Exception: # noqa: BLE001 -- keeping a sighting is never part of the run's outcome
110
+ pass
111
+ with self.lock:
112
+ if state != "unknown":
113
+ self.state, self.shown = state, shown
114
+ self.emit({"event": "account_checked", "account": self.account["id"], "state": state})
115
+ return state, shown
116
+
117
+ def commit(self, act, snapshot):
118
+ """The verdict before a commit ``act`` on ``snapshot``: None (not in the profile's app), {"acting_as"},
119
+ {"refuse": feedback} or {"stop": reason, "code"}."""
120
+ if getattr(snapshot, "bundle_id", None) != self.bundle:
121
+ return None
122
+ # A later own-profile screen may show a different account. Other screens retain the last check.
123
+ self.look(snapshot)
124
+ with self.lock:
125
+ state, shown = self.state, self.shown
126
+ if state == "mismatch":
127
+ return {"stop": self.mismatch_line(act, shown), "code": "account_mismatch"}
128
+ if state == "confirmed":
129
+ paced = self.paced(act)
130
+ if paced is not None:
131
+ return paced
132
+ if self.media is not None and act in self.media.POST_ACTS:
133
+ # A queued post: the app's limited-photos notice ends the task here; otherwise the frontier hands
134
+ # the screen to ``preview``, and the batch approval answers this Post only when it holds the media.
135
+ return self.media.stop(snapshot) or {"acting_as": self.acting_as, "preview": self.media.preview}
136
+ return {"acting_as": self.acting_as}
137
+ with self.lock:
138
+ self.refusals += 1
139
+ if self.refusals >= MAX_REFUSALS:
140
+ return {"stop": self.unconfirmed_line(act), "code": "account_unconfirmed"}
141
+ return {"refuse": (f"Refused: confirm the profile first. Open {self.app_name}'s own profile screen (the one "
142
+ f"with Edit profile), use CHECK_PROFILE there, and once it confirms @{self.handle}, come "
143
+ "back and do this.")}
144
+
145
+ def paced(self, act):
146
+ """The profile's pacing before ``act`` (accounts/pacing.py): None to go on."""
147
+ if self.pacing is None:
148
+ return None
149
+ from .pacing import WAIT_MAX_S
150
+ try:
151
+ verdict = self.pacing.check(self.account["id"], act)
152
+ except Exception: # noqa: BLE001
153
+ return {"stop": "Mobster couldn't read this profile's pacing, so it stopped before acting.",
154
+ "code": "pacing_unreadable"}
155
+ if verdict is None:
156
+ return None
157
+ if "wait" in verdict and verdict["wait"] <= WAIT_MAX_S:
158
+ self.emit({"event": "pacing_wait", "account": self.account["id"], "seconds": round(verdict["wait"])})
159
+ end = time.monotonic() + verdict["wait"]
160
+ while time.monotonic() < end:
161
+ if self.cancelled():
162
+ return {"stop": "Stopped while waiting for this profile's pace.", "code": "pacing_wait"}
163
+ time.sleep(min(1.0, end - time.monotonic()))
164
+ return None
165
+ if "wait" in verdict:
166
+ minutes = round(verdict["wait"] / 60)
167
+ stop = {"stop": f"@{self.handle} {verdict['reason']}; the next can go in {minutes} minutes. Mobster "
168
+ "stopped before acting.", "code": "pacing_gap"}
169
+ else:
170
+ stop = {"stop": f"@{self.handle}: {verdict['stop']}", "code": verdict.get("code") or "pacing"}
171
+ if isinstance(verdict.get("until"), (int, float)):
172
+ stop["until"] = verdict["until"]
173
+ # The task's record of why it stopped and when the profile may act again (a workflow waits for it).
174
+ self.emit({"event": "pacing_stop", "account": self.account["id"], "code": stop["code"],
175
+ **({"until": stop["until"]} if "until" in stop else {})})
176
+ return stop
177
+
178
+ def mismatch_line(self, act, shown):
179
+ verb = VERBS.get(act, ("do this", "acting"))
180
+ who = shown or "another profile"
181
+ return f"This would {verb[0]} as {who}, not @{self.handle}. Mobster stopped before {verb[1]}."
182
+
183
+ def unconfirmed_line(self, act):
184
+ verb = VERBS.get(act, ("do this", "acting"))[1]
185
+ where = f" on {self.device_name}" if self.device_name else ""
186
+ return (f"Mobster couldn't confirm on screen that @{self.handle} is signed in to {self.app_name}, so it "
187
+ f"stopped before {verb}. Open {self.app_name}{where}, make sure @{self.handle} is the profile signed "
188
+ f"in, and run the task again.")
189
+
190
+
191
+ class CheckAccount:
192
+ """The CHECK_PROFILE tool (agent_hooks.Skill): reads the screen at hand and says what it shows."""
193
+
194
+ op = "CHECK_PROFILE"
195
+ needs_target = False
196
+
197
+ def __init__(self, check):
198
+ self.check = check
199
+ self.prompt = (f"- CHECK_PROFILE: confirm the signed-in profile is @{check.handle} in {check.app_name}. Use it "
200
+ "on the app's own profile screen (the one with Edit profile) before you post, reply, comment "
201
+ "or send a message.")
202
+
203
+ def available(self, context):
204
+ return True
205
+
206
+ def prepare(self, context, act, snapshot):
207
+ from ..agent_hooks import Prepared
208
+ return Prepared(title=None)
209
+
210
+ def perform(self, context, prepared, act, snapshot):
211
+ from ..agent_hooks import GuardVerdict, SkillResult
212
+ if getattr(snapshot, "bundle_id", None) != self.check.bundle:
213
+ return SkillResult(f"This screen isn't {self.check.app_name}. Open {self.check.app_name}'s own profile "
214
+ "screen, then use CHECK_PROFILE.")
215
+ state, shown = self.check.look(snapshot)
216
+ if state == "confirmed":
217
+ return SkillResult(f"Confirmed: @{self.check.handle} is the profile signed in to {self.check.app_name}. "
218
+ "Go on with the task.")
219
+ if state == "mismatch":
220
+ line = (f"{shown or 'Another profile'} is signed in to {self.check.app_name}, not @{self.check.handle}. "
221
+ "Mobster stopped before acting as the wrong profile. Switch to "
222
+ f"@{self.check.handle} in {self.check.app_name}, then run the task again.")
223
+ return SkillResult(line, stop=GuardVerdict("stop", "account_mismatch", line))
224
+ return SkillResult(f"This isn't {self.check.app_name}'s own profile screen (it has no Edit profile, or it "
225
+ f"shows Follow or Message). Open the profile tab, then use CHECK_PROFILE.")
226
+
227
+
228
+ _checks = {}
229
+ _checks_lock = threading.Lock()
230
+
231
+
232
+ def check_for(run_context):
233
+ """The run's AccountCheck, made once per run from its ``account`` field; None for a run with no profile."""
234
+ extras = getattr(run_context, "extras", None) or {}
235
+ account = extras.get("account")
236
+ if not isinstance(account, dict) or not account.get("id"):
237
+ return None
238
+ with _checks_lock:
239
+ check = _checks.get(run_context.run_id)
240
+ if check is None:
241
+ from .store import store_for
242
+ known = ()
243
+ try:
244
+ known = tuple(a["handle"] for a in store_for(run_context.runtime).all()
245
+ if a["app"]["bundleId"] == account["app"]["bundleId"] and a["id"] != account["id"])
246
+ except Exception: # noqa: BLE001 -- the other handles only name a mismatch better
247
+ pass
248
+ check = _checks[run_context.run_id] = AccountCheck(account, known, account.get("deviceName"),
249
+ emit=run_context.emit)
250
+ try:
251
+ from .pacing import pacing_for
252
+ check.pacing = pacing_for(run_context.runtime)
253
+ except Exception: # noqa: BLE001 - a runtime with no data folder (tests): no pacing to keep
254
+ check.pacing = None
255
+ check.cancelled = getattr(run_context, "cancelled", None) or (lambda: False)
256
+ from ..content_queue.media_check import for_run
257
+ check.media = for_run(run_context)
258
+ health = getattr(getattr(run_context.runtime, "fleet", None), "health", None)
259
+ if health is not None:
260
+ device = run_context.device_id
261
+ check.on_signal = lambda signal: health.note_screen(account, device, signal)
262
+ return check
263
+
264
+
265
+ def forget(run_id):
266
+ with _checks_lock:
267
+ _checks.pop(run_id, None)
268
+
269
+
270
+ def commit_check_for(run_context):
271
+ """The frontier's ``commit_check`` for this run, or None (no profile: the frontier is as before)."""
272
+ check = check_for(run_context)
273
+ return check.commit if check is not None else None
@@ -0,0 +1,54 @@
1
+ """An account's numbers over time: one row a day per app and handle, in ``account-stats.json`` beside accounts.json."""
2
+
3
+ import json
4
+ import os
5
+ import tempfile
6
+ import threading
7
+ from datetime import datetime, timedelta, timezone
8
+ from pathlib import Path
9
+
10
+ FILE = "account-stats.json"
11
+ KEEP_DAYS = 400
12
+ FIELDS = ("followers", "following", "posts", "likes", "period", "views", "reach", "profile_visits", "follower_change")
13
+ _lock = threading.Lock()
14
+
15
+
16
+ def _load(folder):
17
+ try:
18
+ data = json.loads((Path(folder) / FILE).read_text())
19
+ except (OSError, ValueError):
20
+ return {}
21
+ return data if isinstance(data, dict) else {}
22
+
23
+
24
+ def record(folder, app, handle, values, now=None):
25
+ """Merge ``values`` (any of FIELDS) into today's row for ``app`` and ``handle``;
26
+ a value of None keeps what is there."""
27
+ now = now or datetime.now(timezone.utc)
28
+ key, today = f"{app}/{handle.lstrip('@').casefold()}", now.date().isoformat()
29
+ with _lock:
30
+ data = _load(folder)
31
+ rows = data.setdefault(key, {})
32
+ row = rows.setdefault(today, {})
33
+ row.update({k: v for k, v in values.items() if k in FIELDS and v is not None})
34
+ row["at"] = now.isoformat(timespec="seconds")
35
+ for old in sorted(rows)[:-KEEP_DAYS]:
36
+ del rows[old]
37
+ Path(folder).mkdir(parents=True, exist_ok=True)
38
+ handle_, temporary = tempfile.mkstemp(prefix=".stats.", suffix=".json", dir=folder)
39
+ with os.fdopen(handle_, "w") as stream:
40
+ os.fchmod(stream.fileno(), 0o600)
41
+ stream.write(json.dumps(data, indent=1) + "\n")
42
+ os.replace(temporary, Path(folder) / FILE)
43
+
44
+
45
+ def series(folder, app, handle, days=30, now=None):
46
+ """{app, handle, days, snapshots: [{date, at, ...FIELDS}] oldest first, latest: newest snapshot or None}."""
47
+ now = now or datetime.now(timezone.utc)
48
+ since = (now - timedelta(days=days)).date().isoformat()
49
+ handle = handle.lstrip("@").casefold()
50
+ rows = _load(folder).get(f"{app}/{handle}", {})
51
+ snapshots = [{"date": day, **{k: row.get(k) for k in ("at",) + FIELDS}} for day, row in sorted(rows.items())
52
+ if day >= since]
53
+ return {"app": app, "handle": handle, "days": days, "snapshots": snapshots,
54
+ "latest": snapshots[-1] if snapshots else None}