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,89 @@
1
+ """The terminal UI (`mobster`, `mobster tui`). Textual loads only when it opens, so `serve` never imports it."""
2
+
3
+ import signal
4
+ import sys
5
+
6
+
7
+ def main(args):
8
+ """Open the terminal UI for parsed command-line ``args``. Returns an exit code."""
9
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
10
+ print("mobster: the terminal UI needs an interactive terminal. In a script, run the task with "
11
+ "`mobster run \"TASK\" --execute` (JSON lines when piped).", file=sys.stderr)
12
+ return 2
13
+ try:
14
+ from .app import MobsterApp
15
+ except ImportError as error:
16
+ print(f"mobster: the terminal UI is not available in this build ({error.name} is missing). "
17
+ "Give the task in Mobster for Mac (https://mobster.dev/download), or use `mobster run`.",
18
+ file=sys.stderr)
19
+ return 1
20
+ from .images import image_widget
21
+ image_class = image_widget() # before the UI starts: it may ask the terminal what it can draw
22
+ demo = bool(getattr(args, "demo", False))
23
+ wda_url = getattr(args, "wda_url", None)
24
+ env_file = getattr(args, "env_file", None)
25
+
26
+ def open_session():
27
+ # Runs on a worker thread after the first frame: the agent's modules load behind the UI.
28
+ from .session import Session
29
+ return Session(wda_url=wda_url, env_file=env_file, demo=demo)
30
+
31
+ resume = getattr(args, "resume", None)
32
+ app = MobsterApp(open_session, demo=demo, resume=resume,
33
+ app_id=getattr(args, "app", None),
34
+ bell=not getattr(args, "no_bell", False), image_class=image_class,
35
+ task=getattr(args, "task", None), continue_=bool(getattr(args, "continue_", False)),
36
+ thread_id=getattr(args, "thread_id", None))
37
+ with _exit_on_signals(app) as caught:
38
+ app.run()
39
+ from ..style import color_enabled
40
+ summary = app.transcript_summary(color=color_enabled(sys.stdout))
41
+ session = app.session
42
+ if session is not None:
43
+ if session.run is not None and session.run.finished_at is None:
44
+ _say("Stopping the running task at its next safe point…", sys.stderr)
45
+ session.close()
46
+ if summary:
47
+ _say(summary, sys.stdout)
48
+ return 128 + caught[0] if caught else 0
49
+
50
+
51
+ class _exit_on_signals:
52
+ """SIGTERM and SIGHUP (a closed terminal window, `kill`) end the UI the way ctrl+d does,
53
+ so Textual restores the terminal."""
54
+
55
+ NAMES = ("SIGTERM", "SIGHUP")
56
+
57
+ def __init__(self, app):
58
+ self.app, self.caught, self.previous = app, [], {}
59
+
60
+ def handler(self, signum, frame):
61
+ self.caught.append(signum)
62
+ loop = getattr(self.app, "_loop", None)
63
+ if len(self.caught) == 1 and loop is not None and not loop.is_closed():
64
+ try:
65
+ loop.call_soon_threadsafe(self.app.exit) # safe from a signal handler: it wakes the loop
66
+ return
67
+ except RuntimeError:
68
+ pass
69
+ raise KeyboardInterrupt
70
+
71
+ def __enter__(self):
72
+ for name in self.NAMES:
73
+ number = getattr(signal, name, None)
74
+ if number is not None:
75
+ self.previous[number] = signal.signal(number, self.handler)
76
+ return self.caught
77
+
78
+ def __exit__(self, *exc):
79
+ for number, old in self.previous.items():
80
+ signal.signal(number, old)
81
+ return False
82
+
83
+
84
+ def _say(text, stream):
85
+ """Print after the UI closed; the terminal may be gone (SIGHUP), and that is no error."""
86
+ try:
87
+ print(text, file=stream, flush=True)
88
+ except (OSError, ValueError):
89
+ pass
@@ -0,0 +1,582 @@
1
+ """The Mobster terminal UI: a Textual app over ``session.Session``."""
2
+
3
+ import asyncio
4
+ import threading
5
+ import time
6
+
7
+ from textual import on, work
8
+ from textual.app import App, ComposeResult
9
+ from textual.binding import Binding
10
+ from textual.containers import Horizontal, Vertical, VerticalScroll
11
+ from textual.css.query import NoMatches
12
+ from textual.widgets import Input, OptionList, Static
13
+
14
+ from ..app_choice import find_app
15
+ from ..catalog import APPS
16
+ from ..narrate import Approval, Launch, Outcome, Step, facts, status_glyph
17
+ from .chrome import PLUG_IN, ChromeMixin
18
+ from .commands import CommandMixin
19
+ from .prompt import PromptMixin
20
+ from .render import APPROVALS
21
+ from .tasks import TaskMixin
22
+ from .theme import APP_CSS, THEME
23
+ from .widgets import ApprovalCard, Composer, PhonePanel, RunView
24
+
25
+ PHONE_MIN_WIDTH = 110 # the phone panel shows by default from this terminal width
26
+ TALL = 30 # from this many rows the header sits one row under the top edge
27
+
28
+
29
+ class MobsterApp(ChromeMixin, PromptMixin, TaskMixin, CommandMixin, App):
30
+ TITLE = "Mobster"
31
+ ENABLE_COMMAND_PALETTE = False
32
+ CSS = APP_CSS
33
+
34
+ BINDINGS = [
35
+ Binding("ctrl+c", "interrupt", show=False, priority=True),
36
+ Binding("ctrl+d", "quit_now", show=False, priority=True),
37
+ Binding("escape", "escape", show=False),
38
+ Binding("shift+tab", "toggle_ask", show=False, priority=True),
39
+ Binding("ctrl+o", "toggle_details", show=False, priority=True),
40
+ Binding("ctrl+s", "toggle_screen", show=False, priority=True),
41
+ Binding("ctrl+r", "history", show=False, priority=True),
42
+ Binding("ctrl+l", "clear", show=False, priority=True),
43
+ Binding("ctrl+n", "new_thread", show=False, priority=True),
44
+ Binding("ctrl+t", "threads", show=False, priority=True),
45
+ ]
46
+
47
+ def __init__(self, session_factory, *, demo=False, resume=None, app_id=None, bell=True,
48
+ image_class=None, task=None, continue_=False, thread_id=None):
49
+ super().__init__()
50
+ self.session_factory = session_factory
51
+ # A textual-image widget class when this terminal shows images (tui.images), else None: text.
52
+ self.image_class = image_class
53
+ self.session = None
54
+ self.demo = demo
55
+ self.resume_id = resume
56
+ self.bell_enabled = bell
57
+ self.apps = [dict(app) for app in APPS]
58
+ self.app_choice = find_app(app_id, self.apps) if app_id else None
59
+ # An --app the catalog does not know (an app installed on the phone, or a typo) waits for the phone's
60
+ # own list (apps_ready), and is reported if nothing there matches: never dropped without a word. A list
61
+ # without the phone's apps (WDA still starting) rules nothing out: it is read again once the phone is ready.
62
+ self.requested_app = app_id if app_id and self.app_choice is None else None
63
+ self.requested_app_waiting = self.requested_app_reread = False
64
+ self.last_app = self.app_choice
65
+ self.status = {}
66
+ from ..permissions import from_env as permission_settings
67
+ mode = permission_settings().mode
68
+ self.settings = {"askBeforeActing": mode != "dont_ask", "permissionMode": mode}
69
+ self.settings_writer = asyncio.Lock()
70
+ self.setting_requests = {}
71
+ self.output_format = "auto"
72
+ self.expanded = False
73
+ self.frame = 0
74
+ self.run_view = None
75
+ self.finished_views = [] # this session's tasks, for the summary printed after the UI closes
76
+ self.running = None
77
+ self.follow_stop = threading.Event()
78
+ self.recalls = []
79
+ self.recall_index = None
80
+ self.quit_armed = 0.0
81
+ self.phone_visible = None
82
+ self.phone_busy = False
83
+ self.connect_error = None
84
+ # Conversations: the one tasks go to (None: a new one starts with the next task), -c's request, and the
85
+ # files attached for the next task.
86
+ self.thread_id = thread_id
87
+ self.continue_requested = bool(continue_)
88
+ self.attachments = []
89
+ self.initial_task = task
90
+ self.phone = None # (state, words, name) from the device list: the checklist's iPhone row
91
+ self.tasks_done = 0
92
+ self.update_line = ""
93
+
94
+ def call_from_thread(self, callback, *args, **kwargs):
95
+ """Discard worker UI updates after shutdown, including callbacks queued before widgets were removed."""
96
+ if not self.is_running:
97
+ return None
98
+
99
+ def deliver():
100
+ if self.is_running:
101
+ return callback(*args, **kwargs)
102
+ return None
103
+
104
+ try:
105
+ return super().call_from_thread(deliver)
106
+ except RuntimeError:
107
+ if self.is_running:
108
+ raise
109
+ return None # the event loop stopped between the check and dispatch
110
+
111
+ def compose(self) -> ComposeResult:
112
+ yield Static(id="header")
113
+ with Horizontal(id="body"):
114
+ with VerticalScroll(id="transcript", classes="idle"):
115
+ yield Static(self.welcome(), id="welcome")
116
+ yield PhonePanel(self.image_class, id="phone")
117
+ with Vertical(id="bottom"):
118
+ yield OptionList(id="suggest")
119
+ yield Static(id="suggest-hint")
120
+ yield ApprovalCard()
121
+ yield Static(id="notice")
122
+ yield Composer(id="composer")
123
+ yield Static(id="status")
124
+
125
+ def on_mount(self):
126
+ self.register_theme(THEME)
127
+ self.theme = "mobster"
128
+ prompt = self.query_one("#prompt", Input)
129
+ prompt.focus()
130
+ if self.initial_task:
131
+ prompt.value = self.initial_task
132
+ prompt.cursor_position = len(prompt.value)
133
+ self.refresh_chrome()
134
+ self.set_interval(.1, self.tick)
135
+ self.set_interval(2, self.poll_status)
136
+ self.set_interval(1.5, self.poll_phone)
137
+ self.open_session()
138
+ if not self.demo:
139
+ self.check_for_update()
140
+
141
+ def on_resize(self, event):
142
+ self.screen.set_class(event.size.height >= TALL, "tall")
143
+ if self.phone_visible is None:
144
+ self.query_one("#phone").set_class(event.size.width < PHONE_MIN_WIDTH, "hidden")
145
+ self.call_after_refresh(self.poll_phone)
146
+ self.call_after_refresh(self.refresh_layout) # after the resize lands, so self.size is the new one
147
+
148
+ def refresh_layout(self):
149
+ """The parts that fit themselves to the terminal: the header and the welcome screen."""
150
+ try:
151
+ self.refresh_header()
152
+ self.refresh_welcome()
153
+ except NoMatches:
154
+ pass
155
+
156
+ @work(thread=True, exclusive=True, group="session")
157
+ def open_session(self):
158
+ try:
159
+ session = self.session_factory()
160
+ except Exception as error:
161
+ from .session import error_text
162
+ self.call_from_thread(self.session_failed, error_text(error))
163
+ return
164
+ self.session = session
165
+ status = settings = None
166
+ try:
167
+ settings = session.settings()
168
+ status = session.status()
169
+ except Exception:
170
+ pass
171
+ phone = self.read_phone()
172
+ self.call_from_thread(self.session_ready, status, settings, phone)
173
+ try:
174
+ apps = session.apps()
175
+ except Exception:
176
+ apps = None
177
+ self.call_from_thread(self.apps_ready, apps or self.apps)
178
+
179
+ def session_failed(self, message):
180
+ self.connect_error = message
181
+ self.refresh_chrome()
182
+ self.refresh_welcome()
183
+
184
+ def session_ready(self, status, settings, phone=None):
185
+ if status:
186
+ self.status = status
187
+ if settings:
188
+ self.settings.update(settings)
189
+ self.phone = phone
190
+ history = self.session.history()
191
+ self.recalls = [run.goal for run in reversed(history)][-200:]
192
+ self.tasks_done = sum(1 for run in history if run.finished_at is not None)
193
+ self.refresh_chrome()
194
+ self.refresh_welcome()
195
+ self.poll_phone()
196
+ if self.continue_requested or self.thread_id:
197
+ self.continue_thread(self.thread_id)
198
+ if self.resume_id:
199
+ self.resume(self.resume_id)
200
+ if self.initial_task:
201
+ self.start_initial_task()
202
+
203
+ def start_initial_task(self):
204
+ """`mobster "TASK"`: run it now when Mobster can."""
205
+ prompt = self.query_one("#prompt", Input)
206
+ if prompt.value.strip() != (self.initial_task or "").strip():
207
+ return # changed meanwhile: it's the person's now
208
+ if self.demo or not self.needs_setup():
209
+ self.initial_task = None
210
+ self.submit_value(prompt.value)
211
+ else:
212
+ self.note("Your task is in the box. It runs when the checklist above is done: press enter then.")
213
+ self.initial_task = None
214
+
215
+ @work(thread=True, exclusive=True, group="apps")
216
+ def fetch_apps(self):
217
+ """The phone's apps, read now rather than from the cache: the phone has just become ready."""
218
+ try:
219
+ apps = self.session.apps(fresh=True)
220
+ except Exception:
221
+ apps = None
222
+ self.call_from_thread(self.apps_ready, apps or self.apps)
223
+
224
+ def apps_ready(self, apps):
225
+ self.apps = apps
226
+ if self.app_choice:
227
+ self.app_choice = next((a for a in apps if a["id"] == self.app_choice["id"]), self.app_choice)
228
+ if self.requested_app:
229
+ self.resolve_requested_app()
230
+
231
+ def phone_listed_apps(self):
232
+ """Whether ``self.apps`` holds the phone's own list (its installed apps are marked), not the catalog alone."""
233
+ return self.demo or any(app.get("foundOnPhone") or app.get("installed") is not None for app in self.apps)
234
+
235
+ def resolve_requested_app(self):
236
+ """Choose the app --app named, or say why not, once the phone's own list can tell."""
237
+ wanted = self.requested_app
238
+ if self.app_choice is not None:
239
+ self.requested_app = None # chosen since, with /app or @
240
+ return
241
+ found = find_app(wanted, self.apps)
242
+ if found is None and not self.phone_listed_apps():
243
+ if not self.status.get("device_ready"):
244
+ if not self.requested_app_waiting:
245
+ self.requested_app_waiting = True
246
+ self.note(f"Couldn't read the phone's apps yet. Mobster looks for {wanted} when the phone "
247
+ "answers.")
248
+ return # status_ready reads them again when the phone is ready
249
+ if not self.requested_app_reread:
250
+ self.requested_app_reread = True
251
+ self.fetch_apps()
252
+ return
253
+ self.requested_app = None
254
+ self.note(f"Couldn't read the phone's apps to find {wanted}. Mobster picks the app from each task; "
255
+ "/app chooses one.", "warning")
256
+ self.refresh_chip()
257
+ return
258
+ self.requested_app = None
259
+ self.app_choice = found
260
+ if found is None:
261
+ self.note(f"No app called {wanted} on this phone. Mobster picks the app from each task; "
262
+ "/app chooses one.", "warning")
263
+ self.refresh_chip()
264
+
265
+ def poll_status(self):
266
+ if self.session is not None and not self.status_polling:
267
+ self.fetch_status()
268
+
269
+ status_polling = False
270
+
271
+ def read_phone(self):
272
+ """(state, words, name) for the checklist's iPhone row, from the device list; None in the demo."""
273
+ if self.demo or self.session is None:
274
+ return None
275
+ from ..native_helpers import devices_allowed
276
+ if not devices_allowed(): # MOBSTER_NO_DEVICES=1, as in local CI: never list a phone that's plugged in
277
+ return ("unplugged", PLUG_IN, None)
278
+ try:
279
+ from .. import devices
280
+ found = devices.discover(probe=False)
281
+ except Exception:
282
+ return None
283
+ url = (self.session.wda_url or "").rstrip("/")
284
+ mine = next((item for item in found if (item.get("wdaUrl") or "").rstrip("/") == url), None)
285
+ usb = [item for item in found if item.get("kind") == "usb"]
286
+ item = mine or (usb[0] if usb else None)
287
+ if item is None:
288
+ return ("unplugged", PLUG_IN, None)
289
+ name = item.get("name")
290
+ state = item.get("state")
291
+ reason = (item.get("reason") or "").lower()
292
+ if state == "needs_setup" and "trust" in reason:
293
+ return ("trust", "tap Trust on it", name)
294
+ if state == "needs_setup":
295
+ return ("setup", "run mobster setup, or set it up in Mobster for Mac", name)
296
+ if state == "disconnected":
297
+ return ("unplugged", PLUG_IN, name)
298
+ if "lock" in reason:
299
+ return ("locked", "unlock it", name)
300
+ return ("starting", "starting Mobster's helper on it", name)
301
+
302
+ @work(thread=True, group="status")
303
+ def fetch_status(self):
304
+ self.status_polling = True
305
+ try:
306
+ status = self.session.status()
307
+ phone = None if status.get("device_ready") else self.read_phone()
308
+ except Exception:
309
+ status, phone = None, None
310
+ finally:
311
+ self.status_polling = False
312
+ if status:
313
+ self.call_from_thread(self.status_ready, status, phone)
314
+
315
+ def status_ready(self, status, phone=None):
316
+ was_ready = self.status.get("device_ready")
317
+ was_setup = self.needs_setup()
318
+ self.status = status
319
+ self.phone = phone
320
+ if not self.query("#header"):
321
+ return # the UI is closing: a status fetch still in flight lands after its widgets are gone
322
+ self.refresh_chrome()
323
+ if self.requested_app and status.get("device_ready") and not was_ready:
324
+ # The phone has just answered: its apps can be read now, for the app --app named.
325
+ self.requested_app_reread = True
326
+ self.fetch_apps()
327
+ if self.run_view is None:
328
+ self.refresh_welcome()
329
+ if was_setup and not self.needs_setup():
330
+ prompt = self.query_one("#prompt", Input)
331
+ if prompt.value.strip() and self.running is None:
332
+ self.note("Everything's ready. Press enter to run your task.")
333
+
334
+ def tick(self):
335
+ if not self.is_running or self.running is None:
336
+ return
337
+ self.frame += 1
338
+ if self.run_view is not None:
339
+ for view in self.run_view.views.values():
340
+ if view.live:
341
+ view.redraw(self.frame)
342
+ if self.frame % 5 == 0:
343
+ self.refresh_status()
344
+ try:
345
+ card = self.query_one(ApprovalCard)
346
+ except NoMatches: # the timer fires once more while the app closes mid-task, its widgets gone
347
+ return
348
+ if card.has_class("open") and self.frame % 10 == 0:
349
+ card.redraw()
350
+
351
+ @on(Input.Changed, "#prompt")
352
+ def prompt_changed(self, event):
353
+ self.refresh_chip()
354
+ self.suggest(event.value)
355
+
356
+ @on(OptionList.OptionSelected, "#suggest")
357
+ def suggestion_clicked(self):
358
+ self.accept_suggestion()
359
+ self.query_one("#prompt", Input).focus()
360
+
361
+ @on(Input.Submitted, "#prompt")
362
+ def prompt_submitted(self, event):
363
+ value = event.value.strip()
364
+ if not value:
365
+ return
366
+ if value.startswith("/"):
367
+ name, _, argument = value[1:].partition(" ")
368
+ event.input.value = ""
369
+ self.run_command(name.lower(), argument.strip())
370
+ return
371
+ self.submit_value(value)
372
+
373
+ def submit_value(self, value):
374
+ prompt = self.query_one("#prompt", Input)
375
+ if self.running is not None:
376
+ prompt.value = ""
377
+ self.steer(value)
378
+ return
379
+ if self.session is None:
380
+ self.note(self.connect_error or "Still starting. Try again in a moment.", "warning")
381
+ return
382
+ if self.awaiting_requested_app(value):
383
+ # Never run it in an inferred app while the app --app named may still be on the phone.
384
+ self.note(f"Still looking for {self.requested_app} on the phone. Try again when it is ready, or "
385
+ "choose an app with /app.", "warning")
386
+ return
387
+ app = self.target_app(value)
388
+ goal = self.split_mentions(value)[0] or value
389
+ prompt.value = ""
390
+ self.recalls.append(value)
391
+ self.recall_index = None
392
+ self.start_task(app, goal)
393
+
394
+ def action_interrupt(self):
395
+ prompt = self.query_one("#prompt", Input)
396
+ if self.running is not None:
397
+ self.stop_task()
398
+ return
399
+ if prompt.value:
400
+ prompt.value = ""
401
+ return
402
+ now = time.monotonic()
403
+ if now - self.quit_armed < 2:
404
+ self.exit()
405
+ return
406
+ self.quit_armed = now
407
+ self.notify("Press ctrl+c again to quit", timeout=2)
408
+
409
+ def action_quit_now(self):
410
+ self.exit()
411
+
412
+ def action_escape(self):
413
+ if self.suggestions_open():
414
+ self.close_suggestions()
415
+ elif self.running is not None:
416
+ self.stop_task()
417
+ else:
418
+ self.query_one("#prompt", Input).value = ""
419
+
420
+ def stop_task(self):
421
+ run = self.running
422
+ if run is None or self.session is None:
423
+ return
424
+ self.notify("Stopping at the next safe point…", timeout=3)
425
+ threading.Thread(target=self.session.stop, args=(run,), daemon=True).start()
426
+
427
+ def action_toggle_ask(self):
428
+ self.change_setting("askBeforeActing", not self.settings.get("askBeforeActing", True))
429
+
430
+ def change_setting(self, name, value):
431
+ self.settings[name] = value
432
+ if name == "askBeforeActing":
433
+ # The older switch sets the mode (the server keeps a stricter one when it goes back on).
434
+ from ..permissions.policy import mode_for_ask
435
+ self.settings["permissionMode"] = mode_for_ask(value, self.settings.get("permissionMode"))
436
+ self.refresh_status()
437
+ if self.session is not None:
438
+ request = self.setting_requests[name] = object()
439
+ self.save_setting(name, value, request)
440
+ if self.running is not None:
441
+ self.notify("Applies from the next task.", timeout=3)
442
+
443
+ @work(group="settings")
444
+ async def save_setting(self, name, value, request):
445
+ async with self.settings_writer:
446
+ if self.setting_requests.get(name) is not request:
447
+ return # A newer choice already replaced this queued save.
448
+ try:
449
+ settings = await asyncio.to_thread(self.session.set_setting, name, value)
450
+ except Exception as error:
451
+ from .session import error_text
452
+ self.note(error_text(error), "error")
453
+ try:
454
+ settings = await asyncio.to_thread(self.session.settings)
455
+ except Exception:
456
+ return # The save error is already shown; a closed service cannot provide a rollback.
457
+ if self.setting_requests.get(name) is request:
458
+ self.settings[name] = settings[name]
459
+ if name == "askBeforeActing":
460
+ self.settings["permissionMode"] = settings["permissionMode"]
461
+ self.refresh_status()
462
+
463
+ def action_toggle_details(self):
464
+ self.expanded = not self.expanded
465
+ for view in self.query(RunView):
466
+ view.redraw()
467
+
468
+ def action_toggle_screen(self):
469
+ phone = self.query_one("#phone")
470
+ self.phone_visible = phone.has_class("hidden")
471
+ phone.set_class(not self.phone_visible, "hidden")
472
+ if self.phone_visible:
473
+ self.poll_phone()
474
+
475
+ def action_history(self):
476
+ self.run_command("history", "")
477
+
478
+ def action_clear(self):
479
+ for view in self.query(".run"):
480
+ if view is not self.run_view or self.running is None:
481
+ view.remove()
482
+ if self.running is None:
483
+ self.run_view = None
484
+ self.refresh_welcome()
485
+ self.show_welcome(True)
486
+ self.refresh_status()
487
+
488
+ def poll_phone(self):
489
+ """Refresh the phone pane: the outline from the latest observation, or a new screenshot."""
490
+ try:
491
+ phone = self.query_one("#phone")
492
+ if phone.has_class("hidden") or self.session is None:
493
+ return
494
+ if not self.image_class:
495
+ self.show_outline()
496
+ return
497
+ except NoMatches:
498
+ return # the UI is closing: its interval can fire after the widgets are gone
499
+ if self.phone_busy:
500
+ return
501
+ if self.running is None and self.run_view is not None and self.frame and not self.demo and \
502
+ not self.status.get("device_ready"):
503
+ return
504
+ self.phone_busy = True
505
+ self.fetch_phone()
506
+
507
+ def phone_caption(self):
508
+ view = self.run_view
509
+ if view is not None and view.narrator.screen is not None:
510
+ screen = view.narrator.screen
511
+ return f"{screen.get('app') or view.run.app['name']}, {len(screen['elements'])} elements read"
512
+ return "Scripted screens, drawn locally" if self.demo and self.image_class else ""
513
+
514
+ def show_outline(self):
515
+ view = self.run_view
516
+ screen = view.narrator.screen if view is not None else None
517
+ step = view.narrator.step if view is not None else None
518
+ target = step.target_id if step is not None and step.act in {None, "started"} and \
519
+ step.state in {"thinking", "acting"} else None
520
+ self.query_one(PhonePanel).show_outline(screen, target, self.phone_caption())
521
+
522
+ @work(thread=True, group="phone")
523
+ def fetch_phone(self):
524
+ try:
525
+ png = self.session.preview_image(self.running or (self.run_view.run if self.run_view else None))
526
+ except Exception:
527
+ png = None
528
+ finally:
529
+ self.phone_busy = False
530
+ self.call_from_thread(self.phone_ready, png)
531
+
532
+ def phone_ready(self, png):
533
+ self.query_one(PhonePanel).show_image(png, self.phone_caption())
534
+
535
+ def transcript_summary(self, color=False):
536
+ """The session, printed to the terminal after the UI closes so it stays in scrollback."""
537
+ from ..style import Palette
538
+ paint = Palette(color)
539
+ roles = {"success": "green", "error": "coral", "warning": "amber", "review": "accent", "neutral": "secondary"}
540
+ lines = []
541
+ for view in self.finished_views:
542
+ narrator = view.narrator
543
+ app = view.run.app.get("name") if view.run.app.get("bundleId") else None
544
+ lines.append(paint("❯ ", "accent", "bold") + paint(view.run.goal, "bold")
545
+ + (paint(f" {app}", "tertiary") if app else ""))
546
+ outcome = None
547
+ for item in narrator.items:
548
+ if isinstance(item, Launch) and item.done:
549
+ lines.append(" " + paint("●", "green") + f" Opened {item.app}")
550
+ elif isinstance(item, Step) and item.operation and item.operation not in {"DONE", "WAIT", "BLOCKED"}:
551
+ glyph = paint("●", "green" if item.state == "done" else "amber" if item.state == "unchanged"
552
+ else "tertiary")
553
+ lines.append(f" {glyph} {item.title}")
554
+ elif isinstance(item, Approval) and item.decision:
555
+ words = APPROVALS.get(item.decision, (None, "You chose"))[1]
556
+ lines.append(" " + paint("◆", "accent") + f" {words}: {item.title}")
557
+ elif isinstance(item, Outcome):
558
+ outcome = item
559
+ elapsed = narrator.elapsed_ms(time.time() * 1000)
560
+ line = facts(narrator.actions, elapsed, narrator.cost_nanodollars, bool(narrator.priced_calls),
561
+ plan=bool(narrator.plan_calls))
562
+ if outcome is not None:
563
+ glyph = status_glyph(outcome.head_tone)
564
+ lines.append(paint(f"{glyph} {outcome.head}", roles.get(outcome.head_tone, "secondary"), "bold")
565
+ + " " + paint(line, "tertiary"))
566
+ if outcome.hero:
567
+ lines.append(" " + outcome.hero)
568
+ detail = outcome.proof or outcome.detail
569
+ if detail:
570
+ lines.append(" " + paint(detail, "tertiary"))
571
+ elif narrator.finished_at is None:
572
+ # Still running when the UI closed: `tui.main` stops it at its next safe point.
573
+ lines.append(paint("■ Stopped when you quit", "secondary", "bold") + " " + paint(line, "tertiary"))
574
+ if not self.demo:
575
+ lines.append(" " + paint(f"mobster --resume {view.run.id}", "tertiary"))
576
+ lines.append("")
577
+ while lines and not lines[-1]:
578
+ lines.pop()
579
+ return "\n".join(lines)
580
+
581
+ def on_unmount(self):
582
+ self.follow_stop.set()