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,289 @@
1
+ """Files to and from an iPhone app's Documents folder, the one Files shows under On My iPhone › <App>.
2
+
3
+ Only apps that share files (``UIFileSharingEnabled``: Pages, Numbers, Keynote, VLC, Documents…) have one; ``apps``
4
+ lists them from the phone's installed apps (ideviceinstaller). ``put``, ``get`` and ``ls`` go through Apple's file
5
+ service for those folders (AFC house_arrest, "VendDocuments") with ``afcclient``: the copy bundled in the Mac app,
6
+ else Homebrew's. Nothing else on the phone is reachable: not other apps' data, not the camera roll.
7
+
8
+ Rules every caller gets:
9
+ - **USB only.** A phone on Wi-Fi answers ``needs_cable`` until the wireless probe shows the file service runs
10
+ through the encrypted tunnel. afcclient is never given ``-n``/``--network``.
11
+ - **Names**: sniff.clean_name (no ``/``, ``..`` or control characters), so nothing lands outside the app's folder;
12
+ remote paths are always ``/<name>`` at the folder's top.
13
+ - **100 MB** a file. A put never overwrites: a name that is taken becomes "menu 2.pdf" (the CLI's ``--replace``
14
+ overwrites on purpose). Each put is checked by listing the folder after.
15
+ - Under ``MOBSTER_NO_DEVICES=1`` (local CI) the real tools never run: RuntimeError.
16
+
17
+ Who approves: the agent's PUT_FILE asks first (its title), the Mac app's "Put on iPhone…" is the user's own click
18
+ (and needs the app session), ``mobster phone file put`` is the user's own command, and MCP's put_file needs
19
+ ``mobster mcp --allow-files``.
20
+ """
21
+
22
+ import os
23
+ import plistlib
24
+ import re
25
+ import stat
26
+ import subprocess
27
+ import tempfile
28
+ import time
29
+ from contextlib import contextmanager
30
+ from pathlib import Path
31
+
32
+ from . import PhoneIOError
33
+
34
+ MAX_BYTES = 100 * 1024 * 1024
35
+ TIMEOUT = 120
36
+ LIST_TIMEOUT = 30
37
+ BUNDLE = re.compile(r"[A-Za-z0-9][A-Za-z0-9.-]{0,154}")
38
+ ANSI = re.compile(r"\x1b\[[0-9;]*m")
39
+ LINE = re.compile(r"^([-dlfbcs])[rwx-]{9}\s+\d+\s+mobile\s+mobile\s+(\d+)\s+(\d{2} \S{3} \d{4} \d{2}:\d{2}:\d{2}) "
40
+ r"(.+)$")
41
+ NO_SHARING = re.compile(r"UIFileSharingEnabled|InstallationLookupFailed|not present on the device", re.I)
42
+ NEEDS_TOOLS = ("Install the iPhone tools: brew install libimobiledevice", "tools")
43
+ NEEDS_CABLE = "Files move only over the cable for now. Plug the iPhone into this Mac, then try again."
44
+
45
+
46
+ def installer_tool():
47
+ from ..mac_tools import tool
48
+ return tool("ideviceinstaller")
49
+
50
+
51
+ def _run(argv, timeout):
52
+ """(exit code, standard output and error) of ``argv``, no shell, no colours, the C locale."""
53
+ from ..native_helpers import require_devices
54
+ require_devices()
55
+ env = dict(os.environ, NO_COLOR="1", LC_ALL="C", LANG="C")
56
+ try:
57
+ done = subprocess.run(argv, capture_output=True, text=True, errors="replace", timeout=timeout,
58
+ stdin=subprocess.DEVNULL, env=env)
59
+ except subprocess.TimeoutExpired:
60
+ raise PhoneIOError(f"The iPhone didn't answer within {timeout} seconds.", "failed",
61
+ "Keep it unlocked and plugged in, then try again.") from None
62
+ except OSError as error:
63
+ raise PhoneIOError(f"The iPhone tools couldn't run ({type(error).__name__}).", "tools") from None
64
+ return done.returncode, (done.stdout or "") + (done.stderr or "")
65
+
66
+
67
+ class PhoneFiles:
68
+ """The file service for one device ``record`` (kind, udid, name, transport)."""
69
+
70
+ def __init__(self, record, runner=None, afc=None, installer=None, container=False):
71
+ self.record = record or {}
72
+ # container: the app's whole sandbox (house_arrest's VendContainer), its Documents folder inside; only a
73
+ # development build of your own (signed with get-task-allow) opens it. Otherwise the folder Files shows.
74
+ self.container = bool(container)
75
+ self.base = "/Documents/" if self.container else "/"
76
+ self._runner = runner
77
+ self._afc_path, self._installer = afc, installer
78
+ if self.record.get("kind") != "usb":
79
+ what = "a simulator" if self.record.get("kind") == "simulator" else "not an iPhone on a cable"
80
+ raise PhoneIOError(f"“{self.name}” is {what}. Mobster moves files to iPhones plugged into this Mac.",
81
+ "usage")
82
+ if self.record.get("transport") == "wifi":
83
+ raise PhoneIOError(f"“{self.name}” is on Wi-Fi. " + NEEDS_CABLE, "needs_cable")
84
+ udid = str(self.record.get("udid") or "")
85
+ if not re.fullmatch(r"[0-9A-Fa-f-]{24,40}", udid):
86
+ raise PhoneIOError(f"Mobster doesn't know which iPhone “{self.name}” is yet.", "no_device",
87
+ "Set it up in the Mobster app.")
88
+ self.udid = udid
89
+
90
+ @property
91
+ def name(self):
92
+ return self.record.get("name") or self.record.get("id") or "the iPhone"
93
+
94
+ def run(self, argv, timeout):
95
+ if self._runner is not None:
96
+ return self._runner(argv, timeout)
97
+ return _run(argv, timeout)
98
+
99
+ def _afcclient(self):
100
+ from ..mac_tools import tool
101
+ found = self._afc_path or tool("afcclient")
102
+ if not found:
103
+ raise PhoneIOError("Mobster needs the iPhone tools to move files.", NEEDS_TOOLS[1], NEEDS_TOOLS[0])
104
+ return found
105
+
106
+ def apps(self):
107
+ """[{"bundleId", "name"}] of the apps whose Documents folder shows in Files, sorted by name."""
108
+ tool = self._installer or installer_tool()
109
+ if not tool:
110
+ raise PhoneIOError("Mobster needs the iPhone tools to list the apps.", NEEDS_TOOLS[1], NEEDS_TOOLS[0])
111
+ argv = [tool, "-u", self.udid, "list", "--all", "--xml", "-a", "CFBundleIdentifier", "-a",
112
+ "CFBundleDisplayName", "-a", "CFBundleName", "-a", "UIFileSharingEnabled"]
113
+ code, output = self.run(argv, LIST_TIMEOUT)
114
+ if code != 0:
115
+ raise _explain(output, self.name)
116
+ start = output.find("<?xml")
117
+ try:
118
+ items = plistlib.loads(output[start if start >= 0 else 0:].encode())
119
+ except Exception: # noqa: BLE001
120
+ raise PhoneIOError("The iPhone's app list couldn't be read.", "failed") from None
121
+ apps = []
122
+ for item in items if isinstance(items, list) else ():
123
+ if not isinstance(item, dict) or item.get("UIFileSharingEnabled") is not True:
124
+ continue
125
+ bundle = item.get("CFBundleIdentifier")
126
+ if not isinstance(bundle, str) or not BUNDLE.fullmatch(bundle):
127
+ continue
128
+ name = item.get("CFBundleDisplayName") or item.get("CFBundleName") or bundle
129
+ apps.append({"bundleId": bundle, "name": " ".join(str(name).split())[:60]})
130
+ return sorted(apps, key=lambda app: (app["name"].casefold(), app["bundleId"]))
131
+
132
+ def _afc(self, app, *command, timeout=TIMEOUT):
133
+ if not isinstance(app, str) or not BUNDLE.fullmatch(app):
134
+ raise PhoneIOError("Name the app by its bundle ID, such as com.apple.Pages.", "usage")
135
+ # "--" ends afcclient's own options: macOS's getopt_long reorders arguments, so without it the command's
136
+ # flags ("ls -l", "put -f") would be read as afcclient's and refused as unknown options.
137
+ argv = [self._afcclient(), "-u", self.udid, "--container" if self.container else "--documents", app, "--",
138
+ *map(str, command)]
139
+ code, output = self.run(argv, timeout)
140
+ output = ANSI.sub("", output)
141
+ if NO_SHARING.search(output) or (self.container and re.search(r"InstallationLookupFailed|not allowed|"
142
+ r"Permission", output, re.I)):
143
+ if self.container:
144
+ raise PhoneIOError(f"{app}'s own folder opens only for a development build of yours on {self.name}.",
145
+ "no_file_sharing", "Install a debug build from Xcode, or use its Documents folder.")
146
+ raise PhoneIOError(f"{app} doesn't keep files you can see in Files, or it isn't on {self.name}.",
147
+ "no_file_sharing", "Choose an app that does, such as Pages, Numbers or Keynote.")
148
+ if code != 0:
149
+ raise _explain(output, self.name)
150
+ errors = [line for line in output.splitlines() if line.startswith(("Error:", "ERROR:"))]
151
+ return output, errors
152
+
153
+ def ls(self, app):
154
+ """[{"name", "bytes", "modifiedAt" (ms), "folder"}] at the top of ``app``'s folder."""
155
+ output, errors = self._afc(app, "ls", "-l", self.base, timeout=LIST_TIMEOUT)
156
+ if errors:
157
+ raise _explain("\n".join(errors), self.name)
158
+ entries = []
159
+ for line in output.splitlines():
160
+ found = LINE.match(line.rstrip("\n"))
161
+ if not found:
162
+ continue
163
+ kind, size, stamp, name = found.groups()
164
+ try:
165
+ modified = round(time.mktime(time.strptime(stamp, "%d %b %Y %H:%M:%S")) * 1000)
166
+ except (ValueError, OverflowError):
167
+ modified = None
168
+ entries.append({"name": name, "bytes": int(size), "modifiedAt": modified, "folder": kind == "d"})
169
+ return sorted(entries, key=lambda e: e["name"].casefold())
170
+
171
+ def put(self, app, local, name=None, replace=False, *, before_copy=None):
172
+ """Copy ``local`` into ``app``'s folder, never over a name already there; ``before_copy()`` may refuse."""
173
+ from ..attachments.sniff import clean_name
174
+ source = Path(local).expanduser()
175
+ wanted = clean_name(name or source.name)
176
+ with snapshot(source, MAX_BYTES) as staged:
177
+ size = staged.stat().st_size
178
+ existing = {entry["name"] for entry in self.ls(app)}
179
+ target = wanted if replace or wanted not in existing else _free_name(wanted, existing)
180
+ command = ["put", "-f"] if replace else ["put"]
181
+ if before_copy is not None:
182
+ before_copy()
183
+ _, errors = self._afc(app, *command, str(staged), self.base + target)
184
+ if errors:
185
+ raise _explain("\n".join(errors), self.name)
186
+ arrived = next((e for e in self.ls(app) if e["name"] == target and not e["folder"]), None)
187
+ if arrived is None or arrived["bytes"] != size:
188
+ raise PhoneIOError(f"{target} didn't arrive whole on {self.name}.", "failed",
189
+ "Keep the iPhone unlocked and plugged in, then try again.")
190
+ return {"name": target, "path": self.base + target, "bytes": size, "app": app}
191
+
192
+ def get(self, app, name, folder):
193
+ """Copy ``name`` from ``app``'s folder into the Mac folder ``folder``. Returns the local Path."""
194
+ from ..attachments.sniff import clean_name
195
+ wanted = clean_name(name)
196
+ if wanted != name:
197
+ raise PhoneIOError(f"There is no file named {name!r} in {app}'s folder.", "usage")
198
+ entry = next((e for e in self.ls(app) if e["name"] == wanted), None)
199
+ if entry is None:
200
+ raise PhoneIOError(f"There is no file named {wanted} in {app}'s folder on {self.name}.", "not_found",
201
+ "List the folder with `mobster phone file ls --app " + app + "`.")
202
+ if entry["folder"]:
203
+ raise PhoneIOError(f"{wanted} is a folder; Mobster copies files.", "usage")
204
+ if entry["bytes"] > MAX_BYTES:
205
+ raise PhoneIOError(f"{wanted} is over 100 MB; Mobster moves files up to 100 MB.", "usage")
206
+ folder = Path(folder)
207
+ folder.mkdir(parents=True, exist_ok=True)
208
+ with tempfile.TemporaryDirectory(prefix="mobster-get-", dir=folder) as scratch:
209
+ partial = Path(scratch) / "file"
210
+ _, errors = self._afc(app, "get", self.base + wanted, str(partial))
211
+ if errors or not partial.is_file():
212
+ raise _explain("\n".join(errors) or "Error: nothing arrived", self.name)
213
+ if partial.stat().st_size != entry["bytes"]:
214
+ raise PhoneIOError(f"{wanted} didn't arrive whole.", "failed", "Try again.")
215
+ return land(partial, folder, wanted)
216
+
217
+
218
+ def land(partial, folder, name, *, exact=False):
219
+ """Move the finished ``partial`` into ``folder`` as ``name``, or a free name next to it ("menu 2.pdf") when ``name``
220
+ is taken. Returns the local Path. The name is claimed with a hard link, which fails if the name exists, so two
221
+ runs landing into one folder never replace each other's file; ``partial`` must be on the same filesystem.
222
+ ``exact`` raises FileExistsError instead of choosing another name."""
223
+ folder = Path(folder)
224
+ stem, ext = os.path.splitext(name)
225
+ for number in range(1, 10_000):
226
+ target = folder / (name if number == 1 else f"{stem} {number}{ext}")
227
+ try:
228
+ os.link(partial, target)
229
+ except FileExistsError:
230
+ if exact:
231
+ raise
232
+ continue
233
+ except OSError: # a volume without hard links (exFAT, some network shares)
234
+ if target.exists():
235
+ if exact:
236
+ raise FileExistsError(target) from None
237
+ continue
238
+ os.replace(partial, target)
239
+ return target
240
+ Path(partial).unlink()
241
+ return target
242
+ raise PhoneIOError("That folder has too many files with this name.", "failed")
243
+
244
+
245
+ @contextmanager
246
+ def snapshot(source, limit):
247
+ """A bounded, private copy of one regular source file, removed after its consumer finishes."""
248
+ source = Path(source).expanduser()
249
+ try:
250
+ stream = open(source, "rb", opener=lambda path, flags: os.open(path, flags | os.O_NONBLOCK))
251
+ except OSError:
252
+ raise PhoneIOError(f"There is no file at {source}.", "usage") from None
253
+ with stream, tempfile.TemporaryDirectory(prefix="mobster-upload-") as folder:
254
+ if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode):
255
+ raise PhoneIOError(f"{source.name} isn't a file.", "usage")
256
+ staged = Path(folder) / source.name
257
+ with open(staged, "xb", opener=lambda path, flags: os.open(path, flags, 0o600)) as out:
258
+ size = 0
259
+ while block := stream.read(min(1 << 20, limit + 1 - size)):
260
+ size += len(block)
261
+ if size > limit:
262
+ units = f"{limit / 1024 ** 3:g} GB" if limit >= 1024 ** 3 else f"{limit / 1024 ** 2:g} MB"
263
+ raise PhoneIOError(f"{source.name} is over {units}.", "usage")
264
+ out.write(block)
265
+ yield staged
266
+
267
+
268
+ def _free_name(name, taken):
269
+ """``name`` made unique among ``taken``, as Finder does: "menu 2.pdf", "menu 3.pdf"…"""
270
+ stem, ext = os.path.splitext(name)
271
+ for number in range(2, 10_000):
272
+ candidate = f"{stem} {number}{ext}"
273
+ if candidate not in taken:
274
+ return candidate
275
+ raise PhoneIOError("That folder has too many files with this name.", "failed")
276
+
277
+
278
+ def _explain(output, name):
279
+ """A PhoneIOError for a failed tool run, from its output."""
280
+ text = " ".join((output or "").split())
281
+ if re.search(r"No device found|Device .* not found|not connected", text, re.I):
282
+ return PhoneIOError(f"{name} isn't plugged in.", "no_device", "Plug it into this Mac and unlock it.")
283
+ if re.search(r"lockdown|pair|trust|password protected|PasswordProtected", text, re.I):
284
+ return PhoneIOError(f"{name} is locked or doesn't trust this Mac.", "locked",
285
+ "Unlock it, tap Trust if it asks, then try again.")
286
+ if re.search(r"Object not found|No such file", text, re.I):
287
+ return PhoneIOError("That file isn't on the iPhone.", "not_found")
288
+ return PhoneIOError(f"Moving the file failed{': ' + text[:200] if text else ''}.", "failed",
289
+ "Keep the iPhone unlocked and plugged in, then try again.")
@@ -0,0 +1,123 @@
1
+ """Files from this Mac to an iPhone with no cable: AirDrop to the one recipient named exactly, or iPhone Mirroring."""
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from . import PhoneIOError
9
+
10
+ AIRDROP_WAIT_S = 30
11
+ HELPER_SLACK_S = 30
12
+
13
+ PERMISSION = ("Mobster needs Accessibility to use AirDrop and iPhone Mirroring for you.",
14
+ "Turn it on in System Settings › Privacy & Security › Accessibility, then try again.")
15
+
16
+
17
+ def helper(build=True):
18
+ """The transfer helper's path: the Mac app's copy, else a build from source (Xcode's command line tools)."""
19
+ from .. import native_helpers
20
+ found = native_helpers.find("transfer")
21
+ if found is None and build:
22
+ try:
23
+ found = native_helpers.build("transfer")
24
+ except (LookupError, OSError, subprocess.SubprocessError, RuntimeError):
25
+ found = None
26
+ if found is None:
27
+ raise PhoneIOError("This Mac is missing Mobster's transfer helper.", "tools",
28
+ "Install Xcode's command line tools (xcode-select --install), or use the Mobster app.")
29
+ return str(found)
30
+
31
+
32
+ def _run(argv, timeout, runner=None):
33
+ from ..native_helpers import require_devices
34
+ require_devices()
35
+ if runner is not None:
36
+ return runner(argv, timeout)
37
+ try:
38
+ done = subprocess.run(argv, capture_output=True, text=True, timeout=timeout, stdin=subprocess.DEVNULL)
39
+ except subprocess.TimeoutExpired:
40
+ return None
41
+ except OSError as error:
42
+ raise PhoneIOError(f"The transfer helper couldn't run ({type(error).__name__}).", "tools") from None
43
+ return done.stdout
44
+
45
+
46
+ def _answer(output):
47
+ for line in reversed((output or "").strip().splitlines()):
48
+ try:
49
+ value = json.loads(line)
50
+ except ValueError:
51
+ continue
52
+ if isinstance(value, dict):
53
+ return value
54
+ return None
55
+
56
+
57
+ def _files(paths):
58
+ files = [str(Path(p).expanduser().resolve()) for p in paths]
59
+ if not files:
60
+ raise PhoneIOError("Name at least one file.", "usage")
61
+ for file in files:
62
+ if not os.path.isfile(file):
63
+ raise PhoneIOError(f"There is no file at {file}.", "usage")
64
+ return files
65
+
66
+
67
+ def airdrop(paths, to, *, wait=AIRDROP_WAIT_S, transfer_wait=900, runner=None, tool=None):
68
+ """AirDrop ``paths`` to the device named exactly ``to``; ``unknown``: the helper went silent after the press."""
69
+ if not isinstance(to, str) or not to.strip() or len(to) > 120:
70
+ raise PhoneIOError("Name the iPhone AirDrop should send to.", "usage")
71
+ files = _files(paths)
72
+ argv = [tool or helper(), "airdrop", "--to", to, "--timeout", str(int(wait)), "--transfer-timeout",
73
+ str(int(transfer_wait)), *files]
74
+ output = _run(argv, wait + transfer_wait + HELPER_SLACK_S, runner)
75
+ answer = _answer(output)
76
+ if answer is None:
77
+ raise PhoneIOError(f"AirDrop to {to} stopped answering, so the files may or may not have arrived.",
78
+ "unknown", "Look in Photos on the iPhone before you send them again.")
79
+ if answer.get("ok"):
80
+ return {"sent": int(answer.get("sent") or len(files)), "seconds": float(answer.get("seconds") or 0)}
81
+ code = answer.get("code")
82
+ if code == "permission":
83
+ raise PhoneIOError(PERMISSION[0], "permission", PERMISSION[1])
84
+ if code == "not_found":
85
+ error = PhoneIOError(f"AirDrop doesn't see {to}.", "airdrop_not_found",
86
+ "Unlock it, keep it near this Mac with Wi-Fi and Bluetooth on, and set AirDrop to "
87
+ "Contacts Only or Everyone for 10 Minutes.")
88
+ error.seen = [str(name)[:80] for name in answer.get("seen") or ()][:20]
89
+ raise error
90
+ if code == "ambiguous":
91
+ raise PhoneIOError(f"More than one device near this Mac is called {to}, so Mobster sent nothing.",
92
+ "airdrop_ambiguous", "Give the iPhone its own name in Settings › General › About › Name.")
93
+ if code in ("failed", "cancelled"):
94
+ raise PhoneIOError(f"AirDrop to {to} didn't go through: {answer.get('error') or 'it was declined'}.",
95
+ "airdrop_declined", "Accept it on the iPhone, then try again.")
96
+ if code == "timeout":
97
+ raise PhoneIOError(f"AirDrop to {to} didn't finish in time, so the files may or may not have arrived.",
98
+ "unknown", "Look in Photos on the iPhone before you send them again.")
99
+ raise PhoneIOError(f"AirDrop didn't work: {answer.get('error') or code}.", "failed")
100
+
101
+
102
+ def mirroring_drop(paths, x=0.5, y=0.5, *, runner=None, tool=None):
103
+ """Drop ``paths`` on the iPhone Mirroring window at (x, y), fractions of the window. Returns {"operation"}."""
104
+ files = _files(paths)
105
+ if not (0 <= float(x) <= 1 and 0 <= float(y) <= 1):
106
+ raise PhoneIOError("x and y are fractions of the window, 0 to 1.", "usage")
107
+ argv = [tool or helper(), "drop", "--x", f"{float(x):.4f}", "--y", f"{float(y):.4f}", *files]
108
+ answer = _answer(_run(argv, 30, runner))
109
+ if answer is None:
110
+ raise PhoneIOError("The drop on iPhone Mirroring stopped answering, so the files may or may not have arrived.",
111
+ "unknown", "Look on the iPhone before you send them again.")
112
+ if answer.get("ok"):
113
+ return {"operation": int(answer.get("operation") or 1)}
114
+ code = answer.get("code")
115
+ if code == "permission":
116
+ raise PhoneIOError(PERMISSION[0], "permission", PERMISSION[1])
117
+ if code == "no_window":
118
+ raise PhoneIOError("iPhone Mirroring isn't showing the iPhone.", "no_mirroring",
119
+ "Open iPhone Mirroring, lock the iPhone and keep it near this Mac, then try again.")
120
+ if code == "refused":
121
+ raise PhoneIOError("iPhone Mirroring didn't take the files: the app in front on the iPhone doesn't take drops.",
122
+ "mirroring_refused", "Open Photos or Files on the iPhone, then try again.")
123
+ raise PhoneIOError(f"The drop on iPhone Mirroring didn't work: {answer.get('error') or code}.", "failed")
@@ -0,0 +1,118 @@
1
+ """Photos both ways through iCloud Photos, when the person has it on, on this Mac and the iPhone.
2
+
3
+ Mac to iPhone: ``import_media`` adds the files to this Mac's Photos library (into the album ``album``) with Photos'
4
+ own AppleScript import; iCloud Photos then carries them to every device signed in to the same Apple Account, usually
5
+ within a minute or two on Wi-Fi. iPhone to Mac: ``export_recent`` copies the newest items in this Mac's library (which
6
+ iCloud fills from the iPhone) to a folder, originals as they are.
7
+
8
+ It is the slow route: Mobster uses it only when you choose it (``--via icloud``, or the queue's route), never as a
9
+ silent fallback. It needs Automation permission for Photos the first time (macOS asks). Photos opens in the
10
+ background to do it.
11
+ """
12
+
13
+ import os
14
+ import subprocess
15
+ from pathlib import Path
16
+
17
+ from . import PhoneIOError
18
+
19
+ OSASCRIPT = "/usr/bin/osascript"
20
+ TIMEOUT_S = 300
21
+
22
+ # Each script takes its inputs as arguments (argv), never pasted into its source.
23
+ IMPORT_SCRIPT = """
24
+ on run argv
25
+ set albumName to item 1 of argv
26
+ set theFiles to {}
27
+ repeat with i from 2 to count of argv
28
+ set end of theFiles to (POSIX file (item i of argv)) as alias
29
+ end repeat
30
+ tell application "Photos"
31
+ if albumName is "" then
32
+ set imported to import theFiles skip check duplicates true
33
+ else
34
+ if not (exists album albumName) then make new album named albumName
35
+ set imported to import theFiles into album albumName skip check duplicates true
36
+ end if
37
+ return count of imported
38
+ end tell
39
+ end run
40
+ """
41
+
42
+ EXPORT_SCRIPT = """
43
+ on run argv
44
+ set target to POSIX file (item 1 of argv) as alias
45
+ set wanted to (item 2 of argv) as integer
46
+ tell application "Photos"
47
+ set total to count of media items
48
+ if total is 0 then return "[]"
49
+ set first_index to total - wanted + 1
50
+ if first_index < 1 then set first_index to 1
51
+ set picked to items first_index thru total of (get media items)
52
+ export picked to target with using originals
53
+ set names to {}
54
+ repeat with one in picked
55
+ set end of names to filename of one
56
+ end repeat
57
+ end tell
58
+ set AppleScript's text item delimiters to linefeed
59
+ return names as text
60
+ end run
61
+ """
62
+
63
+
64
+ def _osascript(script, args, timeout=TIMEOUT_S, runner=None):
65
+ from ..native_helpers import require_devices
66
+ require_devices()
67
+ argv = [OSASCRIPT, "-", *map(str, args)]
68
+ if runner is not None:
69
+ return runner(argv, script, timeout)
70
+ try:
71
+ done = subprocess.run(argv, input=script, capture_output=True, text=True, timeout=timeout)
72
+ except subprocess.TimeoutExpired:
73
+ raise PhoneIOError("Photos on this Mac didn't answer in time.", "unknown",
74
+ "Open Photos and look before you try again.") from None
75
+ except OSError as error:
76
+ raise PhoneIOError(f"Photos couldn't be reached ({type(error).__name__}).", "tools") from None
77
+ return done.returncode, done.stdout, done.stderr
78
+
79
+
80
+ def _explain(code, stderr):
81
+ text = " ".join((stderr or "").split())
82
+ if "-1743" in text or "Not authorized" in text:
83
+ return PhoneIOError("Mobster isn't allowed to use Photos on this Mac.", "permission",
84
+ "Allow it in System Settings › Privacy & Security › Automation › Photos.")
85
+ return PhoneIOError(f"Photos on this Mac refused: {text[:160] or f'exit {code}'}.", "failed")
86
+
87
+
88
+ def import_media(paths, album="Mobster", runner=None):
89
+ """Add ``paths`` to this Mac's Photos library (and ``album``)."""
90
+ files = [str(Path(p).expanduser().resolve()) for p in paths]
91
+ for file in files:
92
+ if not os.path.isfile(file):
93
+ raise PhoneIOError(f"There is no file at {file}.", "usage")
94
+ code, out, err = _osascript(IMPORT_SCRIPT, [album or "", *files], runner=runner)
95
+ if code != 0:
96
+ raise _explain(code, err)
97
+ try:
98
+ count = int((out or "0").strip() or 0)
99
+ except ValueError:
100
+ count = 0
101
+ if count != len(files):
102
+ raise PhoneIOError(f"Photos on this Mac took {count} of {len(files)}.", "failed",
103
+ "Check that each one opens in Photos, then try again.")
104
+ return {"imported": count}
105
+
106
+
107
+ def export_recent(folder, count=1, runner=None):
108
+ """Copy the newest ``count`` items of this Mac's Photos library (originals) into ``folder``. Returns [paths]."""
109
+ folder = Path(folder).expanduser()
110
+ folder.mkdir(parents=True, exist_ok=True)
111
+ before = {p.name for p in folder.iterdir()}
112
+ code, out, err = _osascript(EXPORT_SCRIPT, [str(folder.resolve()), max(1, min(int(count), 20))], runner=runner)
113
+ if code != 0:
114
+ raise _explain(code, err)
115
+ names = [line.strip() for line in (out or "").splitlines() if line.strip()]
116
+ arrived = sorted(p for p in folder.iterdir() if p.name not in before and p.is_file())
117
+ return {"names": names, "paths": [str(p) for p in arrived]}
118
+