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
mobile_agent/keys.py ADDED
@@ -0,0 +1,395 @@
1
+ """Bring your own keys for Mobster's agent and the helper model; a key never appears in a response, event or log."""
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import urllib.error
7
+ import urllib.request
8
+ from urllib.parse import urlsplit
9
+
10
+ from .env_secrets import update_env_file
11
+
12
+ OPENAI_URL = "https://api.openai.com/v1"
13
+ # The model Smart runs on with an OpenAI key (engines.OPENAI_SMART_MODEL); the key test checks this key can use it.
14
+ SMART_MODEL = "gpt-5.6-sol"
15
+ OPENAI_BILLING_URL = "https://platform.openai.com/settings/organization/billing/overview"
16
+ ANTHROPIC_URL = "https://api.anthropic.com/v1"
17
+ ANTHROPIC_BILLING_URL = "https://platform.claude.com/settings/billing"
18
+ KEY_PATTERN = re.compile(r"[\x21-\x7e]{16,400}") # printable, no spaces
19
+ MODEL_PATTERN = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/@-]{0,127}")
20
+ # The same rules the Vertex client enforces (gemini.VertexHTTP).
21
+ PROJECT_PATTERN = re.compile(r"[a-z][a-z0-9-]{4,61}[a-z0-9]|[0-9]{6,20}")
22
+ LOCATION_PATTERN = re.compile(r"global|[a-z]+-[a-z]+[0-9]")
23
+
24
+ # Every value these presets need; "custom" takes a base URL, "vertex" a project.
25
+ PROVIDERS = (
26
+ {"id": "openrouter", "label": "OpenRouter", "baseUrl": "https://openrouter.ai/api/v1",
27
+ "modelHint": "google/gemini-2.5-flash", "keyUrl": "https://openrouter.ai/keys"},
28
+ {"id": "openai", "label": "OpenAI", "baseUrl": "https://api.openai.com/v1",
29
+ "modelHint": "gpt-4.1-mini", "keyUrl": "https://platform.openai.com/api-keys"},
30
+ {"id": "google", "label": "Google AI Studio", "baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai",
31
+ "modelHint": "gemini-2.5-flash", "keyUrl": "https://aistudio.google.com/apikey"},
32
+ {"id": "anthropic", "label": "Anthropic", "baseUrl": "https://api.anthropic.com/v1",
33
+ "modelHint": "claude-sonnet-5", "keyUrl": "https://console.anthropic.com/settings/keys"},
34
+ {"id": "groq", "label": "Groq", "baseUrl": "https://api.groq.com/openai/v1",
35
+ "modelHint": "llama-3.3-70b-versatile", "keyUrl": "https://console.groq.com/keys"},
36
+ {"id": "cerebras", "label": "Cerebras", "baseUrl": "https://api.cerebras.ai/v1",
37
+ "modelHint": "llama-3.3-70b", "keyUrl": "https://cloud.cerebras.ai"},
38
+ {"id": "custom", "label": "Other OpenAI-compatible", "baseUrl": None, "modelHint": "model-name", "keyUrl": None},
39
+ {"id": "vertex", "label": "Google Cloud Vertex AI", "baseUrl": None, "modelHint": "gemini-3.5-flash-lite",
40
+ "keyUrl": None},
41
+ )
42
+ PRESETS = {provider["id"]: provider for provider in PROVIDERS}
43
+ LOCAL_KEY = "local-server-no-key"
44
+ # Which key Smart uses when both are saved (engines.smart_model); unset keeps the default (Claude's).
45
+ SMART_PROVIDERS = ("anthropic", "openai")
46
+ LOCAL_HOSTS = {"127.0.0.1", "localhost"} # an endpoint on this Mac may be http and keyless
47
+ HELPER_ENV = ("MOBSTER_HELPER_PROVIDER", "TEXT_MODEL_PROVIDER", "TEXT_MODEL_BASE_URL", "TEXT_MODEL_API_KEY",
48
+ "TEXT_MODEL", "GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_LOCATION")
49
+
50
+
51
+ def _error_code(error):
52
+ """The ``error.code`` an OpenAI-style error body carries ("insufficient_quota", "model_not_found"), or None."""
53
+ try:
54
+ data = json.loads(error.read(65536) or b"{}")
55
+ except (OSError, ValueError, AttributeError):
56
+ return None
57
+ detail = data.get("error") if isinstance(data, dict) else None
58
+ code = detail.get("code") or detail.get("type") if isinstance(detail, dict) else None
59
+ return code if isinstance(code, str) else None
60
+
61
+
62
+ def _anthropic_error(error):
63
+ """(type, message) of an Anthropic error body ({"type": "error", "error": {"type", "message"}})."""
64
+ try:
65
+ data = json.loads(error.read(65536) or b"{}")
66
+ except (OSError, ValueError, AttributeError):
67
+ return None, ""
68
+ detail = data.get("error") if isinstance(data, dict) else None
69
+ if not isinstance(detail, dict):
70
+ return None, ""
71
+ return detail.get("type"), str(detail.get("message") or "")
72
+
73
+
74
+ # The coding agents a saved workflow's model steps may run on (ai_plans/runner.py's engines).
75
+ PREFERRED_AGENTS = ("claude_code", "codex", "gemini")
76
+
77
+
78
+ def _is_smart_provider(value):
79
+ """Whether ``value`` names a Smart provider: anthropic, openai or a provider in Settings › Models."""
80
+ from .ai_plans import providers
81
+ return isinstance(value, str) and (value in SMART_PROVIDERS or providers.get(value) is not None)
82
+
83
+
84
+ def _provider_ready(env):
85
+ """The provider MOBSTER_SMART_PROVIDER names (Settings › Models) has its key or sign-in."""
86
+ from .ai_plans import providers
87
+ provider = providers.get(env.get("MOBSTER_SMART_PROVIDER") or "")
88
+ return provider is not None and providers.configured(provider, env)
89
+
90
+
91
+ def hint(key):
92
+ if key == LOCAL_KEY:
93
+ return "not needed"
94
+ return f"…{key[-4:]}" if key and len(key) >= 8 else None
95
+
96
+
97
+ def base_url(value):
98
+ if not isinstance(value, str):
99
+ raise ValueError("Enter the helper's base URL")
100
+ parts = urlsplit(value or "")
101
+ local = parts.hostname in LOCAL_HOSTS
102
+ if parts.scheme not in {"https"} | ({"http"} if local else set()) or not parts.hostname or parts.username \
103
+ or parts.password or parts.query or parts.fragment or len(value) > 300:
104
+ raise ValueError("Use an https:// base URL (http:// only for this Mac), like https://api.example.com/v1")
105
+ return value.rstrip("/")
106
+
107
+
108
+ def openai_key(env=os.environ):
109
+ """(key, "own" | "helper") for Smart, or (None, None): OPENAI_API_KEY, else an OpenAI helper's key."""
110
+ if env.get("OPENAI_API_KEY"):
111
+ return env["OPENAI_API_KEY"], "own"
112
+ if current_provider(env) == "openai" and env.get("TEXT_MODEL_API_KEY") \
113
+ and env.get("TEXT_MODEL_API_KEY") != LOCAL_KEY:
114
+ return env["TEXT_MODEL_API_KEY"], "helper"
115
+ return None, None
116
+
117
+
118
+ def anthropic_key(env=os.environ):
119
+ """(key, "own" | "helper") for Smart on Claude, or (None, None)."""
120
+ if (env.get("ANTHROPIC_API_KEY") or "").strip():
121
+ return env["ANTHROPIC_API_KEY"].strip(), "own"
122
+ if current_provider(env) == "anthropic" and env.get("TEXT_MODEL_API_KEY") \
123
+ and env.get("TEXT_MODEL_API_KEY") != LOCAL_KEY:
124
+ return env["TEXT_MODEL_API_KEY"], "helper"
125
+ return None, None
126
+
127
+
128
+ def current_provider(env=os.environ):
129
+ saved = env.get("MOBSTER_HELPER_PROVIDER")
130
+ if saved in PRESETS:
131
+ return saved
132
+ if env.get("TEXT_MODEL_PROVIDER") == "vertex":
133
+ return "vertex"
134
+ url = (env.get("TEXT_MODEL_BASE_URL") or PRESETS["openrouter"]["baseUrl"]).rstrip("/")
135
+ return next((p["id"] for p in PROVIDERS if p["baseUrl"] == url), "custom") if env.get("TEXT_MODEL") else None
136
+
137
+
138
+ class Keys:
139
+ def __init__(self, env_file):
140
+ self.env_file = env_file
141
+
142
+ def state(self):
143
+ env = os.environ
144
+ provider = current_provider(env)
145
+ helper_key = env.get("TEXT_MODEL_API_KEY")
146
+ vertex = provider == "vertex"
147
+ configured = (bool(env.get("TEXT_MODEL"))
148
+ and (bool(env.get("GOOGLE_CLOUD_PROJECT")) if vertex else bool(helper_key)))
149
+ openai, source = openai_key(env)
150
+ from .engines import ANTHROPIC_SMART_MODEL, model_provider, smart_model
151
+ anthropic, anthropic_source = anthropic_key(env)
152
+ smart = smart_model(env)
153
+ claude = smart if model_provider(smart) == "anthropic" else ANTHROPIC_SMART_MODEL
154
+ preference = env.get("MOBSTER_SMART_PROVIDER")
155
+ agent = env.get("MOBSTER_PREFERRED_AGENT")
156
+ return {
157
+ "preferredAgent": agent if agent in PREFERRED_AGENTS else None,
158
+ "openai": {"configured": bool(openai), "keyHint": hint(openai), "fromHelper": source == "helper",
159
+ "model": SMART_MODEL},
160
+ "anthropic": {"configured": bool(anthropic), "keyHint": hint(anthropic),
161
+ "fromHelper": anthropic_source == "helper", "model": claude},
162
+ # Whose account Smart runs on now (None: no key yet), and the saved choice for when both keys exist.
163
+ "smart": {"provider": model_provider(smart) if (openai or anthropic or _provider_ready(env)) else None,
164
+ "preference": preference if _is_smart_provider(preference) else None},
165
+ "helper": {"configured": configured, "provider": provider, "model": env.get("TEXT_MODEL") or None,
166
+ "baseUrl": None if vertex else (env.get("TEXT_MODEL_BASE_URL") or
167
+ (PRESETS["openrouter"]["baseUrl"] if provider else None)),
168
+ "keyHint": None if vertex else hint(helper_key),
169
+ "project": env.get("GOOGLE_CLOUD_PROJECT") if vertex else None,
170
+ "location": env.get("GOOGLE_CLOUD_LOCATION") if vertex else None},
171
+ "providers": [dict(provider) for provider in PROVIDERS],
172
+ "persisted": self.env_file is not None,
173
+ }
174
+
175
+ def _apply(self, changes):
176
+ if self.env_file:
177
+ update_env_file(self.env_file, changes)
178
+ for key, value in changes.items():
179
+ if value is None:
180
+ os.environ.pop(key, None)
181
+ else:
182
+ os.environ[key] = value
183
+
184
+ def update(self, body):
185
+ if (not isinstance(body, dict) or not body
186
+ or set(body) - {"openai", "anthropic", "helper", "smartProvider", "preferredAgent"}):
187
+ raise ValueError("Send openai, anthropic, helper, smartProvider and/or preferredAgent settings")
188
+ changes = {}
189
+ if "preferredAgent" in body:
190
+ # The coding agent the first run chose, for a saved workflow's model steps while Mobster's agent has no key
191
+ # (server.py's MOBSTER_PREFERRED_AGENT, DR-56). Only an agent Mobster knows, or null to clear it.
192
+ if body["preferredAgent"] is not None and body["preferredAgent"] not in PREFERRED_AGENTS:
193
+ raise ValueError("preferredAgent must be claude_code, codex, gemini or null")
194
+ changes["MOBSTER_PREFERRED_AGENT"] = body["preferredAgent"]
195
+ if "smartProvider" in body:
196
+ if body["smartProvider"] is not None and not _is_smart_provider(body["smartProvider"]):
197
+ raise ValueError("smartProvider must be anthropic, openai, a provider in Settings › Models, or null")
198
+ changes["MOBSTER_SMART_PROVIDER"] = body["smartProvider"]
199
+ for name, variable, label in (("openai", "OPENAI_API_KEY", "an OpenAI"),
200
+ ("anthropic", "ANTHROPIC_API_KEY", "a Claude")):
201
+ if name not in body:
202
+ continue
203
+ value = body[name]
204
+ if value is not None and (not isinstance(value, dict) or set(value) != {"key"}
205
+ or not isinstance(value["key"], str)
206
+ or not KEY_PATTERN.fullmatch(value["key"].strip())):
207
+ raise ValueError(f"That doesn't look like {label} key. Copy it again without spaces.")
208
+ changes[variable] = value["key"].strip() if value is not None else None
209
+ if "helper" in body:
210
+ changes.update(self._helper_changes(body["helper"]))
211
+ self._apply(changes)
212
+ return self.state()
213
+
214
+ def _helper_changes(self, helper):
215
+ if helper is None:
216
+ return {key: None for key in HELPER_ENV}
217
+ allowed = {"provider", "model", "key", "baseUrl", "project", "location"}
218
+ if not isinstance(helper, dict) or set(helper) - allowed \
219
+ or not isinstance(helper.get("provider"), str) or helper["provider"] not in PRESETS:
220
+ raise ValueError("Choose a helper provider")
221
+ provider = helper["provider"]
222
+ model = helper.get("model")
223
+ if not isinstance(model, str) or not MODEL_PATTERN.fullmatch(model.strip()):
224
+ raise ValueError("Enter the model name, like " + PRESETS[provider]["modelHint"])
225
+ changes = {"MOBSTER_HELPER_PROVIDER": provider, "TEXT_MODEL": model.strip()}
226
+ if provider == "vertex":
227
+ project, location = helper.get("project"), helper.get("location") or "global"
228
+ if not isinstance(project, str) or not PROJECT_PATTERN.fullmatch(project.strip()):
229
+ raise ValueError("Enter your Google Cloud project ID")
230
+ if not isinstance(location, str) or not LOCATION_PATTERN.fullmatch(location):
231
+ raise ValueError("Enter a Vertex location like global or us-central1")
232
+ return {**changes, "TEXT_MODEL_PROVIDER": "vertex", "GOOGLE_CLOUD_PROJECT": project.strip(),
233
+ "GOOGLE_CLOUD_LOCATION": location, "TEXT_MODEL_API_KEY": None, "TEXT_MODEL_BASE_URL": None}
234
+ url = base_url(helper.get("baseUrl")) if provider == "custom" else PRESETS[provider]["baseUrl"]
235
+ key = helper.get("key")
236
+ local = urlsplit(url).hostname in LOCAL_HOSTS
237
+ # A saved key is only ever sent to the endpoint it was entered for.
238
+ same_endpoint = current_provider() == provider and (
239
+ provider != "custom" or os.environ.get("TEXT_MODEL_BASE_URL") == url)
240
+ if key is None or key == "":
241
+ if local and (not same_endpoint or not os.environ.get("TEXT_MODEL_API_KEY")):
242
+ # Ollama and LM Studio need no key; the helper client still sends a bearer value.
243
+ key = LOCAL_KEY
244
+ elif (not os.environ.get("TEXT_MODEL_API_KEY") or not same_endpoint
245
+ or os.environ.get("TEXT_MODEL_API_KEY") == LOCAL_KEY):
246
+ # Keep the saved key when only the model changes on the same endpoint.
247
+ raise ValueError("Paste the API key for " + PRESETS[provider]["label"])
248
+ else:
249
+ key = None
250
+ elif not isinstance(key, str) or not KEY_PATTERN.fullmatch(key.strip()):
251
+ raise ValueError("That doesn't look like an API key. Copy it again without spaces.")
252
+ changes.update({"TEXT_MODEL_PROVIDER": None, "TEXT_MODEL_BASE_URL": url,
253
+ "GOOGLE_CLOUD_PROJECT": None, "GOOGLE_CLOUD_LOCATION": None})
254
+ if key is not None:
255
+ changes["TEXT_MODEL_API_KEY"] = key.strip()
256
+ return changes
257
+
258
+ def test(self, target, opener=urllib.request.urlopen):
259
+ """{"ok": bool, "message": str, "link"?: {label, url}}: one or two tiny requests with the saved key."""
260
+ if target == "openai":
261
+ key, _ = openai_key()
262
+ if not key:
263
+ return {"ok": False, "message": "Add your OpenAI key first."}
264
+ return self._openai_check(opener, key)
265
+ if target == "anthropic":
266
+ key, _ = anthropic_key()
267
+ if not key:
268
+ return {"ok": False, "message": "Add your Claude key first."}
269
+ return self._anthropic_check(opener, key, self.state()["anthropic"]["model"])
270
+ if target == "helper":
271
+ state = self.state()["helper"]
272
+ if not state["configured"]:
273
+ return {"ok": False, "message": "Add a helper model first."}
274
+ if state["provider"] == "vertex":
275
+ return self._vertex_check()
276
+ body = {"model": state["model"], "max_tokens": 16,
277
+ "messages": [{"role": "user", "content": 'Reply with exactly {"ok":true}'}]}
278
+ return self._probe(opener, state["baseUrl"] + "/chat/completions", os.environ["TEXT_MODEL_API_KEY"],
279
+ body, PRESETS.get(state["provider"], {}).get("label", "The helper"))
280
+ raise ValueError("target must be openai, anthropic or helper")
281
+
282
+ @staticmethod
283
+ def _openai_check(opener, key):
284
+ """A free read of Smart's model, then the smallest generation: OpenAI enforces quota only when it generates."""
285
+ headers = {"Authorization": f"Bearer {key}", "User-Agent": "Mobster"}
286
+ body = {"model": SMART_MODEL, "input": "Reply with OK.", "max_output_tokens": 16,
287
+ "reasoning": {"effort": "low"}, "store": False}
288
+ requests = (
289
+ urllib.request.Request(f"{OPENAI_URL}/models/{SMART_MODEL}", method="GET", headers=headers),
290
+ urllib.request.Request(f"{OPENAI_URL}/responses", json.dumps(body).encode(), method="POST",
291
+ headers={**headers, "Content-Type": "application/json"}),
292
+ )
293
+ try:
294
+ for request in requests:
295
+ with opener(request, timeout=30) as response:
296
+ response.read(65536)
297
+ return {"ok": True, "message": f"OpenAI accepted the key, and it can run {SMART_MODEL}."}
298
+ except urllib.error.HTTPError as error:
299
+ code = _error_code(error)
300
+ if error.code == 401:
301
+ return {"ok": False,
302
+ "message": "OpenAI didn't accept this key. Copy it again from the OpenAI Platform: "
303
+ "it starts with sk-.", "problem": "rejected"}
304
+ if code in ("insufficient_quota", "credit_balance_exhausted") or error.code == 402:
305
+ # ``problem`` tells the server Smart can't run until a test passes (Runtime.check_key).
306
+ return {"ok": False,
307
+ "message": "Your OpenAI account has no credit yet. Add $5 in Billing, then test again.",
308
+ "link": {"label": "Add credit", "url": OPENAI_BILLING_URL}, "problem": "no_credit"}
309
+ if error.code in (403, 404) or code == "model_not_found":
310
+ return {"ok": False,
311
+ "message": f"This key's project can't use {SMART_MODEL}, the model Mobster's agent "
312
+ "runs on. Allow it in OpenAI › Settings › Project › Limits, then test again.",
313
+ "problem": "no_model"}
314
+ if error.code == 429:
315
+ return {"ok": True, "message": "OpenAI accepted the key but is rate limiting it right now."}
316
+ return {"ok": False, "message": f"OpenAI answered with an error ({error.code}). Try again in a minute.",
317
+ "problem": "provider_error"}
318
+ except (urllib.error.URLError, TimeoutError, OSError):
319
+ return {"ok": False,
320
+ "message": "Mobster couldn't reach OpenAI. Check your internet connection, then try again.",
321
+ "problem": "offline"}
322
+
323
+ @staticmethod
324
+ def _anthropic_check(opener, key, model):
325
+ """As ``_openai_check``, on the Claude API: a free read of the model, then the smallest generation."""
326
+ from .model_clients import ANTHROPIC_VERSION, anthropic_thinking
327
+ headers = {"x-api-key": key, "anthropic-version": ANTHROPIC_VERSION, "User-Agent": "Mobster"}
328
+ body = {"model": model, "max_tokens": 16, "messages": [{"role": "user", "content": "Reply with OK."}]}
329
+ thinking, effort = anthropic_thinking(model, "none")
330
+ if thinking is not None:
331
+ body["thinking"] = thinking
332
+ if effort is not None:
333
+ body["output_config"] = {"effort": effort}
334
+ requests = (
335
+ urllib.request.Request(f"{ANTHROPIC_URL}/models/{model}", method="GET", headers=headers),
336
+ urllib.request.Request(f"{ANTHROPIC_URL}/messages", json.dumps(body).encode(), method="POST",
337
+ headers={**headers, "Content-Type": "application/json"}),
338
+ )
339
+ try:
340
+ for request in requests:
341
+ with opener(request, timeout=30) as response:
342
+ response.read(65536)
343
+ return {"ok": True, "message": f"Claude accepted the key, and it can run {model}."}
344
+ except urllib.error.HTTPError as error:
345
+ kind, message = _anthropic_error(error)
346
+ if error.code == 401:
347
+ return {"ok": False, "message": "Claude didn't accept this key. Copy it again from the Claude Console: "
348
+ "it starts with sk-ant-.", "problem": "rejected"}
349
+ if error.code == 402 or kind == "billing_error" or "credit balance is too low" in message:
350
+ return {"ok": False, "message": "Your Claude account has no credit yet. Add $5 in Billing, then "
351
+ "test again.",
352
+ "link": {"label": "Add credit", "url": ANTHROPIC_BILLING_URL}, "problem": "no_credit"}
353
+ if error.code in (403, 404):
354
+ return {"ok": False, "message": f"This key can't use {model}, the model Mobster's agent runs on. Check "
355
+ "its workspace in the Claude Console, then test again.",
356
+ "problem": "no_model"}
357
+ if error.code in (429, 529):
358
+ return {"ok": True, "message": "Claude accepted the key but is busy or rate limiting it right now."}
359
+ return {"ok": False, "message": f"Anthropic answered with an error ({error.code}). Try again in a minute.",
360
+ "problem": "provider_error"}
361
+ except (urllib.error.URLError, TimeoutError, OSError):
362
+ return {"ok": False, "message": "Mobster couldn't reach Claude. Check your internet connection, then try "
363
+ "again.", "problem": "offline"}
364
+
365
+ @staticmethod
366
+ def _probe(opener, url, key, body, name):
367
+ request = urllib.request.Request(url, json.dumps(body).encode(), method="POST", headers={
368
+ "Authorization": f"Bearer {key}", "Content-Type": "application/json", "User-Agent": "Mobster"})
369
+ try:
370
+ with opener(request, timeout=20) as response:
371
+ response.read(65536)
372
+ return {"ok": True, "message": f"{name} accepted the key."}
373
+ except urllib.error.HTTPError as error:
374
+ if error.code in (401, 403):
375
+ return {"ok": False, "message": f"{name} rejected the key. Check it and paste it again."}
376
+ if error.code == 404:
377
+ return {"ok": False, "message": f"{name} could not find that model or address. Check the model name."}
378
+ if error.code == 429 and _error_code(error) == "insufficient_quota":
379
+ return {"ok": False, "message": f"{name} accepted the key, but the account has no credit. "
380
+ "Add some, then test again."}
381
+ if error.code == 429:
382
+ return {"ok": True, "message": f"{name} accepted the key but is rate limiting it right now."}
383
+ return {"ok": False, "message": f"{name} answered with an error ({error.code}). Check the model name."}
384
+ except (urllib.error.URLError, TimeoutError, OSError):
385
+ return {"ok": False, "message": f"Could not reach {name}. Check your internet connection."}
386
+
387
+ @staticmethod
388
+ def _vertex_check():
389
+ from .gemini import GCloudToken
390
+ try:
391
+ GCloudToken.get(timeout=15)
392
+ return {"ok": True, "message": "Your gcloud login works for Vertex AI."}
393
+ except Exception:
394
+ return {"ok": False,
395
+ "message": "Vertex AI uses the gcloud CLI. Install it and run gcloud auth login, then test again."}
@@ -0,0 +1,36 @@
1
+ """Pure interval accounting shared by private traces and the public offline latency report."""
2
+
3
+
4
+ def _waiting(name):
5
+ return name == "wait" or name.startswith("wait.") or ".wait" in name
6
+
7
+
8
+ def _merged_intervals(intervals, wall_ms):
9
+ clipped = sorted((max(0.0, start), min(wall_ms, start + duration))
10
+ for start, duration in intervals if duration > 0)
11
+ merged = []
12
+ for start, end in clipped:
13
+ if end <= start:
14
+ continue
15
+ if merged and start <= merged[-1][1]:
16
+ merged[-1] = (merged[-1][0], max(end, merged[-1][1]))
17
+ else:
18
+ merged.append((start, end))
19
+ return merged
20
+
21
+
22
+ def temporal_overlap_ms(phases, calls, wall_ms):
23
+ """Off-lane call time concurrent with recorded non-wait main work, never time saved."""
24
+ if wall_ms <= 0:
25
+ return 0.0
26
+ work = _merged_intervals(((item[1], item[2]) for item in phases if not _waiting(item[0])), wall_ms)
27
+ off_lane = _merged_intervals(((item[2], item[3]) for item in calls if item[1] != "main"), wall_ms)
28
+ total = 0.0
29
+ i = j = 0
30
+ while i < len(work) and j < len(off_lane):
31
+ total += max(0.0, min(work[i][1], off_lane[j][1]) - max(work[i][0], off_lane[j][0]))
32
+ if work[i][1] <= off_lane[j][1]:
33
+ i += 1
34
+ else:
35
+ j += 1
36
+ return total
@@ -0,0 +1,170 @@
1
+ """Always-on latency trace for one run: monotonic spans, cheap enough to leave on.
2
+
3
+ Every run records what its critical path waited on. The run's own thread (the
4
+ "main" lane) records *phases*: consecutive, non-overlapping spans such as
5
+ ``observe.first``, ``decide``, ``verify.action``, ``dispatch.TAP`` and
6
+ ``settle.TAP``. Model calls are recorded separately as *calls*, on the lane of
7
+ the thread that made them (``main``, or a side channel such as ``speculation``,
8
+ ``prediction``, ``prefetch``), with queueing, connection reuse and hedging.
9
+ Point events (``marks``) note speculation hits and misses, memo hits and so on.
10
+
11
+ Cost: a phase is two ``time.monotonic()`` calls and one list append under a
12
+ lock (about 1 microsecond); a run records 20-100 of them. The whole trace is
13
+ emitted once, as one ``latency_trace`` event, when the run finishes. Nothing is
14
+ written per span, so tracing never adds journal writes to the hot path.
15
+
16
+ Times in the emitted event are milliseconds from the trace origin (rounded to
17
+ 0.1 ms). ``origin_wall_ms`` is the wall clock at the origin, for joining traces
18
+ with journal timestamps. The report tool is ``python -m mobile_agent.evals.latency_report``.
19
+ """
20
+
21
+ import threading
22
+ import time
23
+ from contextlib import contextmanager
24
+
25
+ # Bounds keep a pathological run (a long compiled loop) from producing an
26
+ # oversized journal event; the counts of anything dropped are reported.
27
+ MAX_PHASES = 2000
28
+ MAX_CALLS = 1000
29
+ MAX_MARKS = 1000
30
+
31
+
32
+ def _ms(value):
33
+ return round(value * 1000, 1)
34
+
35
+
36
+ class Trace:
37
+ """Spans for one run. Thread-safe; never raises into the caller."""
38
+
39
+ def __init__(self, clock=time.monotonic, wall=time.time):
40
+ self._clock = clock
41
+ self.origin = clock()
42
+ self.origin_wall = wall()
43
+ self._lock = threading.Lock()
44
+ self.phases = []
45
+ self.calls = []
46
+ self.marks = []
47
+ self.dropped = {"phases": 0, "calls": 0, "marks": 0}
48
+ self._open = None
49
+
50
+ def now(self):
51
+ return self._clock()
52
+
53
+ def phase(self, name, start, end=None, **attrs):
54
+ """Record one main-lane phase that ran from ``start`` to ``end`` (default now)."""
55
+ end = self._clock() if end is None else end
56
+ with self._lock:
57
+ if len(self.phases) >= MAX_PHASES:
58
+ self.dropped["phases"] += 1
59
+ return
60
+ self.phases.append((name, start, end, attrs or None))
61
+
62
+ @contextmanager
63
+ def span(self, name, **attrs):
64
+ """``with trace.span("observe.refresh"):`` records one main-lane phase."""
65
+ start = self._clock()
66
+ nested = self._open is not None
67
+ if not nested:
68
+ self._open = name
69
+ extra = {}
70
+ try:
71
+ yield extra
72
+ finally:
73
+ end = self._clock()
74
+ if nested:
75
+ self.mark(name, at=start, ms=_ms(end - start), **attrs, **extra)
76
+ else:
77
+ self._open = None
78
+ self.phase(name, start, end, **attrs, **extra)
79
+
80
+ def call(self, purpose, start, end, lane="main", **attrs):
81
+ """One model request (or one attempt of a hedged request)."""
82
+ with self._lock:
83
+ if len(self.calls) >= MAX_CALLS:
84
+ self.dropped["calls"] += 1
85
+ return
86
+ self.calls.append((purpose, lane, start, end, attrs))
87
+
88
+ def mark(self, name, at=None, **attrs):
89
+ at = self._clock() if at is None else at
90
+ with self._lock:
91
+ if len(self.marks) >= MAX_MARKS:
92
+ self.dropped["marks"] += 1
93
+ return
94
+ self.marks.append((name, at, attrs or None))
95
+
96
+ def event(self, final=None):
97
+ """The one journal event for this run: compact, numbers and short names only."""
98
+ final = self._clock() if final is None else final
99
+ o = self.origin
100
+ with self._lock:
101
+ phases = [[name, _ms(start - o), _ms(end - start), *([attrs] if attrs else [])]
102
+ for name, start, end, attrs in self.phases]
103
+ calls = [[purpose, lane, _ms(start - o), _ms(end - start), attrs]
104
+ for purpose, lane, start, end, attrs in self.calls]
105
+ marks = [[name, _ms(at - o), *([attrs] if attrs else [])] for name, at, attrs in self.marks]
106
+ dropped = {key: value for key, value in self.dropped.items() if value}
107
+ event = {"event": "latency_trace", "version": 1, "origin_wall_ms": round(self.origin_wall * 1000, 1),
108
+ "wall_ms": _ms(final - o), "phases": phases, "calls": calls, "marks": marks,
109
+ "summary": summarize(phases, calls, _ms(final - o))}
110
+ event["summary"]["overlap_complete"] = not (dropped.get("phases") or dropped.get("calls"))
111
+ if dropped:
112
+ event["dropped"] = dropped
113
+ return event
114
+
115
+
116
+ def summarize(phases, calls, wall_ms):
117
+ """Per-run totals: time per phase name, unattributed main-lane time, and overlap."""
118
+ from .latency_overlap import temporal_overlap_ms # here: a run's start never needs it (perf-budgets.json)
119
+ by_phase = {}
120
+ for item in phases:
121
+ name, duration = item[0], item[2]
122
+ by_phase[name] = round(by_phase.get(name, 0) + duration, 1)
123
+ attributed = sum(item[2] for item in phases)
124
+ off_lane = sum(item[3] for item in calls if item[1] != "main")
125
+ main_calls = sum(item[3] for item in calls if item[1] == "main")
126
+ return {"by_phase": by_phase, "attributed_ms": round(attributed, 1),
127
+ "unattributed_ms": round(max(0.0, wall_ms - attributed), 1),
128
+ "model_main_ms": round(main_calls, 1), "model_off_lane_ms": round(off_lane, 1),
129
+ "overlap_ms": round(temporal_overlap_ms(phases, calls, wall_ms), 1),
130
+ "calls": len(calls), "hedges": sum(1 for item in calls if item[4].get("hedge")),
131
+ "hedge_wins": sum(1 for item in calls if item[4].get("hedge") and item[4].get("won"))}
132
+
133
+
134
+ class _Null:
135
+ """The trace used when none is attached: every method is a cheap no-op."""
136
+
137
+ origin = 0.0
138
+
139
+ def now(self):
140
+ return time.monotonic()
141
+
142
+ def phase(self, *args, **kwargs):
143
+ pass
144
+
145
+ @contextmanager
146
+ def span(self, name, **attrs):
147
+ yield {}
148
+
149
+ def call(self, *args, **kwargs):
150
+ pass
151
+
152
+ def mark(self, *args, **kwargs):
153
+ pass
154
+
155
+
156
+ NULL = _Null()
157
+
158
+
159
+ def lane():
160
+ """The lane name for the calling thread: ``main`` unless it is a named worker."""
161
+ name = threading.current_thread().name
162
+ if name.startswith("mobster-speculate"):
163
+ return "speculation"
164
+ if name.startswith("mobster-prefetch"):
165
+ return "prefetch"
166
+ if name.startswith("mobster-hedge"):
167
+ return "hedge"
168
+ if name.startswith("mobster-predict"):
169
+ return "prediction"
170
+ return "main"
@@ -0,0 +1,15 @@
1
+ """Track `learned`: saved workflows that learn their steps from a good run and replay them with no model (the
2
+ accounts half of the replay core; FEATURE-BACKLOG v2, item 1). Part of Mobster for Mac, not the CLI: the public tree
3
+ leaves this package out, and tracks.load then records it as absent.
4
+
5
+ - workflows.py: the run listener that learns, the pre-run hook that replays, and what the Mac app shows;
6
+ - programs.py: the saved-program store, learning and code matching, for any Smart run: a request with the same words
7
+ as one that ended done, and other values, replays that run with code checking each step;
8
+ - parts.py: spans of saved programs that a request of another shape may run (``MOBSTER_PROGRAMS=on``);
9
+ - taught.py: what a stopped lesson becomes (a taught program), and how a practice run's corrections change it;
10
+ - teach.py: the live-view recorder behind Runtime.teach_*;
11
+ - app_path.py: ``ProgramListener`` (keeps a done Smart run whole) and the program pre-run hook;
12
+ - routes.py: ``RouteMemory``, how iOS's own moves work on each phone;
13
+ - register.py: registers the listeners and hooks, and the ``/api/replay`` routes.
14
+
15
+ The engine itself (record, replay, preconditions, approvals) is replay_core, shared with `mobster test`."""