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,77 @@
1
+ """Turning Wi-Fi on or off for one phone: what `mobster wifi on|off` and POST /api/devices/{device}/wifi share."""
2
+
3
+ from . import pair, store
4
+
5
+ # What the pair tool says when the phone or this Mac said no before anything changed on the phone.
6
+ SAID_NO = ("needs_cable", "not_trusted", "refused")
7
+
8
+
9
+ class SwitchRefused(Exception):
10
+ """The switch didn't happen; ``code`` is a ``words.PROBLEMS`` key."""
11
+
12
+ def __init__(self, code):
13
+ super().__init__(code)
14
+ self.code = code
15
+
16
+
17
+ def turn_on(data_dir, udid, listing):
18
+ if listing is None:
19
+ raise SwitchRefused("needs_cable")
20
+ if not listing.get("trusted"):
21
+ raise SwitchRefused("not_trusted")
22
+ if not store.is_set_up(data_dir, udid):
23
+ raise SwitchRefused("not_set_up")
24
+ before = noted(data_dir, udid)
25
+ via = "mobster"
26
+ try:
27
+ pair.run(udid, "enable")
28
+ except LookupError:
29
+ via = "xcode" # no pair tool: nothing was sent, the person ticks it in Xcode
30
+ except pair.PairError as error:
31
+ if error.code in SAID_NO:
32
+ put_back(data_dir, udid, before)
33
+ raise SwitchRefused(error.code if error.code in SAID_NO else "refused") from None
34
+ except RuntimeError:
35
+ put_back(data_dir, udid, before) # devices are off in this process: nothing was sent
36
+ raise
37
+ store.put(data_dir, udid, True, via)
38
+ return via
39
+
40
+
41
+ def noted(data_dir, udid):
42
+ """Mobster's list notes that Mobster may turn on the phone's switch, before the pair tool runs: an exit or crash
43
+ before the answer still leaves the record a later `mobster wifi off` clears it from. Returns the record as it was,
44
+ for put_back. LookupError when the phone isn't set up here."""
45
+ before = store.get(data_dir, udid)
46
+ if not before["enabled"]:
47
+ store.put(data_dir, udid, False, "mobster")
48
+ return before
49
+
50
+
51
+ def put_back(data_dir, udid, before):
52
+ """The phone said no, or nothing was sent: the record goes back to what ``noted`` found."""
53
+ if not before["enabled"]:
54
+ store.put(data_dir, udid, False, before["via"])
55
+
56
+
57
+ def turn_off(data_dir, udid, listing):
58
+ """Wi-Fi off for the phone in Mobster at once, then the phone's own switch when Mobster turned that on, over the
59
+ cable with the phone trusting this Mac. Returns None when no switch is left on that Mobster knows of, else the
60
+ ``words.PROBLEMS`` key of the one left on: "still_on" (not on the cable, or not trusted), "off_failed" (the pair
61
+ tool couldn't) or "xcode_on" (turned on in Xcode, which Mobster leaves alone)."""
62
+ via = store.get(data_dir, udid)["via"]
63
+ try:
64
+ store.put(data_dir, udid, False, "mobster" if via == "mobster" else None)
65
+ except LookupError:
66
+ raise SwitchRefused("not_set_up") from None
67
+ if via != "mobster":
68
+ return "xcode_on" if via == "xcode" else None
69
+ if listing is None or not listing.get("trusted"):
70
+ return "still_on"
71
+ try:
72
+ if pair.run(udid, "disable") is not False:
73
+ return "off_failed"
74
+ except (LookupError, RuntimeError): # no tool, devices off in this process, or the tool said no (PairError)
75
+ return "off_failed"
76
+ store.put(data_dir, udid, False)
77
+ return None
@@ -0,0 +1,118 @@
1
+ """Cable or Wi-Fi: choosing the transport, the reconnect backoff, and each phone's link state (SPEC §3.7)."""
2
+
3
+ import threading
4
+ import time
5
+ from dataclasses import dataclass, replace
6
+ from typing import Optional
7
+
8
+ # Seconds before each reconnect attempt after a failure: 1, 2, 4, 8, ... at most 60.
9
+ BACKOFF = (1, 2, 4, 8, 16, 32, 60)
10
+
11
+
12
+ def choose(usb_attached, wifi_enabled, tunnel_up):
13
+ """The transport a phone should use now: "usb", "wifi" or None (unplugged and not reachable)."""
14
+ if usb_attached:
15
+ return "usb"
16
+ if wifi_enabled and tunnel_up:
17
+ return "wifi"
18
+ return None
19
+
20
+
21
+ def next_step(current, desired, busy):
22
+ if current == desired:
23
+ return "stay"
24
+ return "wait" if busy else "switch"
25
+
26
+
27
+ class Backoff:
28
+ """Reconnect delays: after the n-th failure in a row, wait BACKOFF[n-1] seconds (60 s from then on)."""
29
+
30
+ def __init__(self, schedule=BACKOFF):
31
+ self.schedule = tuple(schedule)
32
+ self.failures = 0
33
+ self.next_at = 0.0
34
+
35
+ def delay(self):
36
+ if self.failures <= 0:
37
+ return 0
38
+ return self.schedule[min(self.failures, len(self.schedule)) - 1]
39
+
40
+ def ready(self, now):
41
+ return now >= self.next_at
42
+
43
+ def fail(self, now):
44
+ """Count a failure; returns the seconds until the next attempt."""
45
+ self.failures += 1
46
+ wait = self.delay()
47
+ self.next_at = now + wait
48
+ return wait
49
+
50
+ def reset(self):
51
+ self.failures = 0
52
+ self.next_at = 0.0
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class Link:
57
+ """One phone's connection as Mobster sees it now."""
58
+ udid: str
59
+ transport: Optional[str] = None # "usb" | "wifi" | None: what its runner uses now
60
+ tunnel: str = "unknown" # "up" | "connecting" | "down" | "unknown"
61
+ reachable: bool = False # its WebDriverAgent answers through this Mac's relay or iproxy
62
+ last_seen_at: Optional[int] = None # ms: the last time it answered
63
+ problem: Optional[str] = None # a words.PROBLEMS code, or None
64
+ switching: bool = False # a change of transport is pending or under way
65
+
66
+
67
+ _links = {}
68
+ _lock = threading.Lock()
69
+
70
+
71
+ def link(udid) -> Optional[Link]:
72
+ with _lock:
73
+ return _links.get(_key(udid))
74
+
75
+
76
+ def update(udid, **changes):
77
+ """Set fields of ``udid``'s link; returns (before, after). ``reachable`` True stamps ``last_seen_at``."""
78
+ key = _key(udid)
79
+ with _lock:
80
+ before = _links.get(key)
81
+ current = before or Link(udid=udid)
82
+ if changes.get("reachable"):
83
+ changes.setdefault("last_seen_at", round(time.time() * 1000))
84
+ after = replace(current, **changes)
85
+ _links[key] = after
86
+ return before, after
87
+
88
+
89
+ def forget(udid=None):
90
+ """Drop one phone's link state, or every phone's (tests, a monitor that stops)."""
91
+ with _lock:
92
+ if udid is None:
93
+ _links.clear()
94
+ else:
95
+ _links.pop(_key(udid), None)
96
+
97
+
98
+ def _key(udid):
99
+ return str(udid or "").upper()
100
+
101
+
102
+ # What lockscreen.DetectGuard.attached() returns when Wi-Fi has nothing to say about a phone: it then asks USB.
103
+ NOT_WIFI = object()
104
+
105
+
106
+ def attached(udid):
107
+ state = link(udid)
108
+ if state is None or state.transport != "wifi":
109
+ return NOT_WIFI # on the cable (a task started there still stops as unplugged when it's pulled)
110
+ if state.switching:
111
+ return None
112
+ return True if state.reachable else None
113
+
114
+
115
+ def lost(udid):
116
+ """Whether ``udid`` was running over Wi-Fi and its link went down (not a switch Mobster is making)."""
117
+ state = link(udid)
118
+ return state is not None and state.transport == "wifi" and not state.reachable and not state.switching
@@ -0,0 +1,81 @@
1
+
2
+ # code -> (what's wrong, the one fix)
3
+ PROBLEMS = {
4
+ "needs_cable": ("Plug {name} into this Mac with a cable to turn on Wi-Fi for it.",
5
+ "Plug it in, unlock it and tap Trust if it asks, then try again."),
6
+ "not_trusted": ("{name} doesn't trust this Mac yet.",
7
+ "Unlock it and tap Trust, then try again."),
8
+ "not_set_up": ("{name} isn't set up in Mobster yet.",
9
+ "Open Setup with it plugged in and finish the steps, then turn on Wi-Fi."),
10
+ "needs_xcode": ("Mobster couldn't turn on Wi-Fi for {name} by itself.",
11
+ "In Xcode, open Window › Devices and Simulators, select {name} and tick Connect via network."),
12
+ "refused": ("{name} didn't let Mobster turn on Wi-Fi.",
13
+ "Unlock it and try again, or tick Connect via network for it in Xcode › Devices and Simulators."),
14
+ "not_seen": ("{name} isn't reachable over Wi-Fi.",
15
+ "Keep it unlocked and on the same Wi-Fi as this Mac, or plug it in."),
16
+ "route": ("Mobster stopped using Wi-Fi for {name}: this Mac no longer reaches it through the encrypted link.",
17
+ "Turn off any VPN on this Mac, or plug {name} in."),
18
+ "tunnel_down": ("The encrypted link to {name} isn't up yet.",
19
+ "Unlock it and keep it on the same Wi-Fi as this Mac; Mobster tries again by itself."),
20
+ "address": ("Wi-Fi isn't available for this iPhone yet.",
21
+ "Plug it in to use it now."),
22
+ "devicectl_hang": ("Xcode's device service stopped answering.",
23
+ "Plug {name} in once, or restart this Mac if it keeps happening."),
24
+ "no_xcode": ("Using an iPhone over Wi-Fi needs Xcode on this Mac.",
25
+ "Install Xcode from the App Store and open it once."),
26
+ "identity": ("CoreDevice gave Mobster another iPhone's Wi-Fi address for {name}, so Mobster didn't use it.",
27
+ "Plug {name} in; Mobster tries Wi-Fi again by itself."),
28
+ "runner": ("Mobster's helper didn't start on {name} over Wi-Fi.",
29
+ "Keep it unlocked and on the charger, or plug it in."),
30
+ "port_busy": ("Another program is using {name}'s connection on this Mac.",
31
+ "Quit the other program, or plug the iPhone in."),
32
+ "busy": ("A task is running on {name}.",
33
+ "Stop it first, or wait for it to finish."),
34
+ "plugged_in": ("{name} is plugged in, so a Wi-Fi check says nothing about it.",
35
+ "Unplug the cable, keep it unlocked on the same Wi-Fi as this Mac, then check again."),
36
+ "probe_running": ("Mobster is already checking Wi-Fi for {name}.",
37
+ "Wait for that check to finish."),
38
+ "probe_timeout": ("The Wi-Fi check for {name} took too long.",
39
+ "Keep it unlocked on the same Wi-Fi as this Mac, then check again."),
40
+ "off": ("Wi-Fi for iPhones is off in this Mobster.",
41
+ "Turn it on in Settings, or set MOBSTER_WIFI_TRANSPORT=1 in your env file."),
42
+ # Wi-Fi turned off in Mobster, with the phone's own switch (Connect via network) left on (switch.turn_off).
43
+ "still_on": ("{name} still accepts Wi-Fi connections from Macs it trusts: Mobster turns its Connect via network "
44
+ "switch off only over the cable.",
45
+ "Plug {name} in and unlock it, then turn off Wi-Fi for it again."),
46
+ "off_failed": ("Mobster couldn't turn off {name}'s Connect via network switch, so it still accepts Wi-Fi "
47
+ "connections from Macs it trusts.",
48
+ "Unlock {name}, keep it plugged in and turn off Wi-Fi for it again, or untick Connect via network "
49
+ "for it in Xcode › Window › Devices and Simulators."),
50
+ "xcode_on": ("{name} may still accept Wi-Fi connections from Macs it trusts: Connect via network was turned on "
51
+ "for it in Xcode, so Mobster left it on.",
52
+ "In Xcode, open Window › Devices and Simulators, select {name} and untick Connect via network."),
53
+ # This Mac's network (network.peer_check), when the phone can't be found over Wi-Fi.
54
+ "network_blocked": ("This network stops devices from seeing each other.",
55
+ "Use the cable or your phone's hotspot."),
56
+ "no_local_network": ("macOS doesn't let Mobster reach devices on your network.",
57
+ "Turn on Mobster in System Settings › Privacy & Security › Local Network."),
58
+ "vpn": ("A VPN on this Mac can keep it and {name} apart.",
59
+ "Turn the VPN off, or use the cable."),
60
+ }
61
+
62
+ # network.peer_check's state -> the problem it is, when a phone can't be found over Wi-Fi.
63
+ NETWORK_PROBLEMS = {"blocked": "network_blocked", "no_permission": "no_local_network", "vpn": "vpn"}
64
+
65
+ # The words a phone's connection shows (CLI `mobster wifi status`; the app's v2 Settings row uses the same).
66
+ ON_WIFI = "On Wi-Fi, encrypted"
67
+ ON_CABLE = "On cable"
68
+ UNREACHABLE = "Not reachable over Wi-Fi"
69
+ OFF = "Wi-Fi off"
70
+
71
+
72
+ def problem(code, name=None):
73
+ """{"code", "message", "fix"} for a problem code, with the phone's name filled in."""
74
+ message, fix = PROBLEMS[code]
75
+ who = name or "your iPhone"
76
+ return {"code": code, "message": _sentence(message.format(name=who)), "fix": _sentence(fix.format(name=who))}
77
+
78
+
79
+ def _sentence(text):
80
+ """A sentence that starts with the phone's name ("your iPhone") starts with a capital."""
81
+ return text[:1].upper() + text[1:] if text else text
@@ -0,0 +1,83 @@
1
+ """The runner's test plan for a launch over Wi-Fi (SPEC §3.7 item 3)."""
2
+
3
+ import os
4
+ import plistlib
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ from ..wda.patches import xctestrun_targets as targets
9
+ from .relay import check_target, require_tunnel
10
+
11
+ FOLDER = "mobster-wifi"
12
+ TESTROOT = "__TESTROOT__"
13
+
14
+
15
+ def source_plan(products):
16
+ """The built ``.xctestrun`` in ``products`` (the newest, when a rebuild left more than one), or LookupError."""
17
+ plans = sorted(Path(products).glob("*.xctestrun"), key=lambda path: (path.stat().st_mtime, path.name))
18
+ if not plans:
19
+ raise LookupError("Build Mobster's helper for this iPhone first.")
20
+ return plans[-1]
21
+
22
+
23
+ def use_ips(plan):
24
+ """Every USE_IP the plan sets, in order (EnvironmentVariables, then TestingEnvironmentVariables)."""
25
+ values = []
26
+ for target in targets(plan):
27
+ for section in ("EnvironmentVariables", "TestingEnvironmentVariables"):
28
+ env = target.get(section)
29
+ if isinstance(env, dict) and "USE_IP" in env:
30
+ values.append(env["USE_IP"])
31
+ return values
32
+
33
+
34
+ def _spell_out(value, root):
35
+ if isinstance(value, str):
36
+ return value.replace(TESTROOT, root)
37
+ if isinstance(value, list):
38
+ return [_spell_out(item, root) for item in value]
39
+ if isinstance(value, dict):
40
+ return {key: _spell_out(item, root) for key, item in value.items()}
41
+ return value
42
+
43
+
44
+ def prepare(plan, products, address):
45
+ address = check_target(address)
46
+ copy = _spell_out(plan, str(Path(products).resolve()))
47
+ found = targets(copy)
48
+ if not found:
49
+ raise LookupError("Mobster's helper build has no test in it. Build it again in Setup.")
50
+ for target in found:
51
+ env = target.get("EnvironmentVariables")
52
+ if not isinstance(env, dict):
53
+ env = target["EnvironmentVariables"] = {}
54
+ env["USE_IP"] = address
55
+ testing = target.get("TestingEnvironmentVariables")
56
+ if isinstance(testing, dict) and "USE_IP" in testing:
57
+ testing["USE_IP"] = address
58
+ if not use_ips(copy) or any(value != address for value in use_ips(copy)):
59
+ raise ValueError("The helper's address couldn't be set.")
60
+ return copy
61
+
62
+
63
+ # Its own folder, never beside the built plan: the cable's scheme-based launch finds that one by itself.
64
+ def write(products, folder, address, *, through=None):
65
+ address = require_tunnel(address, through)
66
+ source = source_plan(products)
67
+ with open(source, "rb") as stream:
68
+ plan = plistlib.load(stream)
69
+ copy = prepare(plan, products, address)
70
+ folder = Path(folder)
71
+ folder.mkdir(parents=True, exist_ok=True)
72
+ os.chmod(folder, 0o700)
73
+ target = folder / source.name
74
+ handle, temporary = tempfile.mkstemp(dir=folder, prefix=".xctestrun-")
75
+ try:
76
+ with os.fdopen(handle, "wb") as stream:
77
+ os.fchmod(stream.fileno(), 0o600)
78
+ plistlib.dump(copy, stream)
79
+ os.replace(temporary, target)
80
+ except BaseException:
81
+ Path(temporary).unlink(missing_ok=True)
82
+ raise
83
+ return target
@@ -0,0 +1,59 @@
1
+ """Published workflow tool names and schemas shared by REST, MCP and Shortcuts."""
2
+
3
+ import re
4
+
5
+ TOOL_PREFIX = "workflow_"
6
+ TOOL_WAIT_MAX = 45 # seconds a tool call or a REST run waits for the run before it answers "running"
7
+ TOOL_WAIT_DEFAULT = 30
8
+ INPUT_TYPES = {"string": "string", "number": "number", "boolean": "boolean", "list": "array", "object": "object"}
9
+ # Names under /api/workflows/ that are routes of their own: a workflow named like one takes its id in its slug.
10
+ RESERVED_SLUGS = frozenset({"approvals", "draft", "evaluate", "node_types", "preview", "templates", "tools",
11
+ "validate"})
12
+
13
+
14
+ def tools(workflows):
15
+ """Each published ``asTool`` workflow as a tool; slugs go oldest first, so a new one never renames an older one."""
16
+ taken, out = set(), []
17
+ for workflow, live in sorted(workflows, key=lambda pair: pair[0].get("createdAt") or 0):
18
+ if not workflow.get("asTool") or not workflow.get("published"):
19
+ continue
20
+ slug = re.sub(r"[^a-z0-9]+", "_", str(workflow.get("name") or "").casefold()).strip("_")[:40] or "run"
21
+ if slug in taken or slug in RESERVED_SLUGS or re.fullmatch(r"[a-f0-9]{12}", slug):
22
+ slug = f"{slug}_{str(workflow.get('id'))[:4]}"
23
+ taken.add(slug)
24
+ steps = " ".join(str(workflow.get("goal") or "").split())
25
+ tool = {"name": TOOL_PREFIX + slug, "slug": slug, "path": f"/v1/workflows/{slug}",
26
+ "workflowId": workflow["id"], "workflowName": workflow.get("name"),
27
+ "description": (f"Run the user's workflow “{workflow.get('name')}” on their iPhone"
28
+ + (f": {steps}" if steps else "."))[:220],
29
+ "inputSchema": tool_schema(workflow)}
30
+ result = next((n for n in (live or {}).get("nodes") or ()
31
+ if n.get("type") == "output" and (n.get("config") or {}).get("schema")), None)
32
+ if result is not None:
33
+ tool["outputSchema"] = result["config"]["schema"]
34
+ out.append(tool)
35
+ return out
36
+
37
+
38
+ def tool_schema(workflow):
39
+ """A workflow tool's input: its inputs and blanks (typed as declared), each by name, and wait_seconds."""
40
+ properties, required = {}, []
41
+ for blank in workflow.get("blanks") or []:
42
+ kind = INPUT_TYPES.get(blank.get("type") or "string", "string")
43
+ prop = {"type": kind, **({"maxLength": 300} if kind == "string" else {})}
44
+ if blank.get("default") is not None:
45
+ prop["description"] = f"default {blank['default']}"
46
+ else:
47
+ required.append(blank["name"])
48
+ properties[blank["name"]] = prop
49
+ properties["wait_seconds"] = {"type": "integer", "minimum": 0, "maximum": TOOL_WAIT_MAX,
50
+ "description": f"how long to wait for it to finish before returning (default "
51
+ f"{TOOL_WAIT_DEFAULT})"}
52
+ return {"type": "object", "properties": properties, "required": required, "additionalProperties": False}
53
+
54
+
55
+ def _as_tool(value):
56
+ """``asTool``: the workflow is its own tool for AI clients (MCP), typed from its inputs. Off unless turned on."""
57
+ if type(value) is not bool:
58
+ raise ValueError("asTool is true or false")
59
+ return value
@@ -0,0 +1,2 @@
1
+ """Saved workflows: the records and their edits, dispatch and runs, the schedule, triggers, history, health, step cards,
2
+ blanks and the brief step."""
@@ -0,0 +1,201 @@
1
+ """The brief: one result put together from the tasks a workflow ran, and the Morning brief job built on it.
2
+
3
+ A workflow's ``brief`` step (batch.register_step, kind ``brief``) runs after its tasks, with no phone and no model. It
4
+ takes the result of every task this round finished, in order, and makes one finished run of them in the workflow's
5
+ conversation (Runtime.compose_result): their result blocks, each kept as it was (a review queue, a media list, a
6
+ table, notes), and a task with an answer but no blocks as a notes card titled with its words. That run is the
7
+ workflow's last result, so Home's job card, a PDF (POST /api/runs/{id}/artifact) and a share link work on it as on any
8
+ task. It then says "Your brief is ready" as a Mac notification (``batch_notification`` on the workflows topic).
9
+
10
+ - **Nothing new acts.** The brief only reads results that are already stored. A review queue's Approve on the brief
11
+ is the Approve of the task that found the item (Runtime.decide_block_item follows ``summary.brief.sources``): the
12
+ same stored item, the same one follow-up task per item, so approving in both places never sends twice.
13
+ - **Fails closed.** A brief with no finished task before it stops the workflow and says why.
14
+ - **Screen text is data.** The cards are the tasks' validated blocks (result_blocks.validate); nothing from them reaches
15
+ an instruction.
16
+
17
+ ``morning_brief(niche, at, timezone)`` is the Morning brief job (POST /api/workflows/templates/morning-brief): every
18
+ morning, your DMs that need you, what's trending in your niche and ideas from your saved posts, on Instagram, in one
19
+ result. App packs (feature/app-packs) take its tasks over when they land; until then Mobster's agent runs them.
20
+ """
21
+
22
+ import re
23
+
24
+ from .. import result_blocks
25
+ from ..api_errors import APIError
26
+ from ..graph.catalog import app_missing
27
+ from . import records as batch
28
+ from .health import run_outcome
29
+
30
+ KIND = "brief"
31
+ TITLE_CHARS = 80
32
+ NOTES_CHARS = 6000
33
+
34
+ MORNING_BRIEF = "morning-brief"
35
+ INSTAGRAM = "com.burbn.instagram"
36
+ NICHE_CHARS = 60
37
+ DM_TASK = ("Go through my Instagram DMs and sort them into a review queue: needs a reply, can wait, and spam. Draft a "
38
+ "one-line reply for each one that needs a reply. Don’t send anything.")
39
+ TRENDS_TASK = ("Watch 20 Reels about {niche} and tell me what’s trending: the formats, sounds and topics that keep "
40
+ "coming up, with an example of each. Don’t like, comment or follow.")
41
+ IDEAS_TASK = ("Go through my newest saved posts on Instagram and give me ideas I could try next, each with the saved "
42
+ "post it comes from. Don’t like, comment or save anything.")
43
+
44
+
45
+ def validate(step, runtime):
46
+ """The step as saved: an optional ``title`` for the result (default: the workflow's name)."""
47
+ if set(step) - {"id", "kind", "title"}:
48
+ raise ValueError("A brief step has only a title")
49
+ title = step.get("title")
50
+ if title is not None and (not isinstance(title, str) or not 0 < len(title.strip()) <= TITLE_CHARS):
51
+ raise ValueError(f"A brief's title is 1 to {TITLE_CHARS} characters")
52
+ return {"title": " ".join(title.split())} if title else {}
53
+
54
+
55
+ def _summary(run):
56
+ return run.summary if isinstance(getattr(run, "summary", None), dict) else {}
57
+
58
+
59
+ def _short(label, limit=60):
60
+ words = " ".join(str(label).split())
61
+ first = re.split(r"(?<=[.:!?])\s", words, maxsplit=1)[0]
62
+ return first if len(first) <= limit else first[:limit - 1].rstrip() + "…"
63
+
64
+
65
+ def compose(sources):
66
+ """(envelope, mapping, answer) for ``sources``: [(label, run)]."""
67
+ blocks, mapping = [], {}
68
+ for index, (label, run) in enumerate(sources, start=1):
69
+ summary = _summary(run)
70
+ envelope = summary.get("blocks") if isinstance(summary.get("blocks"), dict) else None
71
+ own = [dict(block) for block in (envelope or {}).get("blocks") or () if isinstance(block, dict)]
72
+ if own:
73
+ for block in own:
74
+ new_id = f"s{index}-{block.get('id') or 'b'}"[:64]
75
+ mapping[new_id] = [run.id, block.get("id")]
76
+ blocks.append({**block, "id": new_id, "title": block.get("title") or _short(label)})
77
+ continue
78
+ text = summary.get("full_answer") or summary.get("data") or summary.get("answer")
79
+ if isinstance(text, str) and text.strip():
80
+ blocks.append({"type": "markdown", "id": f"s{index}-notes", "title": _short(label),
81
+ "text": text.strip()[:NOTES_CHARS]})
82
+ envelope, _problems = result_blocks.validate(blocks[:result_blocks.MAX_BLOCKS])
83
+ kept = {block["id"] for block in (envelope or {}).get("blocks") or ()}
84
+ mapping = {key: value for key, value in mapping.items() if key in kept}
85
+ return envelope, mapping, answer_of(envelope)
86
+
87
+
88
+ def answer_of(envelope):
89
+ """The brief's one line: what it holds, counted from its cards ("6 drafts to approve, 20 videos, 3 notes.")."""
90
+ parts = []
91
+ for block in (envelope or {}).get("blocks") or ():
92
+ kind = block.get("type")
93
+ if kind == "review_queue":
94
+ drafts = sum(1 for item in block.get("items") or () if item.get("draft"))
95
+ items = len(block.get("items") or ())
96
+ parts.append(f"{drafts} {'draft' if drafts == 1 else 'drafts'} to approve" if drafts
97
+ else f"{items} {'conversation' if items == 1 else 'conversations'} sorted")
98
+ elif kind == "media_list":
99
+ n = len(block.get("items") or ())
100
+ parts.append(f"{n} {'video' if n == 1 else 'videos'}")
101
+ elif kind == "table":
102
+ n = len(block.get("rows") or ())
103
+ parts.append(f"{n} {'row' if n == 1 else 'rows'}")
104
+ elif kind == "checklist":
105
+ n = len(block.get("items") or ())
106
+ parts.append(f"{n} {'check' if n == 1 else 'checks'}")
107
+ notes = sum(1 for block in (envelope or {}).get("blocks") or () if block.get("type") == "markdown")
108
+ if notes:
109
+ parts.append(f"{notes} {'note' if notes == 1 else 'notes'}")
110
+ return ("Your brief: " + ", ".join(parts) + ".") if parts else "Your brief is ready."
111
+
112
+
113
+ def run(going, step, values, *, sources, key=None):
114
+ if not sources:
115
+ raise batch.Stop("failed", "There's nothing to put in the brief: no task before it finished.")
116
+ envelope, mapping, answer = compose(sources)
117
+ if envelope is None:
118
+ raise batch.Stop("failed", "The tasks before the brief found nothing to put in it.")
119
+ runtime, workflow, record = going.runtime, going.workflow, going.record
120
+ title = step.get("title") or workflow.get("name") or "Brief"
121
+ first = sources[0][1]
122
+ # Done only when each task it holds is done (proof on the screen, or you marked it OK); its proof is theirs.
123
+ verified = all(run_outcome(run) == "done" for _, run in sources)
124
+ proof = [item for _, run in sources for item in (_summary(run).get("proof") or ())[:2]
125
+ if isinstance(item, dict)][:6]
126
+ summary = {"event": "result", "status": "completed", "outcome": "done" if verified else "check",
127
+ "engine": None, "answer": answer, "reason": None if verified else
128
+ "Mobster put the brief together, but one of its tasks finished without proof on the screen. Check it.",
129
+ "independently_verified": verified, "actions": 0, "decisions": 0, "elapsed_ms": 0, "model": None,
130
+ "full_answer": "\n\n".join(
131
+ f"**{_short(label, 120)}**\n"
132
+ f"{(_summary(run).get('full_answer') or _summary(run).get('answer') or '').strip()}"
133
+ for label, run in sources)[:12000],
134
+ "blocks": envelope, "proof": proof if verified else [],
135
+ "brief": {"title": title, "runs": [run.id for _, run in sources], "sources": mapping,
136
+ "spentUsd": round(going.spent, 6), "seconds": round(going.clock() - going.started, 1),
137
+ "workflowId": workflow.get("id")}}
138
+ extras = {"threadId": record["threadId"]} if record.get("threadId") else {}
139
+ origin = getattr(first, "origin", None) or "workflow"
140
+ key = key or f"{record['key']}:b{record['id']}:{KIND}"[:128]
141
+ made = runtime.compose_result(app=dict(first.app), goal=f"Put together {title}", summary=summary,
142
+ idempotency_key=key, extras=extras, origin=origin,
143
+ device=(getattr(first, "device_id", None), getattr(first, "device_name", None)))
144
+ # The workflow's last result is the brief: Home's card, its PDF and the run's Needs you read it.
145
+ going.last = made
146
+ going._publish({"event": "batch_notification", "title": "Your brief is ready", "body": answer[:240],
147
+ "runId": made.id})
148
+ return {"answer": answer, "line": f"Put together {title}"}
149
+
150
+
151
+ def register():
152
+ if KIND not in batch.step_kinds():
153
+ batch.register_step(KIND, validate, run)
154
+
155
+
156
+ def clean_niche(value):
157
+ """The niche as the trends task says it: plain words, no braces (a blank's mark), 1 to NICHE_CHARS characters."""
158
+ if not isinstance(value, str):
159
+ raise ValueError("Say your niche, like home cooking or streetwear")
160
+ niche = " ".join(re.sub(r"[{}\[\]<>]", " ", value).split())
161
+ if not 1 <= len(niche) <= NICHE_CHARS:
162
+ raise ValueError(f"Say your niche in 1 to {NICHE_CHARS} characters, like home cooking")
163
+ return niche
164
+
165
+
166
+ def morning_brief(niche, at="08:00", timezone=None, app_id=INSTAGRAM):
167
+ niche = clean_niche(niche)
168
+ match = re.fullmatch(r"([01]?\d|2[0-3]):([0-5]\d)", at or "")
169
+ if not match:
170
+ raise ValueError("Choose a time like 08:00")
171
+ hour, minute = int(match[1]), int(match[2])
172
+ body = {"name": "Morning brief", "appId": app_id, "appName": "Instagram",
173
+ "cron": f"{minute} {hour} * * *", "enabled": True, "sameConversation": True,
174
+ "produces": ["review_queue", "media_list", "markdown"],
175
+ "steps": [{"id": "dms", "kind": "task", "text": DM_TASK, "appId": app_id},
176
+ {"id": "trends", "kind": "task", "text": TRENDS_TASK.format(niche=niche), "appId": app_id},
177
+ {"id": "ideas", "kind": "task", "text": IDEAS_TASK, "appId": app_id},
178
+ {"id": "brief", "kind": KIND}],
179
+ "limits": {"maxSpendUsd": 2.0, "maxMinutes": 60}}
180
+ if timezone:
181
+ body["timezone"] = timezone
182
+ return body
183
+
184
+
185
+ TEMPLATES = {MORNING_BRIEF: morning_brief}
186
+
187
+
188
+ def create_from_template(workflows, name, body):
189
+ """POST /api/workflows/templates/{name}: the template's workflow, saved and scheduled (Workflows.create)."""
190
+ if name not in TEMPLATES:
191
+ raise APIError("There's no job template by that name.", 404, "template_not_found")
192
+ if not isinstance(body, dict) or set(body) - {"niche", "time", "timezone"}:
193
+ raise ValueError("A Morning brief takes niche, time (HH:MM) and timezone")
194
+ made = TEMPLATES[name](body.get("niche"), body.get("time") or "08:00", body.get("timezone"))
195
+ try:
196
+ return workflows.create(made)
197
+ except ValueError as error:
198
+ if str(error) == app_missing(INSTAGRAM):
199
+ raise ValueError("The Morning brief runs in Instagram, and Mobster doesn't see Instagram on your iPhone. "
200
+ "Install it or open it once, then set up the brief again.") from None
201
+ raise