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,333 @@
1
+ """Device routes of the local API: a phone's settings, setup, control, screen and stream."""
2
+
3
+ import re
4
+ from urllib.parse import parse_qs, unquote, urlsplit
5
+
6
+ from . import devices as devices_module
7
+ from .api_http import APP_ONLY, APPROVE_IN_APP, APPROVE_IN_APP_ORIGINS, DEVICE_FIELDS, PRIMARY_VIDEO, TAKE_BACK_IN_APP
8
+ from .app_icons import IconUnavailable
9
+ from .devices import AmbiguousDevice, DeviceNotFound
10
+ from .runtime_create import TaskStartMixin
11
+ from .wda import frame_source
12
+ from .wda.video import BOUNDARY as WDA_BOUNDARY
13
+ from .wda.video import VideoUnavailable as WdaVideoUnavailable
14
+
15
+
16
+ def runtime_teach(runtime, body, phone):
17
+ action = body.get("action")
18
+ if action == "save":
19
+ return runtime.teach_save(body, phone)
20
+ if set(body) != {"action"} or action not in ("start", "stop", "discard", "questions"):
21
+ raise ValueError("action must be start, stop, discard, questions or save")
22
+ return {"start": runtime.teach_start, "stop": runtime.teach_stop, "discard": runtime.teach_discard,
23
+ "questions": runtime.teach_questions}[action](phone)
24
+
25
+
26
+ class DeviceRoutesMixin:
27
+ def device_phone(self, ident):
28
+ fleet = getattr(self.runtime, "fleet", None)
29
+ if fleet is None:
30
+ self.send_json({"error": "This Mobster has no device list", "code": "device_not_found"}, 404)
31
+ return None
32
+ try:
33
+ return fleet.lookup(unquote(ident))
34
+ except DeviceNotFound as error:
35
+ self.send_json({"error": str(error), "code": "device_not_found"}, 404)
36
+ except AmbiguousDevice as error:
37
+ self.send_json({"error": str(error), "code": "device_ambiguous"}, 400)
38
+ return None
39
+
40
+ def send_devices(self):
41
+ fleet = getattr(self.runtime, "fleet", None)
42
+ devices = fleet.public() if fleet is not None and self.runtime.pool is None else []
43
+ fanout = getattr(self.runtime, "fanout", None)
44
+ return self.send_json({"devices": devices,
45
+ "defaultDevice": next((item["id"] for item in devices if item["default"]), None),
46
+ "chosenDevices": fleet.chosen(devices) if devices else [],
47
+ "chosenSaved": list(fleet.stored()["chosen"]) if devices else [],
48
+ **({"limits": fanout.limits()} if fanout is not None and devices else {})})
49
+
50
+ def send_device(self, ident, part):
51
+ phone = self.device_phone(ident)
52
+ if phone is None:
53
+ return
54
+ fleet = self.runtime.fleet
55
+ if part is None:
56
+ return self.send_json({"device": fleet.entry(phone.id or ident)[1]})
57
+ video = self.runtime.video if phone.primary else phone.video
58
+ if part in ("stream", "frame") and video is None:
59
+ # A phone plugged in but not set up has no relay yet: never another phone's picture in its place.
60
+ return self.send_json({"error": "This device has no live view yet", "code": "video_unavailable"}, 503)
61
+ if part == "stream":
62
+ return self.send_stream(video, phone)
63
+ if part == "frame":
64
+ return self.send_frame(video)
65
+ if part == "video/status":
66
+ if video is None:
67
+ return self.send_json({"error": "This device has no live view", "code": "video_unavailable"}, 404)
68
+ return self.send_json({**video.status(), "awakeLease": self.runtime.awake.status()})
69
+ if part == "info":
70
+ return self.send_json(phone.info())
71
+ if part == "status":
72
+ return self.send_json(self.runtime.device_status(phone))
73
+ try:
74
+ return self.send_json(fleet.setup_state(phone))
75
+ except LookupError as error:
76
+ return self.send_json({"error": str(error), "code": "setup_unmanaged"}, 404)
77
+
78
+ def owner_action(self, ident, part, body):
79
+ # An agent holding the phone (MCP, Remote, the local API) can't take it back from its owner.
80
+ if self.needs_app(TAKE_BACK_IN_APP):
81
+ return
82
+ if body:
83
+ raise ValueError("Send an empty JSON object")
84
+ phone = self.device_phone(ident)
85
+ if phone is None:
86
+ return
87
+ if part == "take-back":
88
+ self.runtime.take_back(phone if self.runtime.pool is None else None)
89
+ else:
90
+ self.runtime.resume_queue(phone if self.runtime.pool is None else None)
91
+ return self.send_json({"device": self.runtime.fleet.entry(phone.id or ident)[1]})
92
+
93
+ def device_action(self, ident, part, action, body):
94
+ # Gestures and teaching reach the phone with no approval card: the Mac app's only, never another process.
95
+ if (part in ("control", "teach") or action == "photos") and self.needs_app():
96
+ return
97
+ phone = self.device_phone(ident)
98
+ if phone is None:
99
+ return
100
+ if part == "control":
101
+ return self.send_json({"ok": True, "action": self.runtime.control_device(body, phone)})
102
+ if part == "teach":
103
+ return self.send_json(runtime_teach(self.runtime, body, phone))
104
+ fleet = self.runtime.fleet
105
+ try:
106
+ target = fleet.setup_action(phone, action, body)
107
+ return self.send_json(fleet.setup_state(target))
108
+ except KeyError:
109
+ return self.send_json({"error": "Not found"}, 404)
110
+ except LookupError as error:
111
+ return self.send_json({"error": str(error), "code": "setup_refused"}, 409)
112
+
113
+ def start_group(self, body, key, extras, origin):
114
+ if body.get("device") is not None:
115
+ raise ValueError("Name the phones in phones, or one phone in device, not both")
116
+ if body.get("profiles") is not None and (body.get("phones") is not None or extras.get("account")):
117
+ raise ValueError("Name the profiles in profiles, or the phones in phones, and no single profile")
118
+ team_group = body.get("teamGroup")
119
+ if team_group is not None and (origin != "console" or body.get("phones") is None):
120
+ # The team's console labels the same task on each of its Macs' phones; it grants nothing.
121
+ raise ValueError("teamGroup is set by the team's console, for phones only")
122
+ from .threads.service import fingerprint as body_fingerprint
123
+ by_profile = body.get("profiles") is not None
124
+ group, replayed = (self.runtime.start_profiles if by_profile else self.runtime.start_group)(
125
+ body["profiles"] if by_profile else body["phones"], body.get("appId"), body.get("goal"),
126
+ idempotency_key=key, fingerprint=body_fingerprint({**body, "origin": origin}),
127
+ output_schema=body.get("outputSchema"), output_format=body.get("outputFormat", "auto"),
128
+ allowed_bundles=body.get("allowedBundles"),
129
+ engine=body.get("engine"), **({"extras": extras} if extras else {}),
130
+ **({"origin": origin} if origin != "api" else {}), **({"team_group": team_group} if team_group else {}))
131
+ public = self.runtime.fanout.public(group)
132
+ runs = [self.runtime.runs[member["runId"]].public(include_events=False) for member in group["members"]
133
+ if member.get("runId") in self.runtime.runs]
134
+ refused = [{name: member.get(name) for name in ("device", "deviceName", "profile", "profileName", "error",
135
+ "code") if name in member}
136
+ for member in group["members"] if member["state"] == "refused"]
137
+ status = 200 if replayed else 201 if runs else 202
138
+ return self.send_json({"group": public, "groupId": group["id"], "runs": runs, "refused": refused,
139
+ "replayed": replayed}, status)
140
+
141
+ def group_request(self, method, path, body):
142
+ fanout = getattr(self.runtime, "fanout", None)
143
+ if path == "/api/groups" and method == "GET":
144
+ if fanout is None:
145
+ return self.send_json({"groups": [], "limits": None})
146
+ return self.send_json({"groups": fanout.recent(), "limits": fanout.limits()})
147
+ match = re.fullmatch(r"/api/groups/([a-f0-9]{12})(?:/(stop|approvals))?", path)
148
+ if not match or fanout is None:
149
+ return False
150
+ if method == "GET" and not match[2]:
151
+ return self.send_json({"group": fanout.public(fanout.get(match[1]))})
152
+ if method != "POST" or not match[2]:
153
+ return self.send_json({"error": "Not found"}, 404)
154
+ if match[2] == "stop":
155
+ if body:
156
+ raise ValueError("Send an empty JSON object")
157
+ return self.send_json({"group": fanout.public(fanout.stop(match[1]))})
158
+ if set(body) != {"approve", "approvals"}:
159
+ raise ValueError("Answer a group's approvals with approve (true or false) and approvals: each "
160
+ "phone's run id and the approval id you were shown")
161
+ group = fanout.get(match[1])
162
+ if body["approve"] is True:
163
+ if group.get("origin") in APPROVE_IN_APP_ORIGINS and not self.has_app_session():
164
+ return self.send_json({"error": APP_ONLY, "code": "app_only"}, 403)
165
+ if self.needs_app(APPROVE_IN_APP):
166
+ return
167
+ answers = fanout.answer_approvals(match[1], body["approve"], body["approvals"])
168
+ return self.send_json({"answers": answers, "group": fanout.public(group)})
169
+
170
+ def choose_devices(self, body):
171
+ fleet = getattr(self.runtime, "fleet", None)
172
+ if fleet is None or self.runtime.pool is not None:
173
+ return self.send_json({"error": "This Mobster has no device list", "code": "device_not_found"}, 404)
174
+ if set(body) != {"devices"}:
175
+ raise ValueError("Send {\"devices\": [the ids of the phones you chose]}")
176
+ fleet.choose(body["devices"])
177
+ return self.send_devices()
178
+
179
+ def manage_device(self, ident, part, body):
180
+ phone = self.device_phone(ident)
181
+ if phone is None:
182
+ return
183
+ fleet = self.runtime.fleet
184
+ if part is None:
185
+ if not body or set(body) - DEVICE_FIELDS:
186
+ raise ValueError("Change a phone's name, default, askBeforeActing or apps")
187
+ if {"askBeforeActing", "apps"} & set(body) and self.needs_app():
188
+ return
189
+ if "default" in body and type(body["default"]) is not bool:
190
+ raise ValueError("default is true or false")
191
+ changes = {}
192
+ if "name" in body:
193
+ changes["nickname"] = body["name"] if body["name"] is None else devices_module.clean_nickname(
194
+ body["name"])
195
+ for key in ("askBeforeActing", "apps"):
196
+ if key in body:
197
+ changes[key] = body[key]
198
+ try:
199
+ if changes:
200
+ fleet.set_prefs(phone, **changes)
201
+ if body.get("default") is True:
202
+ fleet.set_default(phone)
203
+ elif body.get("default") is False and fleet.stored()["defaultDevice"] == phone.id:
204
+ fleet.set_default(None)
205
+ except LookupError as error:
206
+ return self.send_json({"error": str(error), "code": "device_refused"}, 409)
207
+ return self.send_json({"device": fleet.entry(phone.id)[1]})
208
+ if body:
209
+ raise ValueError("Send an empty JSON object")
210
+ note = None
211
+ try:
212
+ if part == "disconnect":
213
+ fleet.disconnect(phone)
214
+ else:
215
+ note = fleet.connect(phone)
216
+ except LookupError as error:
217
+ return self.send_json({"error": str(error), "code": "device_refused"}, 409)
218
+ return self.send_json({"device": fleet.entry(phone.id)[1], **({"note": note} if note else {})})
219
+
220
+ def setup_action(self, path, body):
221
+ from .devkit import DevError
222
+
223
+ setup = self.runtime.setup
224
+ if setup is None:
225
+ return self.send_json({"error": "Setup is managed outside this app", "code": "setup_unmanaged"}, 404)
226
+ try:
227
+ if path == "/api/setup/build" and set(body) <= {"team"}:
228
+ setup.manager.start_build(body.get("team"))
229
+ elif path == "/api/setup/start" and not body:
230
+ setup.manager.start_runner()
231
+ elif path == "/api/setup/stop" and not body:
232
+ setup.manager.stop_runner(remember=True)
233
+ elif path == "/api/setup/phone" and set(body) == {"udid"}:
234
+ self.runtime.manager.choose(body["udid"])
235
+ elif path == "/api/setup/live" and set(body) == {"enabled"}:
236
+ setup.set_live(body["enabled"])
237
+ elif path == "/api/setup/photos" and not body:
238
+ if self.needs_app(): # iOS's question appears on the person's iPhone: their click in the Mac app
239
+ return
240
+ setup.allow_photos()
241
+ elif path == "/api/setup/refresh" and not body:
242
+ setup.refresh()
243
+ elif path == "/api/setup/tools" and not body:
244
+ setup.install_tools()
245
+ elif path == "/api/setup/xcode" and not body:
246
+ setup.open_xcode()
247
+ elif path == "/api/setup/signing/card" and not body:
248
+ setup.signing_card()
249
+ elif path == "/api/setup/signing/approve" and set(body) == {"digest"}:
250
+ setup.signing_approve(body["digest"])
251
+ elif path == "/api/setup/signing/resolve" and set(body) == {"digest", "made"}:
252
+ setup.signing_resolve(body["digest"], body["made"])
253
+ elif path == "/api/setup/signing/devmode" and not body:
254
+ setup.signing_reveal_developer_mode()
255
+ else:
256
+ return self.send_json({"error": "Not found"}, 404)
257
+ except LookupError as error:
258
+ return self.send_json({"error": str(error)}, 409)
259
+ except ValueError as error:
260
+ return self.send_json({"error": str(error)}, 400)
261
+ except DevError as error:
262
+ return self.send_json({"error": error.line(), "code": "signing_refused"}, 409)
263
+ return self.send_json(setup.state())
264
+
265
+ def send_run_frame(self, run_id, frame_id):
266
+ frames = getattr(self.runtime, "frames", None)
267
+ data = frames.get(run_id, frame_id) if frames is not None and run_id in self.runtime.runs else None
268
+ if data is None and frames is not None and run_id in self.runtime.runs:
269
+ data = TaskStartMixin.frame(self.runtime, run_id, frame_id)
270
+ if data is None:
271
+ return self.send_json({"error": "Not found", "code": "frame_not_found"}, 404)
272
+ self.send_bytes(data, "image/jpeg")
273
+
274
+ def send_icon(self):
275
+ query = parse_qs(urlsplit(self.path).query)
276
+ bundle = (query.get("bundle") or [""])[0]
277
+ try:
278
+ icon = self.runtime.icons.get(bundle)
279
+ except ValueError:
280
+ return self.send_json({"error": "Invalid app bundle identifier", "code": "invalid_bundle"}, 400)
281
+ except IconUnavailable:
282
+ return self.send_json({"error": "The app icon is not available right now", "code": "icon_unavailable"}, 503)
283
+ except OSError:
284
+ return self.send_json({"error": "The app icon could not be saved", "code": "icon_unavailable"}, 503)
285
+ if icon is None:
286
+ return self.send_json({"error": "This app has no icon", "code": "no_icon"}, 404)
287
+ self.send_bytes(*icon)
288
+
289
+ def send_stream(self, video=PRIMARY_VIDEO, phone=None):
290
+ # ?framing=raw: WebKit parses multipart/x-mixed-replace itself and hands fetch() bodies without part headers.
291
+ query = parse_qs(urlsplit(self.path).query)
292
+ raw = query.get("framing") == ["raw"]
293
+ asked = (query.get("fps") or [None])[0]
294
+ if asked is not None and not (asked.isdigit() and 1 <= int(asked) <= 60):
295
+ return self.send_json({"error": "fps is a whole number from 1 to 60", "code": "invalid_fps"}, 400)
296
+ rate = (lambda: int(asked)) if asked is not None else (lambda: self.runtime.stream_fps(phone))
297
+ video = self.runtime.video if video is PRIMARY_VIDEO else video
298
+ if not frame_source.streams(video):
299
+ return self.send_json({"error": "Live stream needs a USB iPhone", "code": "video_unavailable"}, 503)
300
+ try:
301
+ viewer = video.subscribe()
302
+ except WdaVideoUnavailable as exc:
303
+ return self.send_json({"error": str(exc), "code": "video_unavailable"}, 503)
304
+ self.close_connection = True
305
+ try:
306
+ self.connection.settimeout(10) # a stuck viewer releases its slot
307
+ self.send_response(200)
308
+ self.send_header("Content-Type", "application/octet-stream" if raw else
309
+ f"multipart/x-mixed-replace; boundary={WDA_BOUNDARY}")
310
+ self.send_header("Cache-Control", "no-store, no-transform")
311
+ self.send_header("X-Content-Type-Options", "nosniff")
312
+ self.send_header("Connection", "close")
313
+ self.end_headers()
314
+ for frame in video.paced_frames(viewer, rate):
315
+ content_type, data = frame[1], frame[2]
316
+ # A slow link brought the stream down to this rate (WdaVideo's LinkGovernor): the pane says so.
317
+ reduced = video.reduced_fps()
318
+ note = f"X-Mobster-Reduced: {reduced}\r\n" if reduced else ""
319
+ self.wfile.write(f"--{WDA_BOUNDARY}\r\nContent-Type: {content_type}\r\n{note}"
320
+ f"Content-Length: {len(data)}\r\n\r\n".encode() + data + b"\r\n")
321
+ self.wfile.flush()
322
+ except (OSError, TimeoutError):
323
+ pass
324
+ finally:
325
+ video.unsubscribe(viewer)
326
+
327
+ def send_frame(self, video=PRIMARY_VIDEO):
328
+ video = self.runtime.video if video is PRIMARY_VIDEO else video
329
+ # A peek starts a stopped relay, so a phone nobody watches still gets a current picture.
330
+ frame = video.peek() if frame_source.streams(video) else None
331
+ if frame is None:
332
+ return self.send_json({"error": "No frame yet", "code": "video_unavailable"}, 503)
333
+ self.send_bytes(frame[2], frame[1])
@@ -0,0 +1,17 @@
1
+ """HTTP-facing domain errors shared by runs and saved workflows."""
2
+
3
+ from .errors import MobsterError
4
+
5
+
6
+ class APIError(MobsterError):
7
+ def __init__(self, message, status=409, code="conflict", **details):
8
+ super().__init__(message)
9
+ self.status, self.code, self.details = status, code, details
10
+
11
+
12
+ def nothing_started(error):
13
+ """``error`` as a new APIError that also says ``"started": false``: Mobster recorded and ran nothing for the
14
+ request. Only a layer that has just found no task under the request's Idempotency-Key may say it, since a task is
15
+ recorded under that key before anything is sent to a phone. Without the field, a client must treat a 5xx as an
16
+ outcome it can't know."""
17
+ return APIError(str(error), error.status, error.code, **{**error.details, "started": False})
@@ -0,0 +1,249 @@
1
+ """GET routes of the local API."""
2
+
3
+ import json
4
+ import re
5
+ import time
6
+ from urllib.parse import parse_qs, urlsplit
7
+
8
+ from .api_errors import APIError
9
+ from .api_http import DEVICE_PATH, WORKFLOW_TOOL, api_path
10
+ from .journal import JournalError
11
+ from .workflows.records import WORKFLOW_ID
12
+
13
+
14
+ class GetRoutesMixin:
15
+ def do_GET(self):
16
+ if self.mcp_run():
17
+ return
18
+ if self.refused():
19
+ return
20
+ path = api_path(urlsplit(self.path).path)
21
+ if self.extension("GET", path):
22
+ return
23
+ if path == "/api/events":
24
+ return self.send_events()
25
+ if path == "/api/status":
26
+ return self.send_json({**self.runtime.status(), "app_session": bool(self.app_session)})
27
+ if path == "/api/engine":
28
+ return self.send_json(self.runtime.engine())
29
+ if path == "/api/ai-plans":
30
+ # Settings › Models: the plans and providers, and the coding agents, never a token.
31
+ fresh = (parse_qs(urlsplit(self.path).query).get("refresh") or ["0"])[0] == "1"
32
+ from .ai_plans import routes as plan_routes
33
+ return self.send_json({"providers": plan_routes.public(),
34
+ "inUse": (self.runtime.keys.state().get("smart") or {}).get("preference"),
35
+ "agents": self.runtime.agents_status(max_age=0 if fresh else 60, wait=True)})
36
+ if path == "/api/agents":
37
+ # Settings and Setup's "Check again": read now (the status's copy may be a minute old).
38
+ fresh = (parse_qs(urlsplit(self.path).query).get("refresh") or ["0"])[0] == "1"
39
+ return self.send_json({"agents": self.runtime.agents_status(max_age=0 if fresh else 60, wait=True)})
40
+ if path == "/api/apps":
41
+ device = (parse_qs(urlsplit(self.path).query).get("device") or [None])[0]
42
+ if device is None:
43
+ return self.send_json(self.runtime.apps_payload())
44
+ phone = self.device_phone(device)
45
+ return phone and self.send_json(self.runtime.apps_payload(phone))
46
+ if path == "/api/devices":
47
+ return self.send_devices()
48
+ if path == "/api/fleet/health":
49
+ fleet = getattr(self.runtime, "fleet", None)
50
+ if fleet is None or self.runtime.pool is not None:
51
+ return self.send_json({"devices": [], "checkedAt": round(time.time() * 1000)})
52
+ return self.send_json(fleet.health.report())
53
+ device_match = DEVICE_PATH.fullmatch(path)
54
+ if device_match and device_match[2] == "teach" and not device_match[3]:
55
+ phone = self.device_phone(device_match[1])
56
+ return phone and self.send_json(self.runtime.teach_state(phone))
57
+ if path == "/api/device/teach":
58
+ return self.send_json(self.runtime.teach_state(None))
59
+ if device_match and device_match[2] in ("take-back", "resume-queue"):
60
+ return self.send_json({"error": "Not found"}, 404)
61
+ if device_match and not device_match[3] and device_match[2] not in ("control", "disconnect", "connect"):
62
+ return self.send_device(device_match[1], device_match[2])
63
+ if path == "/api/apps/icon":
64
+ return self.send_icon()
65
+ if path == "/api/workflows":
66
+ try:
67
+ from .graph import schema as graph_schema
68
+ from .workflows import records as workflow_batch
69
+ return self.send_json({"workflows": self.runtime.workflows.list_with_health(),
70
+ "schedulerError": self.runtime.workflows.scheduler_error,
71
+ "running": self.runtime.workflows.running(),
72
+ "stepKinds": workflow_batch.step_kinds(),
73
+ "deliveryKinds": workflow_batch.delivery_kinds(),
74
+ "nodeTypes": list(graph_schema.TYPES)})
75
+ except JournalError:
76
+ return self.send_json({"error": "Mobster couldn't read your workflows from its history on this Mac. "
77
+ "Quit and reopen Mobster, then try again.",
78
+ "code": "persistence_unavailable"}, 503)
79
+ if path == "/api/workflows/approvals":
80
+ return self.send_json({"approvals": self.runtime.workflows.pending_approvals()})
81
+ if path == "/api/workflows/node-types":
82
+ from .graph import schema as graph_schema
83
+ return self.send_json({"nodeTypes": graph_schema.catalog(), "limits": graph_schema.public_limits()})
84
+ versions_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/versions(?:/(\d{1,9})(?:/diff/(\d{1,9}))?)?",
85
+ path)
86
+ if versions_match:
87
+ try:
88
+ identifier, revision, other = versions_match[1], versions_match[2], versions_match[3]
89
+ if other:
90
+ return self.send_json(
91
+ {"diff": self.runtime.workflows.history.diff(identifier, int(revision), int(other))})
92
+ if revision:
93
+ return self.send_json({"workflow": self.runtime.workflows.history.version(identifier,
94
+ int(revision))})
95
+ return self.send_json({"versions": self.runtime.workflows.history.versions(identifier)})
96
+ except APIError as exc:
97
+ return self.send_api_error(exc)
98
+ estimate_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/estimate", path)
99
+ if estimate_match:
100
+ draft = (parse_qs(urlsplit(self.path).query).get("draft") or ["0"])[0] == "1"
101
+ try:
102
+ return self.send_json({"estimate": self.runtime.workflows.estimate(estimate_match[1], draft=draft)})
103
+ except APIError as exc:
104
+ return self.send_api_error(exc)
105
+ timeline_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/timeline", path)
106
+ if timeline_match:
107
+ try:
108
+ return self.send_json({"timeline": self.runtime.workflows.timeline(*timeline_match.groups())})
109
+ except APIError as exc:
110
+ return self.send_api_error(exc)
111
+ resume_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/resume", path)
112
+ if resume_match:
113
+ try:
114
+ return self.send_json({"resume": self.runtime.workflows.resume_preview(*resume_match.groups())})
115
+ except APIError as exc:
116
+ return self.send_api_error(exc)
117
+ node_match = re.fullmatch(
118
+ r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/nodes/([A-Za-z0-9_@.-]{1,64})(/frames)?", path)
119
+ if node_match:
120
+ try:
121
+ if node_match[4]:
122
+ return self.send_json(self.runtime.workflows.node_frames(*node_match.groups()[:3]))
123
+ return self.send_json({"node": self.runtime.workflows.node_history(*node_match.groups()[:3])})
124
+ except APIError as exc:
125
+ return self.send_api_error(exc)
126
+ batches_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/runs", path)
127
+ if batches_match:
128
+ try:
129
+ return self.send_json({"batches": self.runtime.workflows.batch_runs(batches_match[1])})
130
+ except APIError as exc:
131
+ return self.send_api_error(exc)
132
+ if path == "/api/workflows/tools":
133
+ return self.send_json({"tools": self.runtime.workflows.tools()})
134
+ tool_match = WORKFLOW_TOOL.fullmatch(path)
135
+ if tool_match and not WORKFLOW_ID.fullmatch(tool_match[1]):
136
+ try:
137
+ return self.send_json({"tool": self.runtime.workflows.tool(tool_match[1])})
138
+ except APIError as exc:
139
+ return self.send_api_error(exc)
140
+ if path == "/api/inbox":
141
+ return self.send_json(self.runtime.inbox())
142
+ if path == "/api/home":
143
+ since = (parse_qs(urlsplit(self.path).query).get("since") or [None])[0]
144
+ try:
145
+ since = int(since) if since is not None else None
146
+ except ValueError:
147
+ return self.send_json({"error": "since is a time in milliseconds", "code": "invalid_since"}, 400)
148
+ from . import home
149
+ return self.send_json(home.summary(self.runtime, since))
150
+ if path == "/api/usage":
151
+ return self.send_json({**self.runtime.journal.usage(), "monthUsd": self.runtime.month_usd(),
152
+ "monthlyLimitUsd": self.runtime.monthly_limit()})
153
+ if path == "/api/usage/estimate":
154
+ return self.send_json({"engines": self.runtime.engine_estimates()})
155
+ if path == "/api/settings":
156
+ return self.send_json(self.runtime.settings())
157
+ if path == "/api/keys":
158
+ return self.send_json(self.runtime.keys.state())
159
+ if path == "/api/setup":
160
+ if self.runtime.setup is None:
161
+ return self.send_json({"error": "Setup is managed outside this app", "code": "setup_unmanaged"}, 404)
162
+ return self.send_json(self.runtime.setup.state())
163
+ if path == "/api/device/video/status":
164
+ return self.send_json({**self.runtime.video.status(), "awakeLease": self.runtime.awake.status()})
165
+ if path == "/api/device/info":
166
+ return self.send_json(self.runtime.device_info.snapshot())
167
+ if path == "/api/device/status":
168
+ return self.send_json(self.runtime.device_status())
169
+ if path == "/api/device/stream":
170
+ return self.send_stream()
171
+ if path == "/api/device/frame":
172
+ return self.send_frame()
173
+ if path == "/api/groups" or path.startswith("/api/groups/"):
174
+ try:
175
+ if self.group_request("GET", path, None) is False:
176
+ return self.send_json({"error": "Not found"}, 404)
177
+ except APIError as exc:
178
+ return self.send_api_error(exc)
179
+ return
180
+ if path == "/api/runs":
181
+ with self.runtime.lock:
182
+ runs = [r for r in self.runtime.runs.values() if self.may_read(r)]
183
+ device = (parse_qs(urlsplit(self.path).query).get("device") or [None])[0]
184
+ if device is not None:
185
+ phone = self.device_phone(device)
186
+ if phone is None:
187
+ return
188
+ # Runs saved before devices had one ran on the primary phone.
189
+ runs = [r for r in runs if r.device_id == phone.id or (phone.primary and r.device_id is None)]
190
+ return self.send_json({"runs": [r.row() for r in reversed(runs)]})
191
+ frame = re.fullmatch(r"/api/runs/([a-f0-9]{12})/frames/(f[0-9a-f]{10})", path)
192
+ if frame:
193
+ return self.send_run_frame(frame[1], frame[2])
194
+ drawn = re.fullmatch(r"/api/runs/([a-f0-9]{12})/blocks/([A-Za-z0-9_.:-]{1,64})/map", path)
195
+ if drawn:
196
+ query = parse_qs(urlsplit(self.path).query)
197
+ try:
198
+ size = [int((query.get(name) or [default])[0]) for name, default in (("w", "720"), ("h", "360"))]
199
+ return self.send_json(self.runtime.block_map(drawn[1], drawn[2], dark=(query.get("theme") or [""])[0]
200
+ == "dark", width=size[0], height=size[1]))
201
+ except ValueError:
202
+ return self.send_json({"error": "w and h are whole numbers"}, 400)
203
+ except APIError as exc:
204
+ return self.send_api_error(exc)
205
+ match = re.fullmatch(r"/api/runs/([a-f0-9]{12})(?:/(events|screenshot|usage))?", path)
206
+ run = self.runtime.runs.get(match[1]) if match else None
207
+ if not run or not self.may_read(run):
208
+ return self.send_json({"error": "Not found"}, 404)
209
+ if match[2] == "usage":
210
+ return self.send_json(self.runtime.journal.usage(run.id))
211
+ if match[2] == "screenshot":
212
+ return self.send_json(self.runtime.preview(run))
213
+ if match[2] != "events":
214
+ return self.send_json({"run": run.public()})
215
+ if not self.runtime.streams.acquire(blocking=False):
216
+ return self.send_json({"error": "Too many event streams", "code": "stream_limit"}, 429)
217
+ self.close_connection = True
218
+ try:
219
+ self.send_response(200)
220
+ self.send_header("Content-Type", "text/event-stream")
221
+ self.send_header("Cache-Control", "no-cache")
222
+ self.send_header("Connection", "close")
223
+ self.end_headers()
224
+ try:
225
+ index = max(0, int(self.headers.get("Last-Event-ID", "-1")) + 1)
226
+ except ValueError:
227
+ index = 0
228
+ with run.condition:
229
+ if index > len(run.events):
230
+ index = 0
231
+ while True:
232
+ with run.condition:
233
+ events = list(run.events[index:])
234
+ finished = run.finished_at is not None
235
+ if not events and not finished:
236
+ run.condition.wait(timeout=5)
237
+ if events:
238
+ for event in events:
239
+ self.wfile.write(f"id: {event['seq']}\ndata: {json.dumps(event)}\n\n".encode())
240
+ index += len(events)
241
+ else:
242
+ self.wfile.write(b": keepalive\n\n")
243
+ self.wfile.flush()
244
+ if finished:
245
+ break
246
+ except (OSError, TimeoutError):
247
+ pass
248
+ finally:
249
+ self.runtime.streams.release()