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,316 @@
1
+ """The permission gate: one per run; applies the run's mode to an action and says allow or ask."""
2
+
3
+ import re
4
+ import threading
5
+ import time
6
+ from dataclasses import dataclass, replace
7
+
8
+ from ..state import icon_glyph, pixel_read
9
+ from ..task_policy import approval_title
10
+ from . import consequence, project_policy
11
+ from . import policy as P
12
+ from .rules import RULE_OP, Action, changes, code_entry, norm, outside_act, rememberable, rule_words, screen_context
13
+
14
+ TITLES = {"code": "Enter this code?", "setting": "Change this setting?", "save": "Save this?",
15
+ "submit": "Submit this?", "change": "Make this change?", "react": "Add this reaction?",
16
+ "follow": "Follow this account?", "unfollow": "Unfollow this account?", "post": "Repost this?",
17
+ "block": "Block this?", "report": "Report this?"}
18
+ # The card's line for an act ``rules.outside_act`` found (at most 12 words).
19
+ OUTSIDE_REASONS = {"react": "Others see this reaction", "follow": "Others see this follow",
20
+ "unfollow": "Others see this unfollow", "post": "Others see this repost",
21
+ "block": "Changes who can reach you", "report": "Sends a report about someone"}
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class Decision:
26
+ decision: str # allow | ask
27
+ kind: str # navigate | input | change | commit | floor
28
+ act: str | None # the card's verb
29
+ reason: str # for the card, at most 12 words
30
+ ms: float # time this decision added
31
+ title: str | None = None
32
+ # The approval card may offer "Don't ask again" (an allow rule for ``rule`` in this app).
33
+ rememberable: bool = False
34
+ rule: str | None = None
35
+ # The ask is the project policy's (project_policy.py): the mode and the guards would have let it go ahead.
36
+ by_policy: bool = False
37
+
38
+ @property
39
+ def asks(self):
40
+ return self.decision == "ask"
41
+
42
+
43
+ def decision_for(kind, mode, existing=False):
44
+ """allow | ask for an action of ``kind`` under ``mode``. ``existing``: the agent's own rules ask."""
45
+ if kind == "floor":
46
+ return "ask"
47
+ if mode == P.DONT_ASK:
48
+ return "allow"
49
+ if existing or kind == "commit":
50
+ return "ask"
51
+ if kind == "change" and mode == P.ASK_ALL:
52
+ return "ask"
53
+ return "allow"
54
+
55
+
56
+ _COMPOSE = re.compile(r"\b(message|imessage|text message|reply|comment|write|chat|say something|add a caption)\b")
57
+
58
+
59
+ def composer_text(elements):
60
+ """The text waiting in the screen's composer, "" for an empty one, None when the screen has no composer."""
61
+ found = None
62
+ for element in elements or ():
63
+ label, placeholder = getattr(element, "label", "") or "", getattr(element, "placeholder", "") or ""
64
+ if (getattr(element, "role", None) not in ("TextView", "TextField")
65
+ or not _COMPOSE.search(norm(label or placeholder))):
66
+ continue
67
+ value = getattr(element, "value", "") or ""
68
+ if placeholder and value == placeholder or value == label:
69
+ value = ""
70
+ if value.strip():
71
+ return value
72
+ found = ""
73
+ return found
74
+
75
+
76
+ def action_from(op, element, snapshot, *, text=None, typed=None, app_name="", goal="", title=None, origins=None):
77
+ """The gate's Action for ``op`` on ``element`` (a state.Element, or None) in ``snapshot``."""
78
+ elements = getattr(snapshot, "elements", ()) or ()
79
+ if title is None:
80
+ try:
81
+ from ..contract_screen import screen_title
82
+ title = screen_title(snapshot) or ""
83
+ except Exception:
84
+ title = ""
85
+ return Action(op=op, label=getattr(element, "label", "") or "", role=getattr(element, "role", None),
86
+ value=getattr(element, "value", None) or None,
87
+ app=getattr(snapshot, "bundle_id", "") or "", app_name=app_name or "", text=text,
88
+ typed=typed if typed is not None else composer_text(elements), screen=title or "",
89
+ context=screen_context(elements), goal=goal or "",
90
+ hint=" ".join(str(getattr(element, key, "") or "") for key in ("placeholder", "identifier")),
91
+ origins=tuple(origins) if origins is not None else None,
92
+ rect=tuple(getattr(element, "rect", ()) or ()), around=tuple(elements),
93
+ pixels=pixel_read(snapshot), glyph=icon_glyph(getattr(element, "locator", "")))
94
+
95
+
96
+ class PermissionGate:
97
+ def __init__(self, mode=P.DEFAULT_MODE, *, apps=None, emit=None, goal="", origin=None, exceptions=None,
98
+ policy=None, profile=None):
99
+ self.mode = mode if mode in P.MODES else P.DEFAULT_MODE
100
+ self.apps = apps if apps is not None else P.AppPolicy()
101
+ self.emit = emit
102
+ self.goal = goal
103
+ self.origin = origin
104
+ self.exceptions = exceptions
105
+ # The project's policy and the profile (accounts' ``{id, handle...}``) the task acts as: they only add asks.
106
+ self.policy = policy
107
+ self.profile = profile
108
+
109
+ @property
110
+ def narrowed(self):
111
+ """Whether the project policy can ask here, so a run asks (or refuses, with nobody to answer) in Full access."""
112
+ return self.policy is not None and self.policy.narrows
113
+
114
+ def reading(self, action, existing_act=None):
115
+ """(kind, act, reason) for ``action`` by today's rules."""
116
+ if code_entry(action):
117
+ return "floor", "code", "Types a one-time code"
118
+ if existing_act:
119
+ return "commit", existing_act, ""
120
+ found = consequence.read(action if action.goal or not self.goal else replace(action, goal=self.goal),
121
+ action.around)
122
+ if found is not None:
123
+ return "commit", found.act, found.reason
124
+ act = outside_act(action)
125
+ if act is not None:
126
+ return "commit", act, OUTSIDE_REASONS[act]
127
+ act = changes(action)
128
+ if act is not None:
129
+ return "change", act, ""
130
+ op = (action.op or "").upper()
131
+ return ("input" if op in ("TYPE", "SET_TEXT") else "navigate"), None, ""
132
+
133
+ def decide(self, action, existing_act=None, forced=False):
134
+ """The answer for ``action``; ``forced`` asks whatever the mode says and offers or uses no allow rule. The
135
+ project policy, when there is one, can turn an allow into an ask and nothing else."""
136
+ decision = self._decide(action, existing_act, forced)
137
+ if self.policy is not None and decision.kind in ("commit", "change"):
138
+ decision = self._narrow(decision, action)
139
+ return self._record(decision)
140
+
141
+ def _narrow(self, decision, action):
142
+ """``decision`` or, when the project policy asks where the mode and the guards would not, an ask. An ask the
143
+ mode already made stays as it is, without an offer of "Don't ask again" if the policy asks too."""
144
+ kinds = project_policy.kinds_of(decision.kind, decision.act)
145
+ why = self.policy.asks(kinds, action.app, self.profile)
146
+ if decision.asks:
147
+ return replace(decision, rememberable=False, rule=None) if why and decision.rememberable else decision
148
+ why = why or self.policy.take(kinds, action.app, self.profile)
149
+ if why is None:
150
+ return decision
151
+ act = decision.act
152
+ title = consequence.TITLES.get(act or "") or TITLES.get(act or "") or approval_title(act)
153
+ return Decision("ask", decision.kind, act, why, decision.ms, title, by_policy=True)
154
+
155
+ def _decide(self, action, existing_act, forced):
156
+ started = time.perf_counter()
157
+ kind, act, reason = self.reading(action, existing_act)
158
+ verdict = decision_for(kind, self.mode, existing=bool(existing_act))
159
+ if forced and verdict == "allow":
160
+ verdict = "ask"
161
+ outside = self.origin in P.OUTSIDE_ORIGINS
162
+ rule = (rememberable(action, act) if verdict == "ask" and kind in ("commit", "change") and not forced
163
+ else None)
164
+ if rule is not None and not outside and self.exceptions is not None:
165
+ try:
166
+ remembered = self.exceptions.contains(action.app, rule, RULE_OP)
167
+ except Exception: # noqa: BLE001 -- an unreadable store asks
168
+ remembered = False
169
+ if remembered:
170
+ return Decision("allow", kind, act, "You said not to ask again for this",
171
+ (time.perf_counter() - started) * 1000, rule=rule)
172
+ title = None
173
+ if verdict == "ask":
174
+ title = (consequence.REASON_TITLES.get(reason) or TITLES.get(act or "")
175
+ or consequence.TITLES.get(act or "")) if kind in ("floor", "change") or (
176
+ kind == "commit" and not existing_act) else None
177
+ return Decision(verdict, kind, act, reason if verdict != "allow" else "",
178
+ (time.perf_counter() - started) * 1000, title,
179
+ rememberable=rule is not None and not outside, rule=rule)
180
+
181
+ @staticmethod
182
+ def remember(decision, app, app_name=""):
183
+ """The approval request's ``remember`` for ``decision`` (run.Run keeps it until the answer)."""
184
+ if decision is None or not decision.rememberable or not decision.rule or not app:
185
+ return None
186
+ return {"app": app, "label": decision.rule, "op": RULE_OP, "app_name": app_name or "",
187
+ "words": rule_words(decision.rule, app_name)}
188
+
189
+ def _record(self, decision):
190
+ if self.emit is not None and decision.decision != "allow":
191
+ try:
192
+ # No label, no text: what the gate said, and why.
193
+ self.emit({"event": "permission_decision", "decision": decision.decision, "kind": decision.kind,
194
+ "act": decision.act, "ms": round(decision.ms, 2), "mode": self.mode,
195
+ **({"policy": True} if decision.by_policy else {})})
196
+ except Exception:
197
+ pass
198
+ return decision
199
+
200
+
201
+ class PermissionGuard:
202
+ """A phone guard for the person's app choices; the rest is the inner guard's. Fails closed: no answer, no app."""
203
+
204
+ def __init__(self, inner, apps, names=None, outside=False, ask=None, grants=None, conversation=None):
205
+ self.inner, self.names, self.outside = inner, dict(names or {}), outside
206
+ self._apps = apps
207
+ self.ask, self.grants = ask, grants
208
+ self.declined = set() # apps the person said Not now to in this run: not asked again in it
209
+ # Allow covers this run, or its conversation's runs; never an app that is refused or switched off.
210
+ self._conversation = conversation is not None
211
+ self._granted = conversation if conversation is not None else set()
212
+ self.instruction = None
213
+ self._lock = threading.Lock()
214
+
215
+ @property
216
+ def apps(self):
217
+ apps = self._apps
218
+ try:
219
+ return apps() if callable(apps) else apps
220
+ except Exception: # noqa: BLE001 -- unreadable settings: nothing counts as approved, the never-open list holds
221
+ return P.AppPolicy(access="ask")
222
+
223
+ @apps.setter
224
+ def apps(self, value):
225
+ self._apps = value
226
+
227
+ def _approved(self, apps, bundle):
228
+ return bundle in self._granted or apps.approved(bundle, self.grants)
229
+
230
+ def take_instruction(self):
231
+ """What the person said to do instead on the app card, once; None when they didn't."""
232
+ with self._lock:
233
+ instruction, self.instruction = self.instruction, None
234
+ return instruction
235
+
236
+ def __getattr__(self, name):
237
+ if self.inner is None:
238
+ raise AttributeError(name)
239
+ return getattr(self.inner, name)
240
+
241
+ def check(self, driver, *, cause):
242
+ if self.inner is None:
243
+ from ..agent_hooks import READY
244
+ return READY
245
+ return self.inner.check(driver, cause=cause)
246
+
247
+ def attached(self):
248
+ return self.inner.attached() if self.inner is not None else None
249
+
250
+ def finish(self, driver):
251
+ if self.inner is not None:
252
+ self.inner.finish(driver)
253
+
254
+ def _name(self, bundle):
255
+ return self.names.get(bundle) or bundle
256
+
257
+ def request(self, bundles):
258
+ """Whether every app in ``bundles`` may be used now: False if one is refused, declined, or nobody answers."""
259
+ apps = self.apps
260
+ wanted = [b for b in dict.fromkeys(bundles) if b and b != P.SPRINGBOARD]
261
+ if any(not apps.allows(b, outside=self.outside) for b in wanted):
262
+ return False
263
+ with self._lock:
264
+ missing = [b for b in wanted if not self._approved(apps, b)]
265
+ if not missing:
266
+ return True
267
+ if any(b in self.declined for b in missing) or self.ask is None:
268
+ return False
269
+ names = [self._name(b) for b in missing]
270
+ listed = names[0] if len(names) == 1 else ", ".join(names[:-1]) + " and " + names[-1]
271
+ card = {"kind": "commit", "operation": "OPEN_APP", "label": listed[:200], "role": "App", "text": None,
272
+ "title": f"Allow Mobster to use {listed}?", "act": "use app", "app_name": names[0], "apps": names,
273
+ "bundles": missing, "reason": P.task_card_reason(len(names), self._conversation)}
274
+ if self.grants is not None:
275
+ card["remember"] = {"app": missing[0], "label": P.APP_LABEL, "op": P.APP_OP, "app_name": names[0],
276
+ "words": P.ALWAYS_ALLOW,
277
+ "apps": [{"app": b, "app_name": n} for b, n in zip(missing, names)]}
278
+ try:
279
+ answer = self.ask(card)
280
+ except Exception: # noqa: BLE001 -- no answer: the app stays shut
281
+ answer = None
282
+ if answer != "approved":
283
+ self.declined.update(missing)
284
+ if isinstance(answer, str) and answer.startswith("redirected:"):
285
+ self.instruction = " ".join(answer.split(":", 1)[1].split())[:500] or None
286
+ return False
287
+ # Allow: this run only; Always allow is read from the store at each check, so it can be off mid-run.
288
+ self._granted.update(b for b in missing if not apps.approved(b, self.grants))
289
+ return True
290
+
291
+ def allows_app(self, bundle_id):
292
+ try:
293
+ if bundle_id and bundle_id != P.SPRINGBOARD and not self.request([bundle_id]):
294
+ return False
295
+ inner = getattr(self.inner, "allows_app", None)
296
+ return bool(inner(bundle_id)) if callable(inner) else True
297
+ except Exception: # noqa: BLE001 -- fails closed
298
+ return False
299
+
300
+ def refusal(self, bundles):
301
+ """Why ``request(bundles)`` said no, without asking again."""
302
+ apps = self.apps
303
+ wanted = [b for b in dict.fromkeys(bundles) if b and b != P.SPRINGBOARD]
304
+ refused = [b for b in wanted if not apps.allows(b, outside=self.outside)]
305
+ waiting = [b for b in wanted if not self._approved(apps, b)]
306
+ return next((self.blocked_message(b) for b in refused + waiting), None)
307
+
308
+ def blocked_message(self, bundle_id, name=None):
309
+ name = name or self._name(bundle_id)
310
+ apps = self.apps
311
+ if not apps.allows(bundle_id, outside=self.outside):
312
+ return apps.blocked_message(name, outside=self.outside)
313
+ if not self._approved(apps, bundle_id):
314
+ return apps.unapproved_message(name)
315
+ inner = getattr(self.inner, "blocked_message", None)
316
+ return inner(bundle_id, name) if callable(inner) else None
@@ -0,0 +1,316 @@
1
+ """Approvals for outside agents over MCP: the same gate applies, and only the Mac app's session can answer one."""
2
+
3
+ import hashlib
4
+ import json
5
+ import threading
6
+ import time
7
+ import uuid
8
+ from dataclasses import asdict
9
+
10
+ from . import consequence
11
+ from . import policy as P
12
+ from .gate import PermissionGate
13
+
14
+ EXPIRES_S = 600
15
+ WAIT_S = 38.0
16
+ KEEP_S = 120 # a decided approval stays readable this long, for the agent that asked
17
+ MAX_PENDING = 20
18
+ POLL_S = 20.0
19
+
20
+
21
+ class Refused(Exception):
22
+ """The action may not go ahead now; the message says why and what the agent can do."""
23
+
24
+ def __init__(self, message="", state=None, minutes=None):
25
+ super().__init__(message)
26
+ self.state, self.minutes = state, minutes
27
+
28
+
29
+ def _clean(value, limit):
30
+ return " ".join(str(value or "").split())[:limit] or None
31
+
32
+
33
+ class Registry:
34
+ def __init__(self, clock=time.time):
35
+ self.clock = clock
36
+ self._items = {}
37
+ self._version = 0
38
+ self._cond = threading.Condition()
39
+
40
+ def _expire(self, now):
41
+ for item in list(self._items.values()):
42
+ if item["state"] == "pending" and now * 1000 >= item["expiresAt"]:
43
+ item["state"], item["decidedAt"] = "timeout", now * 1000
44
+ self._version += 1
45
+ if item["state"] != "pending" and now * 1000 - (item.get("decidedAt") or 0) > KEEP_S * 1000:
46
+ del self._items[item["id"]]
47
+
48
+ def create(self, body):
49
+ if not isinstance(body, dict) or set(body) - {"key", "request", "device", "source"}:
50
+ raise ValueError("An approval takes key, request, device and source")
51
+ request = body.get("request")
52
+ if not isinstance(request, dict) or not isinstance(request.get("label"), str):
53
+ raise ValueError("request needs at least the control's label")
54
+ key = _clean(body.get("key"), 128)
55
+ source = body.get("source") if isinstance(body.get("source"), dict) else {}
56
+ fingerprint = hashlib.sha256(json.dumps((body.get("device"), source, request), sort_keys=True,
57
+ ensure_ascii=False, allow_nan=False).encode()).hexdigest()
58
+ now = self.clock()
59
+ with self._cond:
60
+ self._expire(now)
61
+ for item in self._items.values():
62
+ if key and item["key"] == key and item["state"] == "pending":
63
+ if item["fingerprint"] != fingerprint:
64
+ raise ValueError("This approval key already names a different waiting action")
65
+ return dict(item)
66
+ if sum(item["state"] == "pending" for item in self._items.values()) >= MAX_PENDING:
67
+ raise ValueError("Too many approvals are waiting")
68
+ item = {"id": uuid.uuid4().hex[:12], "key": key, "fingerprint": fingerprint, "state": "pending",
69
+ "kind": "commit", "outside": True,
70
+ "source": {"kind": "mcp", "client": _clean(source.get("client"), 60)},
71
+ "device": _clean(body.get("device"), 128),
72
+ "operation": _clean(request.get("operation"), 20) or "TAP",
73
+ "label": _clean(request.get("label"), 200) or "", "role": _clean(request.get("role"), 40) or "",
74
+ "text": str(request["text"])[:4000] if isinstance(request.get("text"), str) else None,
75
+ "title": _clean(request.get("title"), 160), "act": _clean(request.get("act"), 40),
76
+ "reason": _clean(request.get("reason"), 120), "app": _clean(request.get("app_name"), 80) or "",
77
+ "target": None, "requestedAt": now * 1000, "expiresAt": (now + EXPIRES_S) * 1000,
78
+ "instruction": None}
79
+ if isinstance(request.get("apps"), list):
80
+ item["apps"] = [name for name in (_clean(n, 80) for n in request["apps"][:6]) if name]
81
+ self._items[item["id"]] = item
82
+ self._version += 1
83
+ self._cond.notify_all()
84
+ return dict(item)
85
+
86
+ def get(self, approval_id, wait=0.0):
87
+ """The approval, once decided or after ``wait`` seconds; None when unknown."""
88
+ deadline = time.monotonic() + max(0.0, min(wait, POLL_S))
89
+ with self._cond:
90
+ while True:
91
+ self._expire(self.clock())
92
+ item = self._items.get(approval_id)
93
+ if item is None or item["state"] != "pending":
94
+ return dict(item) if item else None
95
+ left = deadline - time.monotonic()
96
+ if left <= 0:
97
+ return dict(item)
98
+ self._cond.wait(min(left, 1.0))
99
+
100
+ def pending(self, since=None, wait=0.0):
101
+ """(version, the approvals waiting): returns at once, or when the list changes after ``since``."""
102
+ deadline = time.monotonic() + max(0.0, min(wait, POLL_S))
103
+ with self._cond:
104
+ while True:
105
+ self._expire(self.clock())
106
+ if since is None or since != self._version or time.monotonic() >= deadline:
107
+ items = [dict(i) for i in self._items.values() if i["state"] == "pending"]
108
+ return self._version, sorted(items, key=lambda i: i["requestedAt"])
109
+ self._cond.wait(min(deadline - time.monotonic(), 1.0))
110
+
111
+ def answer(self, approval_id, approve, instruction=None):
112
+ if type(approve) is not bool:
113
+ raise ValueError("approve must be true or false")
114
+ if instruction is not None:
115
+ instruction = " ".join(str(instruction).split())
116
+ if approve or not 1 <= len(instruction) <= 500:
117
+ raise ValueError("A redirect declines the action and says in 1–500 characters what to do instead")
118
+ with self._cond:
119
+ self._expire(self.clock())
120
+ item = self._items.get(approval_id)
121
+ if item is None or item["state"] != "pending":
122
+ return False
123
+ item["state"] = "approved" if approve else "redirected" if instruction else "denied"
124
+ item["instruction"], item["decidedAt"] = instruction, self.clock() * 1000
125
+ self._version += 1
126
+ self._cond.notify_all()
127
+ return True
128
+
129
+ def cancel(self, approval_id):
130
+ with self._cond:
131
+ item = self._items.get(approval_id)
132
+ if item is None or item["state"] != "pending":
133
+ return False
134
+ item["state"], item["decidedAt"] = "cancelled", self.clock() * 1000
135
+ self._version += 1
136
+ self._cond.notify_all()
137
+ return True
138
+
139
+
140
+ # In memory only: an approval can hold the exact text to be sent, which is never journaled.
141
+ REGISTRY = Registry()
142
+
143
+
144
+ def public(item):
145
+ """An approval as the Mac app reads it (the exact text included: it never leaves this Mac's memory)."""
146
+ return {k: v for k, v in item.items() if k not in {"key", "fingerprint"}}
147
+
148
+
149
+ def key_for(device, action):
150
+ raw = json.dumps((device, asdict(action)), sort_keys=True, ensure_ascii=False, separators=(",", ":"))
151
+ return hashlib.sha256(raw.encode()).hexdigest()[:32]
152
+
153
+
154
+ def app_name(bundle, device=None, folder=None):
155
+ """The name the person knows the app by ("Chrome")."""
156
+ try:
157
+ from ..catalog import app_name as catalog_name
158
+ from ..installed_apps import InventoryCache
159
+ from ..paths import user_data_dir
160
+ inventory = InventoryCache(folder or user_data_dir() / "installed-apps").load(device) if device else None
161
+ for app in (inventory.user if inventory is not None else ()):
162
+ if app.get("bundleId") == bundle and app.get("name") and app["name"] != bundle:
163
+ return app["name"]
164
+ return catalog_name(bundle) or bundle
165
+ except Exception: # noqa: BLE001 -- a name is for reading: the bundle id stands in
166
+ return bundle
167
+
168
+
169
+ def check_app(bundle, name=None, *, device, client_name=None, settings=None, grants=None, connect=None,
170
+ wait_s=WAIT_S):
171
+ """An outside agent opening or acting in ``bundle``: returns once approved, else Refused."""
172
+ settings = settings or P.from_file()
173
+ name = name or app_name(bundle, device)
174
+ if not settings.apps.allows(bundle, outside=True):
175
+ raise Refused(settings.apps.blocked_message(name, outside=settings.apps.allows(bundle)) +
176
+ " Nothing was done.")
177
+ if grants is None:
178
+ from .exceptions import Exceptions, default_path
179
+ grants = Exceptions(default_path())
180
+ if settings.apps.approved(bundle, grants):
181
+ return
182
+ request = {"operation": "OPEN_APP", "label": name, "role": "App", "text": None,
183
+ "title": f"Allow Mobster to use {name}?", "act": "use app", "app_name": name, "apps": [name],
184
+ "reason": P.app_card_reason()}
185
+ answer = ask_app(request, key=hashlib.sha256(f"{device}␟app␟{bundle}".encode()).hexdigest()[:32],
186
+ device=device, client_name=client_name, connect=connect, wait_s=wait_s)
187
+ if answer.get("state") == "approved":
188
+ try:
189
+ grants.add(bundle, P.APP_LABEL, P.APP_OP, name)
190
+ except Exception: # noqa: BLE001 -- approved now even if the store can't keep it
191
+ pass
192
+ return
193
+ _refuse(answer, f"Allow Mobster to use {name}")
194
+
195
+
196
+ def _refuse(answer, what):
197
+ """Refused with what the person's answer (not approved) means for the agent."""
198
+ state = answer.get("state")
199
+ if state == "redirected":
200
+ raise Refused(f"The user declined this in the Mobster app and said: {answer.get('instruction')!r}. Nothing "
201
+ "was done.", state)
202
+ if state == "denied":
203
+ raise Refused("The user declined this in the Mobster app. Nothing was done.", state)
204
+ if state == "timeout":
205
+ raise Refused("Nobody answered the approval in the Mobster app in 10 minutes, so nothing was done.", state)
206
+ if state == "pending":
207
+ left = max(0, int((answer.get("expiresAt", 0) / 1000 - time.time()) // 60))
208
+ raise Refused(f"This needs the user's OK in the Mobster app ({what}). They haven't answered yet, so nothing "
209
+ f"was done. Tell the user, then make the same call again to keep waiting (the request stays "
210
+ f"open about {left} more minutes).", state, left)
211
+ raise Refused("The approval was withdrawn, so nothing was done.", "withdrawn")
212
+
213
+
214
+ def _project_policy():
215
+ """The policy of the project this server runs in (its working folder), when MOBSTER_POLICY is on."""
216
+ from ..paths import default_history_db
217
+ from . import project_policy
218
+ folder = project_policy.project_folder() if project_policy.enabled() else None
219
+ return project_policy.load(folder, project_policy.counts_path(default_history_db())) if folder else None
220
+
221
+
222
+ def check_action(action, *, device, client_name=None, settings=None, connect=None, wait_s=WAIT_S, forced=False):
223
+ """An outside agent's action: whether it asked (True), or Refused; never below ask_important."""
224
+ settings = settings or P.from_file()
225
+ if action.app:
226
+ check_app(action.app, action.app_name, device=device, client_name=client_name, settings=settings,
227
+ connect=connect, wait_s=wait_s)
228
+ from ..harness_api import commit_act
229
+ gate = PermissionGate(P.effective_mode(settings.mode, None, "mcp"), apps=settings.apps, origin="mcp",
230
+ policy=_project_policy())
231
+ op = (action.op or "").upper()
232
+ typing = op in ("TYPE", "SET_TEXT")
233
+ # A row's swipe is read by the gate's own signals (permissions.consequence): its words are the row's content.
234
+ existing = None if typing or op.startswith("SWIPE_") else commit_act(action.op, action.label, None,
235
+ action.app or None)
236
+ if existing == "other" and op in consequence.RETURN_OPS and consequence.query_return(action) and commit_act(
237
+ "TYPE", action.label, None, action.app or None) is None:
238
+ # commit_act calls every Return a commit; only a field told for certain to be a search or address bar drops it.
239
+ existing = None
240
+ decision = gate.decide(action, existing_act=existing, forced=forced)
241
+ if decision.decision == "allow":
242
+ return False
243
+ title = decision.title or approval_title(decision.act)
244
+ text = action.text
245
+ if text is None and decision.act in {"send_message", "reply", "comment", "post", "share"}:
246
+ text = action.typed
247
+ request = {"operation": action.op, "label": action.label, "role": action.role or "", "text": text,
248
+ "title": title, "act": decision.act, "reason": decision.reason or None,
249
+ "app_name": action.app_name or action.app}
250
+ answer = ask_app(request, key=key_for(device, action), device=device, client_name=client_name, connect=connect,
251
+ wait_s=wait_s)
252
+ if answer.get("state") == "approved":
253
+ return True
254
+ _refuse(answer, title)
255
+
256
+
257
+ def check_code(bundle, field_name, *, device, client_name=None, connect=None, wait_s=WAIT_S):
258
+ """An outside agent entering a one-time code (use_code): it asks in the Mac app in every mode, never remembered."""
259
+ request = {"operation": "USE_CODE", "label": str(field_name or "the code field")[:200], "role": "TextField",
260
+ "text": None, "title": "Enter this code?", "act": "code", "reason": "Types a one-time code",
261
+ "app_name": bundle or ""}
262
+ answer = ask_app(request, key=hashlib.sha256(f"{device}␟code␟{bundle}␟{field_name}".encode()).hexdigest()[:32],
263
+ device=device, client_name=client_name, connect=connect, wait_s=wait_s)
264
+ if answer.get("state") == "approved":
265
+ return
266
+ _refuse(answer, "Enter this code?")
267
+
268
+
269
+ def check_link(url, title, app, app_name, *, device, client_name=None, connect=None, wait_s=WAIT_S):
270
+ """An outside agent opening a link that asks (deeplinks.decide): the Mac app's card every time, never remembered."""
271
+ request = {"operation": "OPEN_URL", "label": f"Open {str(url)[:180]}", "role": "Link", "text": str(url)[:500],
272
+ "title": title or "Open this link?", "act": "open link", "reason": None,
273
+ "app_name": app_name or app or ""}
274
+ answer = ask_app(request, key=hashlib.sha256(f"{device}␟link␟{url}".encode()).hexdigest()[:32], device=device,
275
+ client_name=client_name, connect=connect, wait_s=wait_s)
276
+ if answer.get("state") == "approved":
277
+ return
278
+ _refuse(answer, title or "Open this link?")
279
+
280
+
281
+ _TITLES = {"send_message": "Send this message?", "reply": "Send this reply?", "comment": "Post this comment?",
282
+ "post": "Post this?", "react": "Send this reaction?", "pay": "Make this payment?",
283
+ "request_money": "Send this request?", "transfer": "Make this transfer?", "order": "Place this order?",
284
+ "book": "Make this booking?", "follow": "Follow this account?", "unfollow": "Unfollow this account?",
285
+ "share": "Share this?", "call": "Place this call?", "delete": "Delete this?", "cancel": "Cancel this?",
286
+ "install": "Get this app?"}
287
+
288
+
289
+ def approval_title(act):
290
+ """The card's question for an act of today's commit test ("Send this message?")."""
291
+ return _TITLES.get(act or "", "Do this now?")
292
+
293
+
294
+ def ask_app(request, *, key, device=None, client_name=None, connect=None, wait_s=WAIT_S):
295
+ """Raise the approval in the Mac app and wait up to ``wait_s`` for its answer: the approval as the app has it."""
296
+ from ..threads.client import Client, NoService, ServiceError
297
+ try:
298
+ client = (connect or (lambda: Client.connect(origin="mcp")))()
299
+ made = client.call("POST", "/api/approvals", {"key": key, "request": request, "device": device,
300
+ "source": {"client": client_name}})
301
+ except (NoService, ServiceError, OSError):
302
+ raise Refused("This needs the user's OK, and the Mobster app isn't open to ask them. Ask the user to open "
303
+ "Mobster, then try again. Nothing was done.") from None
304
+ approval = made.get("approval") or made
305
+ deadline = time.monotonic() + wait_s
306
+ while approval.get("state") == "pending":
307
+ left = deadline - time.monotonic()
308
+ if left <= 0:
309
+ break
310
+ try:
311
+ got = client.call("GET", f"/api/approvals/{approval['id']}?wait={min(POLL_S, max(1, int(left)))}",
312
+ timeout=min(POLL_S, left) + 5)
313
+ except (ServiceError, OSError):
314
+ break
315
+ approval = got.get("approval") or got
316
+ return approval