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,408 @@
1
+ """Mobster's one engine (``smart``, Mobster's agent) and the frozen config the benchmark shares."""
2
+
3
+ import dataclasses
4
+ import hashlib
5
+ import os
6
+ import re
7
+ import threading
8
+ import time
9
+ import urllib.error
10
+ import urllib.request
11
+ from dataclasses import dataclass
12
+
13
+ from . import harness_api
14
+
15
+ SMART = "smart"
16
+ ENGINES = (SMART,)
17
+ RETIRED = ("fast", "quick")
18
+
19
+
20
+ def read_engine(value):
21
+ """``value`` as an engine to run: Quick mode's retired names read as ``smart``; anything else is unchanged."""
22
+ return SMART if isinstance(value, str) and value.strip().casefold() in RETIRED else value
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class SmartConfig:
27
+ """The frozen Smart loop. Change it only with a benchmark run that shows why."""
28
+ # Claude Sonnet 5.5 at low reasoning: paired with gpt-5.6-sol on 16 iOSWorld tasks it matched the pass rate at 0.68x
29
+ # the cost (docs/benchmarks.md "Smart on Claude").
30
+ model: str = "claude-sonnet-5-5"
31
+ reasoning: str = "low"
32
+ max_steps: int = 50 # iOSWorld's own step limit
33
+ max_seconds: int = 900
34
+ screenshots: bool = True
35
+ settle_seconds: float = .6
36
+
37
+
38
+ SMART_CONFIG = SmartConfig()
39
+ ANTHROPIC_SMART_MODEL = "claude-sonnet-5-5"
40
+ OPENAI_SMART_MODEL = "gpt-5.6-sol"
41
+ PROVIDER_NAMES = {"openai": "OpenAI", "anthropic": "Anthropic"}
42
+ # People know the account as "Claude", never "Anthropic" (MESSAGING § 11); PROVIDER_NAMES stays the company for errors.
43
+ ACCOUNT_NAMES = {"OpenAI": "OpenAI", "Anthropic": "Claude"}
44
+
45
+
46
+ def account_name(provider):
47
+ """The account's name in a sentence ("Claude" for Anthropic) from a company name or a model provider id."""
48
+ company = PROVIDER_NAMES.get(provider, provider)
49
+ return ACCOUNT_NAMES.get(company, company)
50
+ MODEL_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}")
51
+
52
+
53
+ def model_provider(model):
54
+ """Who serves ``model``: a Settings › Models provider for "<provider>:<model>",
55
+ anthropic for claude-*, else openai."""
56
+ model = str(model or "")
57
+ providers = harness_api.hooks().model_routes()
58
+ routed, _ = providers.split(model)
59
+ if routed:
60
+ return routed # "<provider>:<model>": a plan or a key from Settings › Models (ai_plans/providers.py)
61
+ return "anthropic" if model.startswith("claude-") else "openai"
62
+
63
+
64
+ def on_plan(model):
65
+ """Whether ``model`` runs on a plan the user pays for (no per-call cost)."""
66
+ providers = harness_api.hooks().model_routes()
67
+ routed, _ = providers.split(model)
68
+ return bool(routed) and providers.get(routed).group == "plan"
69
+
70
+
71
+ def model_words(model):
72
+ """``model`` for a status line: "kimi-for-coding, using your Kimi Code plan" for a plan model, else the id."""
73
+ if on_plan(model):
74
+ providers = harness_api.hooks().model_routes()
75
+ routed, slug = providers.split(model)
76
+ return f"{slug}, using {providers.get(routed).plan_label or 'your plan'}"
77
+ return str(model or "")
78
+
79
+
80
+ def provider_name(model):
81
+ provider = model_provider(model)
82
+ if provider in PROVIDER_NAMES:
83
+ return PROVIDER_NAMES[provider]
84
+ providers = harness_api.hooks().model_routes()
85
+ return providers.get(provider).name
86
+
87
+
88
+ def anthropic_key(env=None):
89
+ """The Anthropic key Smart uses on Claude."""
90
+ key, _ = harness_api.hooks().key("anthropic", os.environ if env is None else env)
91
+ return (key or "").strip() or None
92
+
93
+
94
+ def smart_model(env=None):
95
+ """The model Smart plans with: MOBSTER_SMART_MODEL, else the provider the user chose, else Claude on a Claude key,
96
+ OpenAI on an OpenAI key alone, else SMART_CONFIG's."""
97
+ env = os.environ if env is None else env
98
+ chosen = (env.get("MOBSTER_SMART_MODEL") or "").strip()
99
+ if chosen and MODEL_ID.fullmatch(chosen) and not chosen.startswith("gemini-"):
100
+ return chosen
101
+ preferred = (env.get("MOBSTER_SMART_PROVIDER") or "").strip()
102
+ providers = harness_api.hooks().model_routes()
103
+ if providers.get(preferred) is not None:
104
+ # A plan or key chosen in Settings stays the choice while its key is missing,
105
+ # never a silent switch to another bill.
106
+ return providers.model_id(providers.get(preferred), env)
107
+ if preferred == "openai" and openai_smart_key(env):
108
+ return OPENAI_SMART_MODEL
109
+ if anthropic_key(env):
110
+ return ANTHROPIC_SMART_MODEL
111
+ if openai_smart_key(env):
112
+ return OPENAI_SMART_MODEL
113
+ return SMART_CONFIG.model
114
+
115
+
116
+ def smart_config(env=None):
117
+ """SMART_CONFIG on ``smart_model``: the frozen loop, only its model chosen."""
118
+ model = smart_model(env)
119
+ return SMART_CONFIG if model == SMART_CONFIG.model else dataclasses.replace(SMART_CONFIG, model=model)
120
+
121
+
122
+ def frontier_kwargs(config=SMART_CONFIG, *, max_cost_usd=None):
123
+ """FrontierAgent's keyword arguments for ``config``."""
124
+ return {"max_steps": config.max_steps, "max_seconds": config.max_seconds, "screenshots": config.screenshots,
125
+ "settle_seconds": config.settle_seconds, "max_cost_usd": max_cost_usd}
126
+
127
+
128
+ def build_client(config=None, *, key=None):
129
+ """The Smart model's client on the user's key."""
130
+ from .model_clients import chat_client
131
+ config = smart_config() if config is None else config
132
+ return chat_client(config.model, reasoning=config.reasoning, key=key or None)
133
+
134
+
135
+ def build_frontier(driver, client, *, apps, emit, max_cost_usd=None, approve=None, cancelled=None, config=SMART_CONFIG,
136
+ guard=None, skills=None, output_schema=None, context_blocks=(), turn_context=None,
137
+ steering=None, clarify=None, step_observer=None, checkpoint=None, max_steps=None,
138
+ max_seconds=None, run_context=None, commit_check=None, dialog_approve=None, longform=None,
139
+ forced_approve=None, permissions=None, floor_approve=None,
140
+ permission_mode=None, saved_program=None, program_parts=None, dialogs=None,
141
+ dialog_policy=None, recorder=None):
142
+ """The Smart agent: FrontierAgent with ``config``'s arguments. ``recorder``: traces.Recording.attach, which returns
143
+ the driver and client the agent runs on (a tape on, the pair itself off)."""
144
+ from .frontier import FrontierAgent
145
+ kwargs = frontier_kwargs(config, max_cost_usd=max_cost_usd)
146
+ if max_steps is not None:
147
+ kwargs["max_steps"] = int(max_steps)
148
+ if max_seconds is not None:
149
+ kwargs["max_seconds"] = max_seconds
150
+ if skills:
151
+ # The tool policy (harness/tools): registered tools only when available, interactive ones only when a person
152
+ # watches, at most 8.
153
+ skills = harness_api.hooks().tool_policy(skills, run_context=run_context, driver=driver, guard=guard)
154
+ hooks = {name: value for name, value in (
155
+ ("context_blocks", tuple(context_blocks or ())), ("turn_context", turn_context), ("steering", steering),
156
+ ("clarify", clarify), ("step_observer", step_observer), ("checkpoint", checkpoint),
157
+ ("run_context", run_context), ("commit_check", commit_check), ("dialog_approve", dialog_approve),
158
+ ("longform", longform), ("forced_approve", forced_approve),
159
+ ("permissions", permissions), ("floor_approve", floor_approve),
160
+ ("permission_mode", permission_mode), ("saved_program", saved_program),
161
+ ("program_parts", program_parts), ("dialogs", dialogs), ("dialog_policy", dialog_policy)) if value}
162
+ if recorder is not None:
163
+ driver, client = recorder(driver, client, apps=apps, frontier={**kwargs, "output_schema": output_schema},
164
+ hooks=[*hooks, *(("approve",) if approve else ()), *(("guard",) if guard else ())])
165
+ return FrontierAgent(driver, client, apps=apps, emit=emit, approve=approve, cancelled=cancelled, guard=guard,
166
+ skills=skills, output_schema=output_schema, **hooks, **kwargs)
167
+
168
+
169
+ def acquire_client(key):
170
+ """The Smart model's client for one run (seam S6.2): today, a new one on ``key`` (``build_client``)."""
171
+ return build_client(key=key)
172
+
173
+
174
+ def release_client(client):
175
+ """A run is done with ``client``: close its connection."""
176
+ connection = getattr(client, "connection", None)
177
+ if connection is not None:
178
+ try:
179
+ connection.close()
180
+ except Exception:
181
+ pass
182
+
183
+
184
+ def smart_key(env=None):
185
+ """The key Smart uses: the one of ``smart_model``'s provider."""
186
+ env = os.environ if env is None else env
187
+ provider = model_provider(smart_model(env))
188
+ if provider == "anthropic":
189
+ return anthropic_key(env)
190
+ providers = harness_api.hooks().model_routes()
191
+ if providers.get(provider) is not None:
192
+ key = providers.credential(providers.get(provider), env)
193
+ return None if key is None else (key or "local-server-no-key")
194
+ return openai_smart_key(env)
195
+
196
+
197
+ def openai_smart_key(env=None):
198
+ """The OpenAI key Smart uses: OPENAI_API_KEY, else the helper's key when the helper is OpenAI."""
199
+ env = os.environ if env is None else env
200
+ key = (env.get("OPENAI_API_KEY") or "").strip()
201
+ if key:
202
+ return key
203
+ helper, source = harness_api.hooks().key("openai", {name: value for name, value in env.items()
204
+ if name != "OPENAI_API_KEY"})
205
+ if source != "helper":
206
+ return None
207
+ return helper.strip() or None
208
+
209
+
210
+ # The Mac app reads this sentence as a missing key (keyMissing).
211
+ NO_SMART_KEY = "Connect your AI account in Setup"
212
+ NO_CREDIT = "Your OpenAI account is out of credit. Add credit at platform.openai.com, then try again."
213
+ NO_ANTHROPIC_CREDIT = "Your Claude account is out of credit. Add credit at platform.claude.com, then try again."
214
+ CREDIT_REASONS = (NO_CREDIT, NO_ANTHROPIC_CREDIT)
215
+ CREDIT_CODES = ("insufficient_quota", "credit_balance_exhausted")
216
+ # Anthropic's no-credit answers: a 400 "credit balance is too low", or a 402 billing_error.
217
+ ANTHROPIC_CREDIT = ("credit balance is too low", "billing_error")
218
+ PROVIDER_ERROR = re.compile(r"(OpenAI|Anthropic) HTTP (\d{3})")
219
+
220
+
221
+ def no_key_reason(managed=True, model=None, env=None):
222
+ """Why Smart can't run without a key."""
223
+ env = os.environ if env is None else env
224
+ model = model or smart_model(env)
225
+ chosen = (env.get("MOBSTER_SMART_MODEL") or "").strip() == model
226
+ if model_provider(model) == "anthropic" and (chosen or model != SMART_CONFIG.model):
227
+ return f"Add ANTHROPIC_API_KEY to the agent's env file to run Smart on {model}"
228
+ return NO_SMART_KEY if managed else "Add OPENAI_API_KEY or ANTHROPIC_API_KEY to the agent's env file"
229
+
230
+
231
+ def no_credit(model=None):
232
+ return NO_ANTHROPIC_CREDIT if model_provider(model or smart_model()) == "anthropic" else NO_CREDIT
233
+
234
+
235
+ def credit_provider(text):
236
+ """Whose account an out-of-credit error is about: Anthropic when the error names it, else OpenAI."""
237
+ return "Anthropic" if "Anthropic" in str(text or "") else "OpenAI"
238
+
239
+
240
+ def out_of_credit(text):
241
+ """Whether a model error (its status and body, as a run's reason carries them) says the account has no credit."""
242
+ text = str(text or "")
243
+ return (any(code in text for code in CREDIT_CODES + ANTHROPIC_CREDIT) or "OpenAI HTTP 402" in text
244
+ or "Anthropic HTTP 402" in text)
245
+
246
+
247
+ class ModelReach:
248
+ """Whether the saved key can run Smart's model, cached per key and model; unknown counts as reachable."""
249
+
250
+ TTL_SECONDS = 6 * 3600
251
+
252
+ def __init__(self, model=None, opener=urllib.request.urlopen, enabled=False):
253
+ self.model, self.opener, self.enabled = model, opener, enabled
254
+ self.lock = threading.Lock()
255
+ self.results = {} # key digest -> (ok, reason, checked_at, credit): credit marks an empty account
256
+ self.checking = set()
257
+
258
+ def _model(self, model=None):
259
+ return model or self.model or smart_model()
260
+
261
+ @staticmethod
262
+ def digest(key, model=""):
263
+ return hashlib.sha256(f"{model}\0{key}".encode()).hexdigest()[:16]
264
+
265
+ def state(self, key, model=None):
266
+ """(available, reason) for ``key``; starts a check when none is fresh (never over an empty account)."""
267
+ model = self._model(model)
268
+ if not key:
269
+ return False, no_key_reason(model=model)
270
+ if model_provider(model) not in ("openai", "anthropic"):
271
+ return True, None # a provider from Settings › Models: its key was checked when it was saved
272
+ digest = self.digest(key, model)
273
+ with self.lock:
274
+ found = self.results.get(digest)
275
+ stale = found is None or not found[3] and time.time() - found[2] > self.TTL_SECONDS
276
+ start = self.enabled and stale and digest not in self.checking
277
+ if start:
278
+ self.checking.add(digest)
279
+ if start:
280
+ threading.Thread(target=self._check, args=(key, digest, model), name="mobster-model-reach",
281
+ daemon=True).start()
282
+ return (True, None) if found is None else (found[0], found[1])
283
+
284
+ def record(self, key, ok, reason=None, model=None):
285
+ """What a run or a key test found."""
286
+ if key:
287
+ with self.lock:
288
+ self.results[self.digest(key, self._model(model))] = (ok, reason, time.time(),
289
+ ok is False and reason in CREDIT_REASONS)
290
+
291
+ def forget(self, key, model=None):
292
+ """Drop what is known about ``key``: the next ``state`` counts it reachable and checks it again."""
293
+ if key:
294
+ with self.lock:
295
+ self.results.pop(self.digest(key, self._model(model)), None)
296
+
297
+ def _check(self, key, digest, model=None):
298
+ try:
299
+ ok, reason = self.probe(key, model)
300
+ if ok is not None:
301
+ with self.lock:
302
+ found = self.results.get(digest)
303
+ # The model read can't see credit: a pass says nothing about an account a run found empty.
304
+ if not (ok and found is not None and found[3]):
305
+ self.results[digest] = (ok, reason, time.time(), False)
306
+ finally:
307
+ with self.lock:
308
+ self.checking.discard(digest)
309
+
310
+ def probe(self, key, model=None):
311
+ """(True, None), (False, why) or (None, None) when the answer is unknown."""
312
+ model = self._model(model)
313
+ if model_provider(model) not in ("openai", "anthropic"):
314
+ return None, None
315
+ if model_provider(model) == "anthropic":
316
+ from .model_clients import ANTHROPIC_VERSION
317
+ request = urllib.request.Request(f"https://api.anthropic.com/v1/models/{model}", headers={
318
+ "x-api-key": key, "anthropic-version": ANTHROPIC_VERSION, "User-Agent": "Mobster"})
319
+ else:
320
+ request = urllib.request.Request(f"https://api.openai.com/v1/models/{model}", headers={
321
+ "Authorization": f"Bearer {key}", "User-Agent": "Mobster"})
322
+ try:
323
+ with self.opener(request, timeout=15) as response:
324
+ response.read(65536)
325
+ return True, None
326
+ except urllib.error.HTTPError as error:
327
+ return unreachable(error.code, model)
328
+ except (urllib.error.URLError, TimeoutError, OSError):
329
+ return None, None
330
+
331
+
332
+ def rejected_key(provider):
333
+ """Why Smart can't run on a key its provider refused, in the app's words (no env names: the app saves the key)."""
334
+ return f"{account_name(provider)} didn't accept your key. Check it in Settings › Models."
335
+
336
+
337
+ def unreachable(status, model=None):
338
+ """(ok, reason) for a model provider's HTTP status, or (None, None) when it says nothing about the key."""
339
+ model = model or smart_model()
340
+ if status in (401, 403):
341
+ return False, rejected_key(provider_name(model))
342
+ if status == 404:
343
+ return False, f"Your {account_name(provider_name(model))} key can't use {model}."
344
+ return None, None
345
+
346
+
347
+ def model_failure(reason, model=None):
348
+ """(False, why) when a run's error says Smart can't run on this key, else (None, None)."""
349
+ text = str(reason or "")
350
+ found = PROVIDER_ERROR.search(text)
351
+ if not found:
352
+ return None, None
353
+ if out_of_credit(text):
354
+ return False, NO_ANTHROPIC_CREDIT if found.group(1) == "Anthropic" else NO_CREDIT
355
+ if found.group(2) == "404" and "model" not in text.casefold():
356
+ return None, None
357
+ return unreachable(int(found.group(2)), model or smart_model())
358
+
359
+
360
+ NO_OPENAI_REACH = "Mobster couldn't reach OpenAI. Check your internet connection, then try again."
361
+ NO_ANTHROPIC_REACH = "Mobster couldn't reach Claude. Check your internet connection, then try again."
362
+
363
+
364
+ def model_error(reason, model_failed=False):
365
+ """Which model-provider failure ended a run."""
366
+ text = str(reason or "")
367
+ found = PROVIDER_ERROR.search(text)
368
+ status = found.group(2) if found else None
369
+ if out_of_credit(text):
370
+ return "no_credit"
371
+ if status in ("401", "403"):
372
+ return "rejected_key"
373
+ if status == "404":
374
+ return "model_unavailable"
375
+ if status == "429":
376
+ return "rate_limited"
377
+ if status and status.startswith("5"):
378
+ return "provider_error"
379
+ if "Anthropic declined the request" in text:
380
+ return "declined"
381
+ if model_failed:
382
+ return "unreachable"
383
+ return None
384
+
385
+
386
+ def plain_error(reason, model_failed=False, model=None):
387
+ """A run error in words a person can act on, or None."""
388
+ text = str(reason or "")
389
+ model = model or smart_model()
390
+ found = PROVIDER_ERROR.search(text)
391
+ name = found.group(1) if found else provider_name(model)
392
+ code = model_error(text, model_failed)
393
+ if code:
394
+ return {
395
+ "no_credit": NO_ANTHROPIC_CREDIT if name == "Anthropic" else NO_CREDIT,
396
+ "rejected_key": rejected_key(name),
397
+ "model_unavailable": f"Your {account_name(name)} key can't use {model}.",
398
+ "rate_limited": f"{account_name(name)} is rate limiting your key. Wait a minute, then try again.",
399
+ "provider_error": f"{account_name(name)} had a problem answering. Try again in a moment.",
400
+ "declined": "Claude declined this request. Try rewording it, or run it on another model.",
401
+ "unreachable": NO_ANTHROPIC_REACH if name == "Anthropic" else NO_OPENAI_REACH,
402
+ }[code]
403
+ if re.search(r"TimeoutError|timed out|ConnectionRefused|ConnectionReset|RemoteDisconnected", text):
404
+ return "Your iPhone stopped answering. Check it is unlocked and connected, then try again."
405
+ if re.match(r"[A-Z]\w*(Error|Exception)\b", text):
406
+ # An internal error's name and message mean nothing to the user; the steps show how far it got.
407
+ return "Mobster couldn't read your iPhone's screen partway through. Try the task again."
408
+ return None
@@ -0,0 +1,63 @@
1
+ """Private folders and files, and the KEY=value lines of an env file (agent.env)."""
2
+
3
+ import fcntl
4
+ import os
5
+ import re
6
+ import tempfile
7
+ from contextlib import contextmanager
8
+ from pathlib import Path
9
+
10
+
11
+ def private_dir(path):
12
+ """A folder only this user can open (the app data folder, logs), also when it exists."""
13
+ path = Path(path)
14
+ path.mkdir(parents=True, exist_ok=True)
15
+ os.chmod(path, 0o700)
16
+ return path
17
+
18
+
19
+ def open_private(path, mode):
20
+ """A file opened for writing ("w", "ab") with mode 0600, also when it exists."""
21
+ flags = os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW | (os.O_APPEND if "a" in mode else os.O_TRUNC)
22
+ stream = open(os.open(path, flags, 0o600), mode)
23
+ os.fchmod(stream.fileno(), 0o600)
24
+ return stream
25
+
26
+
27
+ def check_changes(changes):
28
+ for key, value in changes.items():
29
+ if not re.fullmatch(r"[A-Z_][A-Z0-9_]*", key) or value is not None and (
30
+ not isinstance(value, str) or any(c in "\r\n" or "\ud800" <= c <= "\udfff" for c in value)):
31
+ raise ValueError("Invalid environment entry")
32
+
33
+
34
+ @contextmanager
35
+ def writer(path):
36
+ """Own the file for a whole read, change and write, in any process. Keychain migrations hold it too."""
37
+ path = Path(path).expanduser()
38
+ path.parent.mkdir(parents=True, exist_ok=True)
39
+ # Replacing the env file changes its inode, so lock a stable sibling for the entire read/change/write.
40
+ with os.fdopen(os.open(path.with_name(path.name + ".lock"), os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600),
41
+ "w") as lock:
42
+ fcntl.flock(lock, fcntl.LOCK_EX)
43
+ yield path
44
+
45
+
46
+ def write_locked(path, changes):
47
+ """Rewrite while ``writer`` owns the file, including a Keychain migration."""
48
+ # A line that is not UTF-8 is kept byte for byte, so Setup can still save a key into such a file (and
49
+ # a byte-order mark goes, as load_env_file ignores it: a key on the first line is still replaced).
50
+ text = path.read_bytes().decode("utf-8", "surrogateescape").removeprefix("\ufeff") if path.exists() else ""
51
+ lines = text.splitlines()
52
+ for key in changes:
53
+ lines = [line for line in lines if not re.match(rf"\s*(export\s+)?{re.escape(key)}\s*=", line)]
54
+ lines += [f"{key}={value}" for key, value in changes.items() if value is not None]
55
+ handle, temporary = tempfile.mkstemp(dir=path.parent, prefix=".env-")
56
+ try:
57
+ with os.fdopen(handle, "w", encoding="utf-8", errors="surrogateescape") as stream:
58
+ stream.write("\n".join(lines) + ("\n" if lines else ""))
59
+ os.chmod(temporary, 0o600)
60
+ os.replace(temporary, path)
61
+ except BaseException:
62
+ Path(temporary).unlink(missing_ok=True)
63
+ raise
@@ -0,0 +1,149 @@
1
+ """The secrets in Mobster for Mac's settings file live in the login Keychain, never in the file."""
2
+
3
+ import base64
4
+ import json
5
+ import os
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ from . import env_file, keychain_acl
10
+ from .ai_plans.vault import on_mac
11
+
12
+ SECRET_NAMES = ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "TEXT_MODEL_API_KEY", "MOBSTER_ALERT_WEBHOOK_URL")
13
+ MARKER = "MOBSTER_SECRETS_IN_KEYCHAIN"
14
+ SERVICE = "app.mobster.desktop.settings"
15
+ ACCOUNT = "agent.env"
16
+
17
+
18
+ REFUSED = ("Mobster couldn't save this in your Keychain, so nothing was saved. Open Keychain Access, unlock the login "
19
+ "keychain, then try again.")
20
+
21
+
22
+ class KeychainError(OSError):
23
+ """The Keychain didn't read or take the secrets."""
24
+
25
+
26
+ class Keychain:
27
+ """The secrets as one generic password: base64 of a JSON object."""
28
+
29
+ def __init__(self, service=SERVICE, account=ACCOUNT, runner=subprocess.run, bridge=None):
30
+ self.service, self.account = service, account
31
+ self.store = keychain_acl.Store(
32
+ service, bridge=bridge,
33
+ runner=lambda argv, data: runner(argv, input=data, capture_output=True, timeout=10))
34
+
35
+ def load(self):
36
+ """{name: value}; {} when there is no item. KeychainError when there is one it can't read."""
37
+ try:
38
+ word = self.store.get(self.account)
39
+ except keychain_acl.KeychainError as error:
40
+ raise KeychainError(f"The Keychain didn't give Mobster its settings ({error})") from None
41
+ if word is None:
42
+ return {}
43
+ try:
44
+ data = json.loads(base64.b64decode(word.strip()))
45
+ except (ValueError, TypeError):
46
+ raise KeychainError("Mobster's settings in the Keychain aren't readable") from None
47
+ return {k: v for k, v in data.items() if k in SECRET_NAMES and isinstance(v, str)} if isinstance(data, dict) \
48
+ else {}
49
+
50
+ def save(self, record):
51
+ secret = base64.b64encode(json.dumps(record, sort_keys=True).encode()).decode()
52
+ try:
53
+ self.store.set(self.account, secret)
54
+ except keychain_acl.KeychainError:
55
+ raise KeychainError("The Keychain didn't take Mobster's settings") from None
56
+ finally:
57
+ secret = None # noqa: F841 -- the secret's only other copy goes with this frame
58
+
59
+ def clear(self):
60
+ try:
61
+ self.store.delete(self.account)
62
+ except keychain_acl.KeychainError:
63
+ pass
64
+
65
+
66
+ def backend():
67
+ """The Keychain, or None where secrets stay in the file (off a Mac, a scratch HOME, MOBSTER_VAULT=file)."""
68
+ if os.environ.get("MOBSTER_VAULT") == "file" or not on_mac():
69
+ return None
70
+ return Keychain()
71
+
72
+
73
+ def applies(path):
74
+ """Whether ``path`` is the Mac app's agent.env and its secrets go to the Keychain."""
75
+ if path is None:
76
+ return False
77
+ from .config import app_env_file
78
+ try:
79
+ if Path(path).expanduser().resolve() != app_env_file().resolve():
80
+ return False
81
+ except OSError:
82
+ return False
83
+ return backend() is not None
84
+
85
+
86
+ def split(path, changes):
87
+ """The file's own changes: each secret goes to the Keychain first; KeychainError if refused, never plain text."""
88
+ names = [name for name in changes if name in SECRET_NAMES]
89
+ if not names or not applies(path):
90
+ return changes
91
+ store = backend()
92
+ record = store.load()
93
+ for name in names:
94
+ if changes[name] is None:
95
+ record.pop(name, None)
96
+ else:
97
+ record[name] = changes[name]
98
+ if record:
99
+ store.save(record)
100
+ else:
101
+ store.clear()
102
+ rest = {name: value for name, value in changes.items() if name not in SECRET_NAMES}
103
+ return {**rest, **{name: None for name in names}, MARKER: "1" if record else None}
104
+
105
+
106
+ def secrets_for(path, values):
107
+ """(secrets, warnings) for ``path`` (lines ``values``): a secret in the file moves to the Keychain, or stays."""
108
+ if not applies(path):
109
+ return {}, []
110
+ if not any(values.get(name) for name in SECRET_NAMES) and values.get(MARKER) != "1":
111
+ return {}, []
112
+ from .config import read_env_values
113
+ store = backend()
114
+ # The caller's read can precede a save or logout. Only the current file may initiate a migration or fallback.
115
+ try:
116
+ with env_file.writer(path) as path:
117
+ values = read_env_values(path)
118
+ in_file = {name: values[name] for name in SECRET_NAMES if values.get(name)}
119
+ if not in_file and values.get(MARKER) != "1":
120
+ return {}, []
121
+ try:
122
+ record = store.load()
123
+ except KeychainError as error:
124
+ return in_file, [f"{error}; Mobster's keys weren't loaded from it"]
125
+ if in_file:
126
+ try:
127
+ store.save({**record, **in_file})
128
+ env_file.write_locked(path, {**{name: None for name in in_file}, MARKER: "1"})
129
+ except OSError:
130
+ return {**record, **in_file}, ["Mobster couldn't move its keys to the "
131
+ "Keychain; they stay in agent.env"]
132
+ record = {**record, **in_file}
133
+ return record, []
134
+ except OSError as error:
135
+ return {}, [f"Mobster couldn't read its saved keys ({type(error).__name__}); no keys "
136
+ "were loaded from the Keychain"]
137
+
138
+
139
+ def load(path):
140
+ """The secrets saved for the file at ``path`` ({} when none, or not on a Mac)."""
141
+ from .config import read_env_values
142
+ return secrets_for(path, read_env_values(path))[0]
143
+
144
+
145
+ def update_env_file(path, changes):
146
+ """Set (str) or remove (None) several KEY=value lines in one private, atomic rewrite."""
147
+ env_file.check_changes(changes)
148
+ with env_file.writer(path) as path:
149
+ env_file.write_locked(path, split(path, changes))
mobile_agent/errors.py ADDED
@@ -0,0 +1,15 @@
1
+ """One root for every Mobster domain error."""
2
+
3
+
4
+ class MobsterError(RuntimeError):
5
+ """Root of the framework's failures. Never retry without a proven dispatch boundary."""
6
+
7
+ retryable = False
8
+
9
+
10
+ class SimError(RuntimeError):
11
+ """A simulator problem; ``kind`` and ``fix`` are its stable public details."""
12
+
13
+ def __init__(self, kind: str, message: str, fix: str = ""):
14
+ super().__init__(message)
15
+ self.kind, self.fix = kind, fix
@@ -0,0 +1,3 @@
1
+ """On-device measurement tools: the bench's own phone reads (``oracles``), the accessibility coverage survey
2
+ (``app_survey``), the latency report (``latency_report``) and the labelled
3
+ image set the bench's visual fixtures come from (``vision``)."""