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,394 @@
1
+ """accounts.json: the profiles a person runs (Settings › Profiles), each on one app and (optionally) one phone."""
2
+
3
+ import contextlib
4
+ import errno
5
+ import fcntl
6
+ import json
7
+ import os
8
+ import re
9
+ import tempfile
10
+ import threading
11
+ import time
12
+ import uuid
13
+ from pathlib import Path
14
+
15
+ from .. import secret_filter
16
+
17
+ STORE = "accounts.json"
18
+ STORE_LOCK = "accounts.lock"
19
+ LOCK_TIMEOUT = 10.0
20
+ MAX_ACCOUNTS = 200
21
+ MAX_LINKS = 10
22
+ MAX_VARIABLES = 20
23
+ NOTES_CHARS = 2000
24
+ SIGNATURE_CHARS = 300
25
+ VALUE_CHARS = 1000
26
+ LINK_LABEL_CHARS = 40
27
+ URL_CHARS = 500
28
+
29
+ ID = re.compile(r"[a-f0-9]{12}")
30
+ # Instagram, TikTok, X, Threads and LinkedIn vanity names: letters, digits, dots, underscores and hyphens.
31
+ HANDLE = re.compile(r"[A-Za-z0-9._-]{1,64}")
32
+ BUNDLE = re.compile(r"[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+")
33
+ VARIABLE = re.compile(r"[a-z][a-z0-9_]{0,31}")
34
+ # Names a task's placeholders already mean (context.fill): a variable can't take one.
35
+ RESERVED = frozenset({"handle", "account", "app", "signature", "link", "links", "notes"})
36
+ FIELDS = frozenset({"handle", "app", "device", "notes", "signature", "links", "variables", "region", "language",
37
+ "timezone"})
38
+ # What the phone should be set to for this profile (fleet_health.py checks it before a task): optional.
39
+ REGION = re.compile(r"[A-Za-z]{2}")
40
+ LANGUAGE = re.compile(r"[A-Za-z]{2,3}")
41
+ TIMEZONE = re.compile(r"[A-Za-z][A-Za-z0-9_+-]*(?:/[A-Za-z0-9_+-]+){0,2}")
42
+
43
+ NOT_FOUND = "No profile has that name. Add it in Settings › Profiles."
44
+ SECRET = "Profiles don't keep passwords, card numbers or API keys. Leave that value out."
45
+
46
+
47
+ class AccountNotFound(LookupError):
48
+ pass
49
+
50
+
51
+ class AmbiguousAccount(LookupError):
52
+ pass
53
+
54
+
55
+ def handle_of(value):
56
+ """``value`` as a stored handle: no ``@``, no surrounding space. ValueError when it isn't one."""
57
+ if not isinstance(value, str):
58
+ raise ValueError("handle is the profile's name on the app, such as @acme")
59
+ handle = value.strip().lstrip("@").strip()
60
+ if not HANDLE.fullmatch(handle):
61
+ raise ValueError("A handle is 1–64 letters, digits, dots, underscores or hyphens, such as @acme")
62
+ return handle
63
+
64
+
65
+ def display(account):
66
+ """"@acme on Instagram"."""
67
+ return f"@{account['handle']} on {account['app']['name']}"
68
+
69
+
70
+ def _text(value, name, limit):
71
+ """``name`` as a person reads it ("A signature"): the profile form shows the message as it is (P05-18)."""
72
+ if value is None:
73
+ return ""
74
+ if not isinstance(value, str):
75
+ raise ValueError(f"{name} is text of at most {limit:,} characters")
76
+ if len(value) > limit:
77
+ raise ValueError(f"{name} can be at most {limit:,} characters. Shorten it and try again.")
78
+ value = value.strip()
79
+ if value and looks_secret(value):
80
+ raise ValueError(SECRET)
81
+ return value
82
+
83
+
84
+ def looks_secret(text):
85
+ """secret_filter's password words, API keys, card numbers, SSNs, IBANs and an email with a password beside it."""
86
+ f = secret_filter
87
+ return (f.MASK in text or bool(f.KEYWORD.search(text)) or any(p.search(text) for p in (f.API_KEY, f.SSN, f.IBAN))
88
+ or bool(f._cards(text)) or bool(f.EMAIL_PASSWORD.search(text)))
89
+
90
+
91
+ def _app(value):
92
+ if not isinstance(value, dict) or set(value) - {"bundleId", "name"}:
93
+ raise ValueError("app is {bundleId, name}: the app this profile is on")
94
+ bundle, name = value.get("bundleId"), value.get("name")
95
+ if not isinstance(bundle, str) or not BUNDLE.fullmatch(bundle) or len(bundle) > 155:
96
+ raise ValueError("app.bundleId is the app's bundle id, such as com.burbn.instagram")
97
+ if not isinstance(name, str) or not 1 <= len(name.strip()) <= 80:
98
+ raise ValueError("app.name is the app's name, 1–80 characters")
99
+ return {"bundleId": bundle, "name": name.strip()}
100
+
101
+
102
+ def _expectation(value, name, pattern, hint, fold):
103
+ """An expected region, language or time zone: None to clear, else the value in its usual case."""
104
+ if value is None or value == "":
105
+ return None
106
+ if not isinstance(value, str) or not pattern.fullmatch(value.strip()) or len(value.strip()) > 64:
107
+ raise ValueError(f"{value!r:.40} isn't a {name} Mobster can use. Use {hint}, or leave it empty.")
108
+ return fold(value.strip())
109
+
110
+
111
+ def _timezone(value):
112
+ zone = _expectation(value, "timezone", TIMEZONE, "a time zone name such as America/Chicago", lambda v: v)
113
+ if zone is not None:
114
+ import zoneinfo
115
+ known = zoneinfo.available_timezones()
116
+ if known and zone not in known:
117
+ raise ValueError(f"{zone[:40]!r} isn't a time zone Mobster knows. Use a name such as America/Chicago, "
118
+ "or leave it empty.")
119
+ return zone
120
+
121
+
122
+ def _links(value):
123
+ if value is None:
124
+ return []
125
+ if not isinstance(value, list) or len(value) > MAX_LINKS:
126
+ raise ValueError(f"links is a list of at most {MAX_LINKS} {{label, url}}")
127
+ out, labels = [], set()
128
+ for link in value:
129
+ if not isinstance(link, dict) or set(link) - {"label", "url"}:
130
+ raise ValueError("Each link is {label, url}")
131
+ label = _text(link.get("label"), "A link's label", LINK_LABEL_CHARS)
132
+ url = _text(link.get("url"), "A link's url", URL_CHARS)
133
+ if not re.fullmatch(r"https?://[^\s]{1,490}", url):
134
+ raise ValueError("A link's url starts with https:// or http://")
135
+ if not label:
136
+ raise ValueError("Give each link a label, such as Shop")
137
+ if label.casefold() in labels:
138
+ raise ValueError(f"Two links are called {label}")
139
+ labels.add(label.casefold())
140
+ out.append({"label": label, "url": url})
141
+ return out
142
+
143
+
144
+ def _variables(value):
145
+ if value is None:
146
+ return {}
147
+ if not isinstance(value, dict) or len(value) > MAX_VARIABLES:
148
+ raise ValueError(f"variables is an object of at most {MAX_VARIABLES} names and values")
149
+ out = {}
150
+ for name, text in value.items():
151
+ if not isinstance(name, str) or not VARIABLE.fullmatch(name):
152
+ raise ValueError("A variable's name is lowercase letters, digits and underscores, such as caption")
153
+ if name in RESERVED:
154
+ raise ValueError(f"{name} is already a profile value. Choose another name.")
155
+ out[name] = _text(text, f"The variable {name}", VALUE_CHARS)
156
+ return out
157
+
158
+
159
+ def clean(body, partial=False):
160
+ """The validated fields of ``body`` (every field for a new profile; only those given for a change)."""
161
+ if not isinstance(body, dict) or set(body) - FIELDS:
162
+ raise ValueError("A profile has handle, app, device, notes, signature, links, variables, "
163
+ "region, language and timezone")
164
+ if not partial and ("handle" not in body or "app" not in body):
165
+ raise ValueError("Name the profile's handle and its app")
166
+ out = {}
167
+ if "handle" in body:
168
+ out["handle"] = handle_of(body["handle"])
169
+ if "app" in body:
170
+ out["app"] = _app(body["app"])
171
+ if "device" in body:
172
+ device = body["device"]
173
+ if device is not None and (not isinstance(device, str) or not 1 <= len(device.strip()) <= 128):
174
+ raise ValueError("device is the phone's id or name, or null for the default phone")
175
+ out["device"] = device.strip() if isinstance(device, str) else None
176
+ if "notes" in body or not partial:
177
+ out["notes"] = _text(body.get("notes"), "Notes", NOTES_CHARS)
178
+ if "signature" in body or not partial:
179
+ out["signature"] = _text(body.get("signature"), "A signature", SIGNATURE_CHARS)
180
+ if "links" in body or not partial:
181
+ out["links"] = _links(body.get("links"))
182
+ if "variables" in body or not partial:
183
+ out["variables"] = _variables(body.get("variables"))
184
+ if "region" in body:
185
+ out["region"] = _expectation(body["region"], "region", REGION, "a two-letter country code such as US",
186
+ str.upper)
187
+ if "language" in body:
188
+ out["language"] = _expectation(body["language"], "language", LANGUAGE, "a language code such as en",
189
+ str.lower)
190
+ if "timezone" in body:
191
+ out["timezone"] = _timezone(body["timezone"])
192
+ return out
193
+
194
+
195
+ def _valid(entry):
196
+ try:
197
+ return (isinstance(entry, dict) and ID.fullmatch(entry.get("id") or "") is not None
198
+ and HANDLE.fullmatch(entry.get("handle") or "") is not None and isinstance(entry.get("app"), dict)
199
+ and isinstance(entry["app"].get("bundleId"), str) and isinstance(entry["app"].get("name"), str))
200
+ except TypeError:
201
+ return False
202
+
203
+
204
+ class AccountStore:
205
+ """accounts.json in ``data_dir``."""
206
+
207
+ def __init__(self, data_dir):
208
+ self.data_dir = Path(data_dir)
209
+ self.path = self.data_dir / STORE
210
+ self.lock_path = self.data_dir / STORE_LOCK
211
+
212
+ def read(self):
213
+ try:
214
+ data = json.loads(self.path.read_text())
215
+ except (OSError, ValueError):
216
+ return {"version": 1, "accounts": []}
217
+ items = data.get("accounts") if isinstance(data, dict) else None
218
+ accounts = []
219
+ for entry in items if isinstance(items, list) else ():
220
+ if not _valid(entry):
221
+ continue
222
+ links = entry.get("links") if isinstance(entry.get("links"), list) else []
223
+ accounts.append({"id": entry["id"], "handle": entry["handle"],
224
+ "app": {"bundleId": entry["app"]["bundleId"], "name": entry["app"]["name"]},
225
+ "device": entry.get("device") if isinstance(entry.get("device"), str) else None,
226
+ "deviceName": (entry.get("deviceName") if isinstance(entry.get("deviceName"), str)
227
+ else None),
228
+ "notes": entry.get("notes") if isinstance(entry.get("notes"), str) else "",
229
+ "signature": entry.get("signature") if isinstance(entry.get("signature"), str) else "",
230
+ "links": [link for link in links if isinstance(link, dict)
231
+ and isinstance(link.get("label"), str) and isinstance(link.get("url"), str)],
232
+ "variables": {k: v for k, v in (entry.get("variables") or {}).items()
233
+ if isinstance(k, str) and isinstance(v, str)}
234
+ if isinstance(entry.get("variables"), dict) else {},
235
+ **{key: entry[key] for key in ("region", "language", "timezone")
236
+ if isinstance(entry.get(key), str) and entry[key]},
237
+ "createdAt": entry.get("createdAt") if isinstance(entry.get("createdAt"), int) else 0,
238
+ "updatedAt": entry.get("updatedAt") if isinstance(entry.get("updatedAt"), int) else 0})
239
+ return {"version": 1, "accounts": accounts}
240
+
241
+ def all(self):
242
+ return self.read()["accounts"]
243
+
244
+ def get(self, account_id):
245
+ """The stored account with this id, else None."""
246
+ return next((a for a in self.all() if a["id"] == account_id), None)
247
+
248
+ @contextlib.contextmanager
249
+ def edit(self):
250
+ self.data_dir.mkdir(parents=True, exist_ok=True)
251
+ fd = os.open(self.lock_path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
252
+ try:
253
+ end = time.monotonic() + LOCK_TIMEOUT
254
+ while True:
255
+ try:
256
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
257
+ break
258
+ except OSError as exc:
259
+ if exc.errno not in (errno.EWOULDBLOCK, errno.EAGAIN) or time.monotonic() >= end:
260
+ raise OSError("Another Mobster process is holding the profile list") from None
261
+ time.sleep(.05)
262
+ data = self.read()
263
+ before = json.dumps(data, sort_keys=True)
264
+ yield data
265
+ if json.dumps(data, sort_keys=True) != before:
266
+ self._write(data)
267
+ finally:
268
+ os.close(fd)
269
+
270
+ def _write(self, data):
271
+ fd, temporary = tempfile.mkstemp(prefix=".accounts.", suffix=".json", dir=self.data_dir)
272
+ try:
273
+ with os.fdopen(fd, "w", encoding="utf-8") as stream:
274
+ os.fchmod(stream.fileno(), 0o600)
275
+ stream.write(json.dumps(data, indent=1) + "\n")
276
+ os.replace(temporary, self.path)
277
+ except BaseException:
278
+ with contextlib.suppress(OSError):
279
+ os.unlink(temporary)
280
+ raise
281
+
282
+ @staticmethod
283
+ def _taken(accounts, handle, bundle, other=None):
284
+ return any(a["handle"].casefold() == handle.casefold() and a["app"]["bundleId"] == bundle
285
+ and a["id"] != other for a in accounts)
286
+
287
+ def add(self, body, device=None):
288
+ """A new account from ``body`` (store.clean)."""
289
+ fields = clean(body)
290
+ now = round(time.time() * 1000)
291
+ with self.edit() as data:
292
+ if len(data["accounts"]) >= MAX_ACCOUNTS:
293
+ raise ValueError(f"Mobster keeps up to {MAX_ACCOUNTS} profiles. Remove one you no longer run.")
294
+ if self._taken(data["accounts"], fields["handle"], fields["app"]["bundleId"]):
295
+ raise ValueError(f"@{fields['handle']} on {fields['app']['name']} is already in your profiles")
296
+ account = {"id": uuid.uuid4().hex[:12], **fields, "device": device[0] if device else None,
297
+ "deviceName": device[1] if device else None, "createdAt": now, "updatedAt": now}
298
+ data["accounts"].append(account)
299
+ return dict(account)
300
+
301
+ def update(self, account_id, body, device=None):
302
+ """The account with ``body``'s fields changed."""
303
+ fields = clean(body, partial=True)
304
+ with self.edit() as data:
305
+ account = next((a for a in data["accounts"] if a["id"] == account_id), None)
306
+ if account is None:
307
+ raise AccountNotFound(NOT_FOUND)
308
+ merged = {**account, **fields}
309
+ if self._taken(data["accounts"], merged["handle"], merged["app"]["bundleId"], other=account_id):
310
+ raise ValueError(f"@{merged['handle']} on {merged['app']['name']} is already in your profiles")
311
+ if "device" in fields:
312
+ merged["device"], merged["deviceName"] = (device if device else (None, None))
313
+ merged["updatedAt"] = round(time.time() * 1000)
314
+ account.clear()
315
+ account.update(merged)
316
+ return dict(account)
317
+
318
+ def remove(self, account_id):
319
+ with self.edit() as data:
320
+ before = len(data["accounts"])
321
+ data["accounts"] = [a for a in data["accounts"] if a["id"] != account_id]
322
+ if len(data["accounts"]) == before:
323
+ raise AccountNotFound(NOT_FOUND)
324
+
325
+ def forget_device(self, device_id):
326
+ """Phones Mobster forgets leave their accounts on the default phone."""
327
+ with self.edit() as data:
328
+ for account in data["accounts"]:
329
+ if account.get("device") == device_id:
330
+ account["device"], account["deviceName"] = None, None
331
+
332
+
333
+ ON = re.compile(r"^\s*@?(?P<handle>[A-Za-z0-9._-]{1,64})\s+on\s+(?P<app>.{1,80}?)\s*$", re.I)
334
+
335
+
336
+ def resolve(accounts, value, bundle=None):
337
+ """The account ``value`` names: its id, "@acme", "acme", or "@acme on Instagram"."""
338
+ if not isinstance(value, str) or not 1 <= len(value.strip()) <= 160:
339
+ raise ValueError("account is a profile's id or its handle, such as @acme")
340
+ text = value.strip()
341
+ if ID.fullmatch(text):
342
+ found = next((a for a in accounts if a["id"] == text), None)
343
+ if found is not None:
344
+ return found
345
+ on = ON.match(text)
346
+ app_name = None
347
+ if on:
348
+ text, app_name = on.group("handle"), on.group("app").casefold()
349
+ handle = text.lstrip("@").casefold()
350
+ matches = [a for a in accounts if a["handle"].casefold() == handle]
351
+ if app_name is not None:
352
+ matches = [a for a in matches if a["app"]["name"].casefold() == app_name]
353
+ if len(matches) > 1 and bundle:
354
+ matches = [a for a in matches if a["app"]["bundleId"] == bundle] or matches
355
+ if not matches:
356
+ raise AccountNotFound(NOT_FOUND)
357
+ if len(matches) > 1:
358
+ apps = " and ".join(sorted(a["app"]["name"] for a in matches))
359
+ raise AmbiguousAccount(f"@{matches[0]['handle']} is on {apps}. Say which one, such as "
360
+ f"\"@{matches[0]['handle']} on {matches[0]['app']['name']}\".")
361
+ return matches[0]
362
+
363
+
364
+ MENTION = re.compile(r"(?<![\w.@/])@([A-Za-z0-9._-]{1,64})")
365
+
366
+
367
+ def mentioned(accounts, text, bundle=None):
368
+ """The one account a message names with an @handle ("Reply to my comments as @acme"), or None when it names
369
+ none of them, or more than one (then the person picks it in the composer)."""
370
+ found = {}
371
+ for match in MENTION.finditer(text or ""):
372
+ handle = match.group(1).rstrip(".").casefold()
373
+ for account in accounts:
374
+ if account["handle"].casefold() == handle and (bundle is None or account["app"]["bundleId"] == bundle):
375
+ found[account["id"]] = account
376
+ return next(iter(found.values())) if len(found) == 1 else None
377
+
378
+
379
+ _stores = {}
380
+ _stores_lock = threading.Lock()
381
+
382
+
383
+ def store_for(runtime):
384
+ """The AccountStore beside ``runtime``'s journal (as its replays, loops and frames), else in its data folder
385
+ (one store per folder)."""
386
+ from ..paths import user_data_dir
387
+ config = getattr(runtime, "config", None)
388
+ state_db = getattr(config, "state_db", None)
389
+ folder = Path(state_db).parent if state_db else Path(getattr(config, "data_dir", None) or user_data_dir())
390
+ with _stores_lock:
391
+ store = _stores.get(folder)
392
+ if store is None:
393
+ store = _stores[folder] = AccountStore(folder)
394
+ return store
@@ -0,0 +1,123 @@
1
+ """Seams between the agent loop (frontier), the phone guard (lockscreen) and skills."""
2
+ from dataclasses import dataclass
3
+ from typing import Callable, Optional, Protocol
4
+
5
+ SKILL_IMAGES = 2
6
+ MASK = "••••••"
7
+ # A wait for the user this long (approvals, hand-offs) or longer is followed by check(cause="resume").
8
+ RESUME_AFTER_SECONDS = 20
9
+ # The history line the model sees after the guard unlocked the phone (never a frame of the keypad).
10
+ UNLOCKED_LINE = "Mobster unlocked the iPhone"
11
+
12
+ # Why the phone guard stopped a run; none carries a passcode, a code or a digit count.
13
+ STOP_CODES = ("phone_locked", "unlock_declined", "passcode_failed", "face_id", "app_locked", "apple_confirmation",
14
+ "unplugged", "keychain_locked", "blocked_app", "system_dialog")
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class GuardVerdict:
19
+ state: str # "ready" | "unlocked" | "stop"
20
+ code: str = "" # when stop: one of STOP_CODES
21
+ message: str = "" # one plain sentence for the user; never a passcode, a code, or a digit count
22
+
23
+
24
+ class PhoneGuard(Protocol):
25
+ def check(self, driver, *, cause: str) -> GuardVerdict: ...
26
+ # cause: "preflight" | "launch_failed" | "lock_suspected" | "resume" | "sheet_suspected"
27
+ def attached(self) -> Optional[bool]: ... # False = USB detached (fail fast); None = unknown
28
+ def allows_app(self, bundle_id: str) -> bool: ... # C7 stretch; implementations without it return True
29
+ def finish(self, driver) -> None: ... # run ended: relock if this guard unlocked and relock is on
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Prepared:
34
+ title: Optional[str] # approval question, or None when no approval is needed; never contains a secret
35
+ state: object = None # skill-private (e.g. the code, held in memory only)
36
+ remember: Optional[dict] = None # the approval's "Don't ask again" (run.Run.request_approval), or None
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class SkillResult:
41
+ feedback: str # what the model is told next turn (no secrets)
42
+ changed: bool = False
43
+ secrets: tuple = () # strings frontier masks ("••••••") in every later row, note, history line and answer
44
+ secret_rects: tuple = () # (x, y, w, h) screen fractions frontier blurs in screenshots until that field changes
45
+ stop: Optional[GuardVerdict] = None
46
+ # Seconds the skill waited for the person, which never count against the task's time.
47
+ waited: float = 0.0
48
+ status: Optional[str] = None
49
+ # Shown to the model once, on its next turn, before the screen; only a run that sends screenshots sends them.
50
+ images: tuple = ()
51
+
52
+
53
+ @dataclass
54
+ class SkillContext:
55
+ driver: object
56
+ request: str
57
+ app_bundle: Optional[str]
58
+ emit: Callable[[dict], None]
59
+ guard: Optional[PhoneGuard] = None
60
+ approve: Optional[Callable[[dict], str]] = None # the frontier's approver, or None; replayed commit steps only
61
+ clarify: Optional[Callable[[dict], str]] = None # run.request_approval limited to kind "clarify"; others raise
62
+ run: Optional[object] = None # the run's harness_api.RunContext, or None (benchmarks, tests)
63
+ # A skill that needs the user's OK returns Prepared from prepare(); with no approver it never runs.
64
+
65
+
66
+ class Skill(Protocol):
67
+ op: str # e.g. "USE_CODE", "READ_NOTIFICATIONS"; must not clash with frontier.OPERATIONS
68
+ prompt: str # one "- OP ...: ..." line appended to frontier.SYSTEM's action list
69
+ needs_target: bool # True: the first action targets an element id (USE_CODE: the code field)
70
+ # interactive_only offers the tool only in a run a person watches.
71
+ def available(self, ctx: SkillContext) -> bool: ...
72
+ def prepare(self, ctx: SkillContext, act: dict, snapshot) -> Prepared: ... # may navigate; no writes
73
+ def perform(self, ctx: SkillContext, prepared: Prepared, act: dict, snapshot) -> SkillResult: ...
74
+
75
+
76
+ READY = GuardVerdict("ready")
77
+
78
+
79
+ def mask(text, secrets):
80
+ """``text`` with every secret replaced by MASK (longest first, so a code inside a longer one goes too)."""
81
+ if not text or not secrets or not isinstance(text, str):
82
+ return text
83
+ for secret in sorted(secrets, key=len, reverse=True):
84
+ if secret:
85
+ text = text.replace(secret, MASK)
86
+ return text
87
+
88
+
89
+ def safe_check(guard, driver, cause):
90
+ """``guard.check(driver, cause=cause)``, or None without a guard; a guard that raises is a stop, never a go."""
91
+ if guard is None:
92
+ return None
93
+ try:
94
+ verdict = guard.check(driver, cause=cause)
95
+ if isinstance(verdict, GuardVerdict) and verdict.state in ("ready", "unlocked", "stop"):
96
+ return verdict
97
+ except Exception:
98
+ pass
99
+ return GuardVerdict("stop", "phone_locked", "Mobster couldn't check whether your iPhone is unlocked, so it "
100
+ "stopped.")
101
+
102
+
103
+ def safe_attached(guard):
104
+ """``guard.attached()``: False only when the guard knows the phone was unplugged."""
105
+ if guard is None:
106
+ return None
107
+ try:
108
+ return guard.attached()
109
+ except Exception:
110
+ return None
111
+
112
+
113
+ def safe_allows(guard, bundle_id):
114
+ """``guard.allows_app(bundle_id)``; True without a guard or for a guard without the C7 list."""
115
+ if guard is None or not bundle_id:
116
+ return True
117
+ allows = getattr(guard, "allows_app", None)
118
+ if not callable(allows):
119
+ return True
120
+ try:
121
+ return allows(bundle_id) is True
122
+ except Exception:
123
+ return False
@@ -0,0 +1,7 @@
1
+ """Bring your own plan: Mobster's tasks on the person's coding agent and its subscription."""
2
+
3
+ AGENT_ENGINES = ("codex", "claude_code", "gemini")
4
+
5
+
6
+ def is_agent(engine):
7
+ return engine in AGENT_ENGINES
@@ -0,0 +1,96 @@
1
+ """The coding agents Mobster can run a task on, one instance each per process (so their status is cached)."""
2
+
3
+ import threading
4
+
5
+ from .claude_code import ClaudeCode
6
+ from .codex import Codex
7
+ from .gemini import Gemini
8
+
9
+ CLASSES = {"codex": Codex, "claude_code": ClaudeCode, "gemini": Gemini}
10
+ _instances = {}
11
+ _lock = threading.Lock()
12
+
13
+
14
+ def ids():
15
+ return tuple(CLASSES)
16
+
17
+
18
+ def get(agent_id):
19
+ """The agent for ``agent_id`` (codex, claude_code ...). KeyError for another."""
20
+ cls = CLASSES[agent_id]
21
+ with _lock:
22
+ if agent_id not in _instances:
23
+ _instances[agent_id] = cls()
24
+ return _instances[agent_id]
25
+
26
+
27
+ def statuses(max_age=60.0):
28
+ """{agent id: status} for every agent (cli.CliAgent.status), read in parallel."""
29
+ out, threads = {}, []
30
+
31
+ def read(agent_id):
32
+ out[agent_id] = get(agent_id).status(max_age=max_age)
33
+ for agent_id in CLASSES:
34
+ thread = threading.Thread(target=read, args=(agent_id,), daemon=True)
35
+ thread.start()
36
+ threads.append(thread)
37
+ for thread in threads:
38
+ thread.join(15)
39
+ return {agent_id: out.get(agent_id) or {"id": agent_id, "ready": False, "installed": False}
40
+ for agent_id in CLASSES}
41
+
42
+
43
+ def ready(env=None):
44
+ """The id of the coding agent a task that needs a model runs on when Mobster's own agent has no key."""
45
+ import os
46
+ names = {name for name, state in snapshot(max_age=60, wait=True).items() if state.get("ready")}
47
+ preferred = str((os.environ if env is None else env).get("MOBSTER_PREFERRED_AGENT", "")).strip()
48
+ return next((name for name in (preferred, *CLASSES) if name in names and name in CLASSES), None)
49
+
50
+
51
+ def reset_for_tests():
52
+ with _lock:
53
+ _instances.clear()
54
+ with _snapshot_lock:
55
+ _snapshot.update(value=None, at=0.0, refreshing=False, generation=_snapshot["generation"] + 1)
56
+
57
+
58
+ _snapshot = {"value": None, "at": 0.0, "refreshing": False, "generation": 0}
59
+ _snapshot_lock = threading.Lock()
60
+
61
+
62
+ def snapshot(max_age=60.0, wait=False):
63
+ """The agents' statuses from the last check, for GET /api/status (polled often)."""
64
+ import time
65
+ with _snapshot_lock:
66
+ value, at, busy = _snapshot["value"], _snapshot["at"], _snapshot["refreshing"]
67
+ generation = _snapshot["generation"]
68
+ stale = value is None or time.monotonic() - at >= max_age
69
+ if stale and not busy and not wait:
70
+ _snapshot["refreshing"] = True
71
+ if stale and wait:
72
+ fresh = statuses(max_age=max_age)
73
+ with _snapshot_lock:
74
+ _snapshot.update(value=fresh, at=time.monotonic())
75
+ return public(fresh)
76
+ if stale and not busy:
77
+ def refresh():
78
+ try:
79
+ fresh = statuses(max_age=max_age)
80
+ with _snapshot_lock:
81
+ # A check begun before a reset (the agents' paths changed) is dropped, not kept.
82
+ if _snapshot["generation"] == generation:
83
+ _snapshot.update(value=fresh, at=time.monotonic())
84
+ finally:
85
+ with _snapshot_lock:
86
+ if _snapshot["generation"] == generation:
87
+ _snapshot["refreshing"] = False
88
+ threading.Thread(target=refresh, daemon=True, name="mobster-agents-status").start()
89
+ return public(value or {})
90
+
91
+
92
+ def public(states):
93
+ """What the app shows of each agent's status: never its path's home folder beyond the file name."""
94
+ keep = ("id", "name", "plan", "vendor", "installed", "signedIn", "ready", "version", "account", "billing", "hint",
95
+ "installUrl", "installCommand", "signInCommand")
96
+ return {agent_id: {key: state.get(key) for key in keep} for agent_id, state in (states or {}).items()}