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,46 @@
1
+ """`mobster doctor --apps`: score the apps on the phone for automation. Hidden; needs MOBSTER_EXPERIMENTAL_ATTACH=1."""
2
+
3
+ import json
4
+ import sys
5
+ import time
6
+ import urllib.request
7
+
8
+ from . import ENV_FLAG, enabled, score
9
+
10
+ SETTLE_SECONDS = 3.0
11
+ ENV_HINT = f"mobster doctor --apps is experimental: set {ENV_FLAG}=1 to use it"
12
+
13
+
14
+ def installed_bundles(udid):
15
+ """User-app bundle IDs on the phone, or () when the listing is unavailable."""
16
+ from ..installed_apps import InstalledApps
17
+ inventory = InstalledApps(lambda: {"udid": udid, "trusted": True}).refresh() if udid else None
18
+ return tuple(app["bundleId"] for app in inventory.user) if inventory else ()
19
+
20
+
21
+ def run(args, wda_url):
22
+ """Launch each installed app, read its first screen, store and print the scores. Returns an exit code."""
23
+ from ..compose import build_target_driver
24
+ from ..verify.tree import read_tree
25
+ if not enabled():
26
+ print(ENV_HINT, file=sys.stderr)
27
+ return 2
28
+ with urllib.request.urlopen(wda_url + "/status", timeout=10) as response:
29
+ session = json.load(response)["sessionId"]
30
+ driver = build_target_driver(wda_url=wda_url, session=session)
31
+ bundles = installed_bundles(getattr(args, "device_udid", None))
32
+ if not bundles:
33
+ print("mobster doctor --apps: no installed apps could be listed. Plug the iPhone in with a cable, or "
34
+ "name a USB device with --device.", file=sys.stderr)
35
+ return 1
36
+ scores = score.score_apps(
37
+ lambda: read_tree(driver), lambda b: driver.call("POST", "/wda/apps/activate", {"bundleId": b}, timeout=20),
38
+ bundles, settle=lambda: time.sleep(SETTLE_SECONDS))
39
+ driver.call("POST", "/wda/pressButton", {"name": "home"}, timeout=10)
40
+ path = score.save(scores)
41
+ if getattr(args, "json", False):
42
+ print(json.dumps({"ok": True, "apps": [s.public() for s in scores], "stored": str(path)}))
43
+ else:
44
+ print(score.report(scores))
45
+ print(f"Stored in {path}")
46
+ return 0
@@ -0,0 +1,28 @@
1
+ """Recognise Flutter from engine marks or the shape of its accessibility tree.
2
+
3
+ The shape heuristic uses labelled Other nodes and typed-control density. It is
4
+ covered by synthetic fixtures, not calibrated against a real-app survey.
5
+ """
6
+
7
+ MARKS = ("fluttersemantics", "flutterview", "flutter_view")
8
+ TYPED = frozenset({"Button", "Cell", "Link", "TextField", "SearchField", "TextView", "Switch", "Slider", "Tab",
9
+ "SegmentedControl", "Stepper", "Toggle", "NavigationBar", "TabBar", "Table", "CollectionView",
10
+ "Picker", "PickerWheel", "Key"})
11
+ MIN_NODES = 6
12
+ OTHER_SHARE = .6
13
+
14
+
15
+ def _marked(node):
16
+ return any(mark in f"{node.identifier} {node.label}".casefold().replace(" ", "") for mark in MARKS)
17
+
18
+
19
+ def is_flutter(tree):
20
+ """Whether the shown tree has Flutter's shape: engine marks, or mostly labelled ``Other`` and few typed controls."""
21
+ shown = tree.shown()
22
+ if any(_marked(node) for node in shown):
23
+ return True
24
+ labelled = [node for node in shown if node.label.strip() or node.identifier]
25
+ if len(labelled) < MIN_NODES:
26
+ return False
27
+ other = sum(node.role == "Other" for node in labelled)
28
+ return other / len(labelled) >= OTHER_SHARE and sum(node.role in TYPED for node in shown) <= len(labelled) // 4
@@ -0,0 +1,119 @@
1
+ """Rate an app for automation from the accessibility tree it publishes: good, partial or poor, with reasons."""
2
+
3
+ import json
4
+ import time
5
+ from dataclasses import asdict, dataclass
6
+
7
+ from ..paths import user_data_dir, write_text_atomic
8
+ from . import flutter, web
9
+
10
+ ACTIONABLE = frozenset({"Button", "Cell", "Link", "TextField", "SearchField", "TextView", "Switch", "Slider", "Tab",
11
+ "Key", "SegmentedControl", "PickerWheel", "Stepper", "Toggle"})
12
+ OPAQUE_NODES = 3
13
+ GOOD_LABELLED = .85
14
+ PARTIAL_LABELLED = .6
15
+ MIN_DENSITY = 8 # shown nodes below this on a full screen leave the agent little to act on
16
+ RATINGS = ("good", "partial", "poor")
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class AppScore:
21
+ bundle_id: str
22
+ rating: str
23
+ reasons: tuple
24
+ nodes: int
25
+ actionable: int
26
+ labelled_share: float | None
27
+ webview: bool
28
+ flutter: bool
29
+ scored_at: float
30
+
31
+ def public(self):
32
+ return {**asdict(self), "reasons": list(self.reasons)}
33
+
34
+
35
+ def score_tree(tree, bundle_id=None, now=None):
36
+ """Score one screen. The caller decides which screen speaks for the app (its first screen after launch)."""
37
+ shown = tree.shown()
38
+ actionable = [node for node in shown if node.role in ACTIONABLE]
39
+ labelled = [node for node in actionable if (node.label or node.identifier or node.value).strip()]
40
+ share = len(labelled) / len(actionable) if actionable else None
41
+ view = web.web_view(tree)
42
+ has_web = view is not None
43
+ has_flutter = flutter.is_flutter(tree)
44
+ reasons, rating = [], "good"
45
+
46
+ def lower(to, why):
47
+ nonlocal rating
48
+ reasons.append(why)
49
+ if RATINGS.index(to) > RATINGS.index(rating):
50
+ rating = to
51
+
52
+ if len(shown) <= OPAQUE_NODES:
53
+ lower("poor", f"the tree has {len(shown)} elements: the screen is drawn, not published")
54
+ elif len(shown) < MIN_DENSITY:
55
+ lower("partial", f"the tree has {len(shown)} elements, too few to act on with confidence")
56
+ if share is None:
57
+ if len(shown) > OPAQUE_NODES:
58
+ lower("partial", "no buttons, fields or links are exposed")
59
+ elif share < PARTIAL_LABELLED:
60
+ lower("poor", f"{len(labelled)} of {len(actionable)} controls have a label ({share:.0%})")
61
+ elif share < GOOD_LABELLED:
62
+ lower("partial", f"{len(labelled)} of {len(actionable)} controls have a label ({share:.0%})")
63
+ if has_web:
64
+ inside = sum(1 for node in shown if web.inside(node, view)) - 1
65
+ lower("partial", f"a WebView holds the content ({inside} accessibility elements inside it); "
66
+ "only its published accessibility elements are scored")
67
+ if has_flutter:
68
+ lower("partial", "Flutter draws its own widgets; its semantics tree is the only thing the agent can read")
69
+ if not reasons:
70
+ reasons.append(f"{len(labelled)} of {len(actionable)} controls are labelled across {len(shown)} elements")
71
+ return AppScore(bundle_id or tree.bundle_id, rating, tuple(reasons), len(shown), len(actionable),
72
+ None if share is None else round(share, 3), has_web, has_flutter,
73
+ time.time() if now is None else now)
74
+
75
+
76
+ def score_apps(read_tree, launch, bundles, settle=lambda: None):
77
+ """Launch each bundle, read its tree and score it. ``launch(bundle)`` and ``read_tree()`` are the driver's.
78
+
79
+ An app that will not open or read is recorded as poor with the reason, so one bad app never stops the survey.
80
+ """
81
+ scores = []
82
+ for bundle in bundles:
83
+ try:
84
+ launch(bundle)
85
+ settle()
86
+ tree = read_tree()
87
+ except Exception as error:
88
+ scores.append(AppScore(bundle, "poor", (f"could not be read ({type(error).__name__})",), 0, 0, None,
89
+ False, False, time.time()))
90
+ continue
91
+ scores.append(score_tree(tree, bundle))
92
+ return scores
93
+
94
+
95
+ def store_path():
96
+ return user_data_dir() / "attach" / "app-scores.json"
97
+
98
+
99
+ def save(scores, path=None):
100
+ """Merge ``scores`` into the stored file by bundle ID (newest wins) and return the path."""
101
+ path = path or store_path()
102
+ merged = {row["bundle_id"]: row for row in load(path)}
103
+ merged.update({score.bundle_id: score.public() for score in scores})
104
+ return write_text_atomic(path, json.dumps(sorted(merged.values(), key=lambda row: row["bundle_id"]), indent=1))
105
+
106
+
107
+ def load(path=None):
108
+ try:
109
+ rows = json.loads((path or store_path()).read_text())
110
+ except (OSError, ValueError):
111
+ return []
112
+ return [row for row in rows if isinstance(row, dict) and isinstance(row.get("bundle_id"), str)] \
113
+ if isinstance(rows, list) else []
114
+
115
+
116
+ def report(scores):
117
+ """Plain lines: bundle, rating and the first reason."""
118
+ width = max((len(score.bundle_id) for score in scores), default=0)
119
+ return "\n".join(f"{score.bundle_id:{width}} {score.rating:7} {score.reasons[0]}" for score in scores)
@@ -0,0 +1,13 @@
1
+ """WebView detection for the accessibility-based app score."""
2
+
3
+
4
+ def web_view(tree):
5
+ """The largest shown WebView in the accessibility tree, or None."""
6
+ views = [node for node in tree.shown() if node.role == "WebView"]
7
+ return max(views, key=lambda node: node.rect[2] * node.rect[3], default=None)
8
+
9
+
10
+ def inside(node, view):
11
+ x, y, w, h = node.rect
12
+ vx, vy, vw, vh = view.rect
13
+ return node is view or (x >= vx and y >= vy and x + w <= vx + vw + 1 and y + h <= vy + vh + 1)
@@ -0,0 +1 @@
1
+ """Track `files`: files attached to tasks and conversations, and files to and from the phone (docs/files.md)."""
@@ -0,0 +1,418 @@
1
+ """What Mobster's agent gets from attached files: one context block, and the READ_ATTACHMENT and PUT_FILE tools."""
2
+
3
+ import re
4
+ import threading
5
+ import time
6
+
7
+ from .. import harness_api
8
+ from ..agent_hooks import Prepared, SkillResult
9
+ from . import sniff
10
+ from .extract import pages_of
11
+
12
+ MAX_CHARS = 12_000
13
+ READ_CHARS = 6_000
14
+ MIN_SHARE = 800
15
+ MAX_IMAGES = 4
16
+ TITLE = "Files the user attached to this task"
17
+ CLIPBOARD_MAX = 64 * 1024
18
+ # The approval title's parts: with "Put “…” in … on …?" they stay within the 160 characters the card shows.
19
+ NAME_CHARS, APP_CHARS, DEVICE_CHARS = 48, 40, 40
20
+
21
+
22
+ def _defang(text):
23
+ """File text can't close the fence it sits in."""
24
+ return str(text or "").replace("<<<", "‹‹‹").replace(">>>", "›››")
25
+
26
+
27
+ def _short(text, limit):
28
+ """``text`` at most ``limit`` characters, cut in the middle so a file keeps its extension ("Luigi’s…menu.pdf").
29
+ The approval card's title is at most 160 characters: a long file name must never push out where it goes."""
30
+ text = str(text or "")
31
+ if len(text) <= limit:
32
+ return text
33
+ stem, dot, ext = text.rpartition(".")
34
+ tail = f".{ext}" if dot and stem and len(ext) <= 8 else ""
35
+ head = text[:len(text) - len(tail)] if tail else text
36
+ keep = limit - len(tail) - 1
37
+ return head[:max(1, keep)].rstrip() + "…" + tail
38
+
39
+
40
+ def describe(attachment):
41
+ """"menu.pdf: PDF, 2 pages, 412 KB"."""
42
+ data = attachment.data
43
+ parts = [data.get("label") or attachment.kind or "file"]
44
+ if data.get("pages"):
45
+ parts.append(f"{data['pages']} page" + ("" if data["pages"] == 1 else "s"))
46
+ if data.get("rows") is not None:
47
+ parts.append(f"{data['rows']:,} row" + ("" if data["rows"] == 1 else "s"))
48
+ if data.get("width") and data.get("height"):
49
+ parts.append(f"{data['width']} × {data['height']}")
50
+ parts.append(sniff.size_words(data.get("bytes")))
51
+ return f"{attachment.name}: " + ", ".join(parts)
52
+
53
+
54
+ def chunks(attachment, text):
55
+ """The pages READ_ATTACHMENT serves: a PDF's own pages, else 6,000-character parts of the text."""
56
+ if attachment.kind == "pdf":
57
+ return pages_of(text)
58
+ if not text:
59
+ return [""]
60
+ return [text[i:i + READ_CHARS] for i in range(0, len(text), READ_CHARS)]
61
+
62
+
63
+ def find(attachments, wanted):
64
+ """The attachment ``wanted`` names: its exact name (any case), else the one name containing it, else the only file
65
+ there is."""
66
+ wanted = " ".join(str(wanted or "").strip().strip("\"'“”‘’").split()).casefold()
67
+ if not attachments:
68
+ return None
69
+ exact = [a for a in attachments if a.name.casefold() == wanted]
70
+ if exact:
71
+ return exact[0]
72
+ partial = [a for a in attachments if wanted and (wanted in a.name.casefold() or a.name.casefold() in wanted)]
73
+ if len(partial) == 1:
74
+ return partial[0]
75
+ if len(attachments) == 1 and not partial:
76
+ return attachments[0]
77
+ return None
78
+
79
+
80
+ def run_files(ctx, store):
81
+ """The attachments this run may read: the ones its ``attachmentIds`` names that exist and belong to no other
82
+ conversation."""
83
+ ids = list(ctx.extras.get("attachmentIds") or ())
84
+ if store is None or not ids:
85
+ return []
86
+ thread = ctx.thread_id
87
+ return [a for a in store.many(ids) if a.thread_id is None or thread is None or a.thread_id == thread]
88
+
89
+
90
+ class AttachmentsProvider:
91
+ """harness_api.ContextProvider ``attachments``: one stable, untrusted block for the run's files."""
92
+
93
+ key = "attachments"
94
+ max_chars = MAX_CHARS
95
+
96
+ def __init__(self, store):
97
+ self.store = store
98
+
99
+ def blocks(self, ctx):
100
+ files = run_files(ctx, self.store)
101
+ if not files:
102
+ return ()
103
+ try:
104
+ # This task reads them: record it, so they stay while it is in history and join its conversation.
105
+ self.store.attach([a.id for a in files], ctx.run_id, ctx.thread_id)
106
+ except Exception: # noqa: BLE001 -- the block matters more than the bookkeeping
107
+ harness_api.log.exception("could not record which task read its files")
108
+ texts = {a.id: self.store.text(a) for a in files}
109
+ headers = [f"{index}. {describe(a)}" for index, a in enumerate(files, 1)]
110
+ room = max(MIN_SHARE * len(files), MAX_CHARS - sum(len(h) + 80 for h in headers))
111
+ shares = _shares([len(texts[a.id]) for a in files], room)
112
+ parts, images = [], []
113
+ for index, (attachment, header, share) in enumerate(zip(files, headers, shares), 1):
114
+ body = self._excerpt(attachment, texts[attachment.id], share, images)
115
+ parts.append(header + ("\n" + body if body else ""))
116
+ try:
117
+ ctx.emit({"event": "attachment_read", "id": attachment.id, "name": attachment.name,
118
+ **({"pages": attachment.data["pages"]} if attachment.data.get("pages") else {})})
119
+ except Exception: # noqa: BLE001 -- an event never stops the block
120
+ pass
121
+ text = _defang("\n\n".join(parts))
122
+ if len(text) > MAX_CHARS:
123
+ text = text[:MAX_CHARS - 1] + "…"
124
+ return (harness_api.ContextBlock(self.key, TITLE, text, stable=True, untrusted=True,
125
+ images=tuple(images[:MAX_IMAGES])),)
126
+
127
+ def turn_blocks(self, ctx, turn):
128
+ return ()
129
+
130
+ def _excerpt(self, attachment, text, share, images):
131
+ name = attachment.name
132
+ if attachment.kind == "image":
133
+ lines = []
134
+ model = self.store.path(attachment, "model")
135
+ if model is not None and len(images) < MAX_IMAGES:
136
+ images.append(("image/jpeg", model.read_bytes()))
137
+ lines.append(f"Shown to you as picture {len(images)} above.")
138
+ else:
139
+ lines.append("Not shown to you as a picture." if model is None
140
+ else f"Not shown: at most {MAX_IMAGES} pictures go with a task.")
141
+ if text.strip():
142
+ lines.append("Text in the picture:\n" + _cut(text.strip(), share))
143
+ return "\n".join(lines)
144
+ if not attachment.data.get("textAvailable") and not text.strip():
145
+ return ("Mobster couldn't read text from this file on this Mac."
146
+ if attachment.kind == "pdf" else "It holds no text.")
147
+ if attachment.kind == "pdf":
148
+ pages = pages_of(text)
149
+ out, used, shown = [], 0, 0
150
+ for number, page in enumerate(pages, 1):
151
+ page = page.strip()
152
+ block = f"Page {number}:\n{page or '(no text on this page)'}"
153
+ if used + len(block) > share and shown:
154
+ break
155
+ if used + len(block) > share:
156
+ block = _cut(block, share - used)
157
+ out.append(block)
158
+ used += len(block) + 2
159
+ shown = number
160
+ if shown < len(pages):
161
+ out.append(f"[Pages {shown + 1}–{len(pages)} not shown: READ_ATTACHMENT \"{name} page {shown + 1}\"]")
162
+ elif out and out[-1].endswith("…"):
163
+ out.append(f"[Page {shown} cut short: READ_ATTACHMENT \"{name} page {shown}\" reads it all]")
164
+ return "\n\n".join(out)
165
+ if len(text) <= share:
166
+ return text.strip()
167
+ parts = chunks(attachment, text)
168
+ shown = _cut(text, share)
169
+ next_page = min(len(parts), len(shown) // READ_CHARS + 1)
170
+ note = (f"[{len(text) - len(shown) + 1:,} more characters: READ_ATTACHMENT \"{name} page {next_page}\" "
171
+ f"(page {next_page} of {len(parts)})]")
172
+ return shown + "\n" + note
173
+
174
+
175
+ def _cut(text, limit):
176
+ limit = max(1, int(limit))
177
+ return text if len(text) <= limit else text[:limit - 1].rstrip() + "…"
178
+
179
+
180
+ def _shares(lengths, room):
181
+ """Each file's characters: an equal share, with what short files don't need handed to the long ones."""
182
+ if not lengths:
183
+ return []
184
+ shares = [0] * len(lengths)
185
+ left, open_ = room, list(range(len(lengths)))
186
+ while open_ and left > 0:
187
+ fair = left // len(open_)
188
+ done = [i for i in open_ if lengths[i] - shares[i] <= fair]
189
+ if not done:
190
+ for i in open_:
191
+ shares[i] += fair
192
+ break
193
+ for i in done:
194
+ left -= lengths[i] - shares[i]
195
+ shares[i] = lengths[i]
196
+ open_ = [i for i in open_ if i not in done]
197
+ return [max(MIN_SHARE, s) for s in shares]
198
+
199
+
200
+ READ_TEXT = re.compile(r"^\s*(?P<name>.+?)(?:\s*(?:,|#|\bp\.?|\bpage|\bpart)\s*(?P<page>\d{1,4}))?\s*$", re.I)
201
+
202
+
203
+ class ReadAttachment:
204
+ """agent_hooks.Skill READ_ATTACHMENT: a page of an attached file, fenced as data."""
205
+
206
+ op = "READ_ATTACHMENT"
207
+ prompt = ("- READ_ATTACHMENT text: read more of a file the user attached; text is its name, then \"page N\" for "
208
+ "a PDF's page or a later part of a long text (\"menu.pdf page 2\"). The text comes back next turn.")
209
+ needs_target = False
210
+
211
+ def __init__(self, store, ctx):
212
+ self.store, self.ctx = store, ctx
213
+
214
+ def available(self, ctx):
215
+ return True
216
+
217
+ def prepare(self, ctx, act, snapshot):
218
+ return Prepared(None)
219
+
220
+ def perform(self, ctx, prepared, act, snapshot):
221
+ files = run_files(self.ctx, self.store)
222
+ found = READ_TEXT.match(str(act.get("text") or ""))
223
+ attachment = find(files, found.group("name") if found else "")
224
+ if attachment is None:
225
+ names = ", ".join(f"“{a.name}”" for a in files) or "none"
226
+ return SkillResult(feedback=f"READ_ATTACHMENT needs one file's name. The attached files: {names}.")
227
+ parts = chunks(attachment, self.store.text(attachment))
228
+ page = int(found.group("page")) if found and found.group("page") else 1
229
+ if not 1 <= page <= len(parts):
230
+ return SkillResult(feedback=f"“{attachment.name}” has {len(parts)} page"
231
+ f"{'' if len(parts) == 1 else 's'}; ask for page 1 to {len(parts)}.")
232
+ text = parts[page - 1].strip() or "(no text on this page)"
233
+ if len(text) > READ_CHARS:
234
+ text = text[:READ_CHARS - 1] + "…"
235
+ try:
236
+ self.ctx.emit({"event": "attachment_read", "id": attachment.id, "name": attachment.name,
237
+ "page": page, **({"pages": len(parts)} if attachment.kind == "pdf" else {})})
238
+ except Exception: # noqa: BLE001
239
+ pass
240
+ title = _defang(f"{attachment.name}, page {page} of {len(parts)}")
241
+ more = f"\n(Next: READ_ATTACHMENT \"{attachment.name} page {page + 1}\".)" if page < len(parts) else ""
242
+ return SkillResult(feedback=harness_api.fence(title, _defang(text)) + more, changed=False)
243
+
244
+
245
+ PUT_TEXT = re.compile(r"^\s*(?P<name>.+?)\s*(?:>|->|→|\bto\b|\binto\b|\bin\b)\s*(?P<dest>[A-Za-z0-9:._-]+)\s*$", re.I)
246
+ _apps_cache = {}
247
+ _apps_lock = threading.Lock()
248
+ APPS_TTL = 60.0
249
+
250
+
251
+ def file_sharing_apps(phone_files):
252
+ """The phone's apps that share files, cached a minute per iPhone."""
253
+ key = phone_files.udid
254
+ with _apps_lock:
255
+ at, apps = _apps_cache.get(key, (0.0, None))
256
+ if apps is not None and time.monotonic() - at < APPS_TTL:
257
+ return apps
258
+ apps = phone_files.apps()
259
+ with _apps_lock:
260
+ _apps_cache[key] = (time.monotonic(), apps)
261
+ return apps
262
+
263
+
264
+ class _Plan:
265
+ def __init__(self, refusal=None, attachment=None, destination=None, app_name=None, phone=None):
266
+ self.refusal, self.attachment, self.destination = refusal, attachment, destination
267
+ self.app_name, self.phone = app_name, phone
268
+
269
+
270
+ class PutFile:
271
+ """agent_hooks.Skill PUT_FILE: an attached file into an app's folder or onto the clipboard, after the user's OK (its
272
+ ``prepare`` returns the question, so the frontier asks; with no way to ask, the copy never runs)."""
273
+
274
+ op = "PUT_FILE"
275
+ prompt = ("- PUT_FILE text: put a file the user attached on the iPhone, only when the task needs it there; text "
276
+ "is the file's name, \" > \", then clipboard (a text file, to paste), photos (an image, into the Photos "
277
+ "library so any app's photo picker can attach it) or the bundle id of an app whose folder shows in Files "
278
+ "(\"menu.pdf > com.apple.Pages\", \"beach.jpg > photos\"). Mobster asks the user first.")
279
+ needs_target = False
280
+
281
+ def __init__(self, store, ctx):
282
+ self.store, self.ctx = store, ctx
283
+
284
+ def available(self, ctx):
285
+ return True
286
+
287
+ def _device_name(self):
288
+ try:
289
+ from .service import device_record
290
+ return _short(device_record(self.ctx.runtime, self.ctx.device_id)["name"], DEVICE_CHARS)
291
+ except Exception: # noqa: BLE001
292
+ return "your iPhone"
293
+
294
+ def prepare(self, ctx, act, snapshot):
295
+ found = PUT_TEXT.match(str(act.get("text") or ""))
296
+ files = run_files(self.ctx, self.store)
297
+ if not found:
298
+ return Prepared(None, _Plan("PUT_FILE needs the file's name, \" > \", then clipboard or an app's bundle "
299
+ "id, such as \"menu.pdf > com.apple.Pages\"."))
300
+ attachment = find(files, found.group("name"))
301
+ if attachment is None:
302
+ names = ", ".join(f"“{a.name}”" for a in files) or "none"
303
+ return Prepared(None, _Plan(f"PUT_FILE found no attached file by that name. The attached files: {names}."))
304
+ destination = found.group("dest")
305
+ destination = destination[4:] if destination.lower().startswith("app:") else destination
306
+ device = self._device_name()
307
+ if destination.lower() == "clipboard":
308
+ if attachment.kind not in ("text", "csv"):
309
+ return Prepared(None, _Plan(f"Only a text file goes on the clipboard; “{attachment.name}” is "
310
+ f"{attachment.data.get('label') or attachment.kind}. Put it in an app's "
311
+ "folder instead, or type what is needed."))
312
+ if (attachment.data.get("bytes") or 0) > CLIPBOARD_MAX:
313
+ return Prepared(None, _Plan(f"“{attachment.name}” is over 64 KB, too long for the clipboard."))
314
+ return Prepared(f"Put the text of “{_short(attachment.name, NAME_CHARS)}” on {device}'s clipboard?",
315
+ _Plan(attachment=attachment, destination="clipboard"))
316
+ if destination.lower() == "photos":
317
+ if attachment.kind != "image":
318
+ return Prepared(None, _Plan(f"Only a picture goes into Photos; “{attachment.name}” is "
319
+ f"{attachment.data.get('label') or attachment.kind}."))
320
+ return Prepared(f"Add “{_short(attachment.name, NAME_CHARS)}” to Photos on {device}?",
321
+ _Plan(attachment=attachment, destination="photos"))
322
+ from ..permissions import from_env as permission_settings
323
+ apps = permission_settings().apps
324
+ if not apps.allows(destination, outside=not self.ctx.interactive):
325
+ return Prepared(None, _Plan(apps.blocked_message(destination, outside=apps.allows(destination))))
326
+ if not self._approved(ctx, destination, apps):
327
+ # The app the file goes into has to be one the person allowed (the run's guard asks the first time).
328
+ return Prepared(None, _Plan(apps.unapproved_message(destination)))
329
+ try:
330
+ from . import service
331
+ record = service.device_record(self.ctx.runtime, self.ctx.device_id)
332
+ phone = service.phone_files(record)
333
+ apps = file_sharing_apps(phone)
334
+ except Exception as error: # noqa: BLE001 -- a phone that can't take files is a sentence for the model
335
+ return Prepared(None, _Plan(f"PUT_FILE can't reach the iPhone's files: {_sentence(error)}"))
336
+ app = next((a for a in apps if a["bundleId"] == destination), None)
337
+ if app is None:
338
+ names = ", ".join(f"{a['name']} ({a['bundleId']})" for a in apps[:12]) or "none on this iPhone"
339
+ return Prepared(None, _Plan(f"{destination} doesn't keep files you can see in Files. Apps that do: "
340
+ f"{names}."))
341
+ title = f"Put “{_short(attachment.name, NAME_CHARS)}” in {_short(app['name'], APP_CHARS)} on {device}?"
342
+ return Prepared(title, _Plan(attachment=attachment, destination=destination, app_name=app["name"], phone=phone))
343
+
344
+ @staticmethod
345
+ def _approved(ctx, destination, apps):
346
+ """Whether the person allowed ``destination``: through the run's guard (it asks the first time), else the
347
+ approvals store."""
348
+ from ..agent_hooks import safe_allows
349
+ guard = getattr(ctx, "guard", None)
350
+ if guard is not None:
351
+ return safe_allows(guard, destination)
352
+ try:
353
+ from ..permissions.exceptions import Exceptions, default_path
354
+ return apps.approved(destination, Exceptions(default_path()))
355
+ except Exception: # noqa: BLE001
356
+ return False
357
+
358
+ def perform(self, ctx, prepared, act, snapshot):
359
+ plan = prepared.state if isinstance(prepared.state, _Plan) else _Plan("PUT_FILE didn't run.")
360
+ if plan.refusal or plan.attachment is None:
361
+ return SkillResult(feedback=plan.refusal or "PUT_FILE didn't run.")
362
+ attachment = plan.attachment
363
+ size = attachment.data.get("bytes") or 0
364
+ if plan.destination == "clipboard":
365
+ from ..phone_io.clipboard import set_text
366
+ driver = ctx.driver
367
+ text = self.store.whole_text(attachment)
368
+ if text is None:
369
+ return SkillResult(feedback=f"“{attachment.name}” is no longer on this Mac.")
370
+ try:
371
+ set_text(lambda method, path, body, timeout: driver.call(method, path, body, timeout=timeout), text)
372
+ except Exception as error: # noqa: BLE001
373
+ self._event(attachment, size, "clipboard", "failed")
374
+ return SkillResult(feedback=f"PUT_FILE couldn't set the clipboard: {_sentence(error)}")
375
+ self._event(attachment, size, "clipboard", "copied")
376
+ return SkillResult(feedback=f"The text of “{attachment.name}” is on the clipboard now: paste it with a "
377
+ "long press in a field, then Paste.", changed=False)
378
+ original = self.store.path(attachment, "original")
379
+ if original is None:
380
+ return SkillResult(feedback=f"“{attachment.name}” is no longer on this Mac.")
381
+ if plan.destination == "photos":
382
+ from ..phone_io.photos import send
383
+ driver = ctx.driver
384
+ try:
385
+ send(lambda method, path, body, timeout: driver.call(method, path, body, timeout=timeout), original,
386
+ name=self._device_name(),
387
+ activate=lambda bundle: driver.call("POST", "/wda/apps/activate", {"bundleId": bundle},
388
+ timeout=15),
389
+ front=getattr(driver, "active_app", None))
390
+ except Exception as error: # noqa: BLE001
391
+ self._event(attachment, size, "photos", "failed")
392
+ return SkillResult(feedback=f"PUT_FILE couldn't add “{attachment.name}” to Photos: {_sentence(error)}")
393
+ self._event(attachment, size, "photos", "copied")
394
+ return SkillResult(feedback=f"Added “{attachment.name}” to Photos: it is the newest item in the photo "
395
+ "picker now.", changed=False)
396
+ try:
397
+ result = plan.phone.put(plan.destination, original, name=attachment.name)
398
+ except Exception as error: # noqa: BLE001
399
+ self._event(attachment, size, f"app:{plan.destination}", "failed")
400
+ return SkillResult(feedback=f"PUT_FILE couldn't copy “{attachment.name}”: {_sentence(error)}")
401
+ self._event(attachment, size, f"app:{plan.destination}", "copied")
402
+ renamed = f" (as “{result['name']}”, since that name was taken)" if result["name"] != attachment.name else ""
403
+ return SkillResult(feedback=f"Put “{attachment.name}” in {plan.app_name}{renamed}. In the Files app it is "
404
+ f"under On My iPhone › {plan.app_name}, and {plan.app_name} can open it.",
405
+ changed=False)
406
+
407
+ def _event(self, attachment, size, destination, status):
408
+ try:
409
+ self.ctx.emit({"event": "file_put", "name": attachment.name, "bytes": size, "destination": destination,
410
+ "status": status})
411
+ except Exception: # noqa: BLE001
412
+ pass
413
+
414
+
415
+ def _sentence(error):
416
+ text = " ".join(str(error).split())[:240] or type(error).__name__
417
+ fix = getattr(error, "fix", "")
418
+ return (text if text.endswith((".", "?", "!")) else text + ".") + (f" {fix}" if fix else "")