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,453 @@
1
+ """DELETE and POST routes of the local API."""
2
+
3
+ import re
4
+ from urllib.parse import urlsplit
5
+
6
+ from . import ai_plans, engines, env_secrets, harness_api, ownership
7
+ from .api_devices import runtime_teach
8
+ from .api_errors import APIError
9
+ from .api_http import (
10
+ ALREADY_ANSWERED,
11
+ APP_ONLY,
12
+ APPROVE_IN_APP,
13
+ APPROVE_IN_APP_ORIGINS,
14
+ DEVICE_PATH,
15
+ FILE_TROUBLE,
16
+ TOOK_TOO_LONG,
17
+ WORKFLOW_TOOL,
18
+ api_path,
19
+ )
20
+ from .downloads import EXPORT_MAX_BYTES, save_export
21
+ from .graph.schema import GraphError
22
+ from .journal import JournalError
23
+ from .output_contract import FORMATS as OUTPUT_FORMATS
24
+ from .output_contract import validate_schema
25
+ from .transport import decode_json
26
+ from .workflows.cron import preview as schedule_preview
27
+ from .workflows.records import WORKFLOW_ID
28
+ from .workflows.runs import WorkflowDispatchUncertain
29
+
30
+
31
+ class WriteRoutesMixin:
32
+ def do_DELETE(self):
33
+ self.close_connection = True
34
+ if self.mcp_run():
35
+ return
36
+ if self.refused():
37
+ return
38
+ path = api_path(urlsplit(self.path).path)
39
+ if self.extension("DELETE", path):
40
+ return
41
+ link_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/approvals/"
42
+ r"([A-Za-z0-9_@.-]{1,64})/link", path)
43
+ if link_match:
44
+ if not self.has_app_session():
45
+ return self.send_json({"error": "Stop a link in the Mobster app.", "code": "app_only"}, 403)
46
+ try:
47
+ return self.send_json(self.runtime.workflows.approval_links.stop(*link_match.groups()))
48
+ except APIError as exc:
49
+ return self.send_api_error(exc)
50
+ workflow_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})", path)
51
+ if workflow_match:
52
+ try:
53
+ return self.send_json({"deleted": self.runtime.workflows.delete(workflow_match[1])})
54
+ except APIError as exc:
55
+ return self.send_api_error(exc)
56
+ except JournalError:
57
+ return self.send_json({"error": "Workflows could not be saved. Try again.",
58
+ "code": "persistence_unavailable"}, 503)
59
+ device_match = DEVICE_PATH.fullmatch(path)
60
+ if device_match and device_match[2] is None and device_match[3] is None:
61
+ phone = self.device_phone(device_match[1])
62
+ if phone is None:
63
+ return
64
+ try:
65
+ return self.send_json({"forgotten": self.runtime.fleet.forget(phone.id)})
66
+ except LookupError as error:
67
+ return self.send_json({"error": str(error), "code": "device_refused"}, 409)
68
+ match = re.fullmatch(r"/api/runs/([a-f0-9]{12})", path)
69
+ if not match:
70
+ return self.send_json({"error": "Not found"}, 404)
71
+ try:
72
+ self.runtime.delete_run(match[1])
73
+ except APIError as exc:
74
+ return self.send_api_error(exc)
75
+ except JournalError:
76
+ return self.send_json({"error": "Task history could not be saved. Try again.",
77
+ "code": "persistence_unavailable"}, 503)
78
+ return self.send_json({"deleted": match[1]})
79
+
80
+ def do_POST(self):
81
+ # Close after POST so unread malformed bodies cannot become a second request.
82
+ self.close_connection = True
83
+ ownership.set_client(self.headers.get("X-Mobster-Client")) # a label for who holds the phone, nothing more
84
+ if self.mcp_run():
85
+ return
86
+ if self.refused():
87
+ return
88
+ if self.extension("POST", api_path(urlsplit(self.path).path)):
89
+ return
90
+ if self.headers.get_content_type() != "application/json":
91
+ return self.send_json({"error": "Use application/json"}, 415)
92
+ try:
93
+ if self.headers.get("Transfer-Encoding") or len(self.headers.get_all("Content-Length", [])) != 1:
94
+ raise ValueError("Supply one Content-Length and no Transfer-Encoding")
95
+ size = int(self.headers.get("Content-Length", "0"))
96
+ # Exports carry a whole result; every other body is a small command.
97
+ limit = EXPORT_MAX_BYTES + 4096 if api_path(self.path) == "/api/export" else 64000
98
+ if not 0 < size <= limit:
99
+ return self.send_json({"error": "Invalid body length"}, 400)
100
+ raw = self.rfile.read(size)
101
+ if len(raw) != size:
102
+ raise ValueError("Incomplete request body")
103
+ body = decode_json(raw)
104
+ if not isinstance(body, dict):
105
+ raise ValueError("JSON body must be an object")
106
+ path = api_path(self.path)
107
+ if path.startswith("/api/setup/"):
108
+ return self.setup_action(path, body)
109
+ if path == "/api/devices/chosen":
110
+ return self.choose_devices(body)
111
+ if path == "/api/fleet/health/acknowledge":
112
+ # "Mark as handled" lifts a profile's pre-run refusal, so it needs the Mac app's own session.
113
+ if self.needs_app():
114
+ return
115
+ fleet = getattr(self.runtime, "fleet", None)
116
+ profile = body.get("profile")
117
+ if fleet is None or set(body) - {"profile", "code"} or not isinstance(profile, str) or not profile:
118
+ raise ValueError("Name the profile to mark as handled, and optionally the code")
119
+ try:
120
+ cleared = fleet.health.acknowledge(profile, body.get("code"))
121
+ except OSError:
122
+ raise APIError("Mobster could not save that: another Mobster process is holding the phone "
123
+ "health file. Choose Mark as handled again in a moment.", 503,
124
+ "health_busy") from None
125
+ return self.send_json({"cleared": cleared})
126
+ device_match = DEVICE_PATH.fullmatch(path)
127
+ if device_match and device_match[2] in ("take-back", "resume-queue"):
128
+ return self.owner_action(device_match[1], device_match[2], body)
129
+ if device_match and (device_match[3] or device_match[2] in ("control", "teach")):
130
+ return self.device_action(device_match[1], device_match[2], device_match[3], body)
131
+ provider_match = re.fullmatch(r"/api/ai-plans/providers/([a-z_]{2,30})/([a-z-]{2,20})", path)
132
+ if provider_match:
133
+ if self.needs_app():
134
+ return
135
+ from .ai_plans import routes as plan_routes
136
+ try:
137
+ return self.send_json(plan_routes.handle(self.runtime.keys, provider_match[1], provider_match[2],
138
+ body))
139
+ except plan_routes.RouteError as error:
140
+ return self.send_json({"error": str(error), "code": error.code}, error.status)
141
+ if device_match and device_match[2] in ("disconnect", "connect", None) and not device_match[3]:
142
+ return self.manage_device(device_match[1], device_match[2], body)
143
+ if path.startswith("/api/groups/"):
144
+ if self.group_request("POST", path, body) is False:
145
+ return self.send_json({"error": "Not found"}, 404)
146
+ return
147
+ if path == "/api/workflows/preview":
148
+ return self.send_json(schedule_preview(body, self.runtime.workflows.now()))
149
+ if path == "/api/workflows":
150
+ return self.send_json({"workflow": self.runtime.workflows.create(body)}, 201)
151
+ if path == "/api/workflows/draft":
152
+ return self.send_json(self.runtime.workflows.draft(body))
153
+ if path == "/api/workflows/validate":
154
+ if not isinstance(body, dict) or set(body) - {"graph", "workflowId"}:
155
+ raise ValueError("Send the graph, and the workflowId when it is a saved workflow's")
156
+ return self.send_json({"graph": self.runtime.workflows.check_graph(body.get("graph"),
157
+ body.get("workflowId"))})
158
+ if path == "/api/workflows/evaluate":
159
+ from .graph import expr as graph_expr
160
+ if not isinstance(body, dict) or set(body) - {"expression", "scope", "workflowId", "batchId", "node"} \
161
+ or not isinstance(body.get("scope", {}), dict):
162
+ raise ValueError("Send the expression, and a scope of values it reads or the run and step "
163
+ "(workflowId, batchId, node) whose values it reads")
164
+ scope = body.get("scope") or {}
165
+ if body.get("batchId"):
166
+ try:
167
+ scope = {**self.runtime.workflows.recorded_scope(body.get("workflowId"), body["batchId"],
168
+ body.get("node")), **scope}
169
+ except APIError as exc:
170
+ return self.send_api_error(exc)
171
+ try:
172
+ value = graph_expr.render(body.get("expression"), scope)
173
+ except graph_expr.ExprError as error:
174
+ return self.send_json({"error": str(error), "code": "invalid_expression", "at": error.at}, 400)
175
+ return self.send_json({"value": value, "type": graph_expr.kind_of(value)})
176
+ template = re.fullmatch(r"/api/workflows/templates/([a-z-]{1,40})", path)
177
+ if template:
178
+ from .workflows import brief
179
+ return self.send_json({"workflow": brief.create_from_template(self.runtime.workflows, template[1],
180
+ body)}, 201)
181
+ link_match = re.fullmatch(r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/approvals/"
182
+ r"([A-Za-z0-9_@.-]{1,64})/link", path)
183
+ if link_match:
184
+ # Only the person asks for a link: MCP, Remote and hooks never make one.
185
+ if not self.has_app_session():
186
+ return self.send_json({"error": "Ask by link in the Mobster app.", "code": "app_only"}, 403)
187
+ if not isinstance(body, dict) or set(body) - {"label"}:
188
+ raise ValueError("A link takes a label, and nothing else")
189
+ return self.send_json(self.runtime.workflows.approval_links.make(*link_match.groups(),
190
+ label=body.get("label")))
191
+ approval_match = re.fullmatch(
192
+ r"/api/workflows/([a-f0-9]{12})/runs/([a-f0-9]{12})/approvals/([A-Za-z0-9_@.-]{1,64})", path)
193
+ if approval_match:
194
+ # Only the person answers: MCP, Remote, hooks and typed messages never approve a step.
195
+ if not self.has_app_session():
196
+ return self.send_json({"error": "Answer this in the Mobster app.", "code": "app_only"}, 403)
197
+ return self.send_json({"approval": self.runtime.workflows.answer_approval(*approval_match.groups(),
198
+ body)})
199
+ workflow_match = re.fullmatch(
200
+ r"/api/workflows/([a-f0-9]{12})(/run|/stop|/publish|/rollback|/prove|/resume)?", path)
201
+ if workflow_match:
202
+ if workflow_match[2] in ("/prove", "/resume"):
203
+ # A prove and a bulk resume start from Mobster's editor, like a test run and a Resume.
204
+ if not self.has_app_session():
205
+ return self.send_json({"error": "A test run or a re-run starts from Mobster's editor.",
206
+ "code": "test_from_editor"}, 403)
207
+ if len(self.headers.get_all("Idempotency-Key", [])) != 1:
208
+ raise ValueError("Send one Idempotency-Key")
209
+ key = self.headers.get("Idempotency-Key")
210
+ if workflow_match[2] == "/prove":
211
+ return self.send_json(self.runtime.workflows.prove(workflow_match[1], body, key,
212
+ origin=self.origin_label()), 201)
213
+ if not isinstance(body, dict) or set(body) != {"batchIds"}:
214
+ raise ValueError("Send batchIds: the runs to resume")
215
+ return self.send_json(self.runtime.workflows.resume_all(workflow_match[1], body["batchIds"], key,
216
+ origin=self.origin_label()), 202)
217
+ if not workflow_match[2]:
218
+ return self.send_json({"workflow": self.runtime.workflows.update(workflow_match[1], body)})
219
+ if workflow_match[2] == "/publish":
220
+ return self.send_json({"workflow": self.runtime.workflows.history.publish(workflow_match[1], body)})
221
+ if workflow_match[2] == "/rollback":
222
+ return self.send_json(
223
+ {"workflow": self.runtime.workflows.history.rollback(workflow_match[1], body)})
224
+ if workflow_match[2] == "/stop":
225
+ return self.send_json({"batch": self.runtime.workflows.stop_batch(workflow_match[1])})
226
+ if set(body) - {"values", "threadId", "newConversation", "rows", "test", "from", "only", "resume",
227
+ "trigger", "input"} \
228
+ or len(self.headers.get_all("Idempotency-Key", [])) != 1:
229
+ raise ValueError("A run takes values, threadId, newConversation, rows, test, from, only, "
230
+ "resume, trigger or input, and one Idempotency-Key")
231
+ rerun = {key: body[key] for key in ("from", "only", "resume") if key in body} or None
232
+ if rerun is not None and len(rerun) > 1:
233
+ raise ValueError("A re-run is one of from, only or resume")
234
+ if (body.get("test") or rerun) and not self.has_app_session():
235
+ return self.send_json({"error": "A test run or a re-run starts from Mobster's editor.",
236
+ "code": "test_from_editor"}, 403)
237
+ from .threads.routes import grant_header
238
+ started = self.runtime.workflows.start(workflow_match[1], self.headers.get("Idempotency-Key"),
239
+ values=body.get("values"), thread_id=body.get("threadId"),
240
+ new_conversation=body.get("newConversation", False),
241
+ origin=self.origin_label(), rows=body.get("rows"),
242
+ remote_grant=grant_header(self.origin_label(), self.headers),
243
+ test=body.get("test", False), rerun=rerun,
244
+ trigger=body.get("trigger"), input=body.get("input"),
245
+ project_secrets=self.has_app_session())
246
+ return self.send_json(started, 200 if started["replayed"] else 201)
247
+ tool_match = WORKFLOW_TOOL.fullmatch(path)
248
+ if tool_match and not WORKFLOW_ID.fullmatch(tool_match[1]):
249
+ # A workflow made a tool, by its slug (POST /v1/workflows/<slug>): the same schema as its MCP tool.
250
+ if len(self.headers.get_all("Idempotency-Key", [])) != 1:
251
+ raise ValueError("Send one Idempotency-Key with a workflow run")
252
+ answer, ended = self.runtime.workflows.run_tool(tool_match[1], body,
253
+ self.headers.get("Idempotency-Key"),
254
+ origin=self.origin_label(),
255
+ project_secrets=self.has_app_session())
256
+ return self.send_json(answer, 200 if ended else 202)
257
+ if path == "/api/usage/estimate":
258
+ engine = engines.read_engine(body.get("engine"))
259
+ if (set(body) - {"goal", "outputSchema", "outputFormat", "engine"}
260
+ or not isinstance(body.get("goal"), str) or len(body["goal"].strip()) > 4000
261
+ or engine is not None and engine not in engines.ENGINES and not ai_plans.is_agent(engine)
262
+ or body.get("outputFormat") not in (None, "auto", *OUTPUT_FORMATS)):
263
+ raise ValueError("Supply a draft of at most 4000 characters and an optional output schema, "
264
+ "output format and engine")
265
+ schema = validate_schema(body["outputSchema"]) if body.get("outputSchema") is not None else None
266
+ estimates = self.runtime.engine_estimates(body["goal"].strip(), schema,
267
+ output_format=body.get("outputFormat"))
268
+ return self.send_json({"engine": engine or engines.SMART, "engines": estimates})
269
+ if path == "/api/runs":
270
+ if body.get("mode", "live") != "live":
271
+ raise ValueError("Dashboard tasks run on your iPhone. Synthetic replays are not supported here.")
272
+ if len(self.headers.get_all("Idempotency-Key", [])) > 1:
273
+ raise ValueError("Supply only one Idempotency-Key")
274
+ key = self.headers.get("Idempotency-Key")
275
+ core = {"appId", "goal", "mode", "outputSchema", "outputFormat", "allowedBundles", "engine", "device",
276
+ "phones", "profiles", "pinnedApps", "queue", "teamGroup", "dryRun"}
277
+ fields = harness_api.run_fields()
278
+ if body.get("dryRun") is True:
279
+ return self.send_json({"dryRun": self.runtime.dry_run(body)})
280
+ if "dryRun" in body:
281
+ raise ValueError("dryRun is true, or leave it out")
282
+ if set(body) - core - set(fields):
283
+ raise ValueError("Unsupported task field")
284
+ if body.get("device") is not None and not isinstance(body["device"], str):
285
+ raise ValueError("device must be a device's id, UDID or name")
286
+ if "queue" in body and type(body["queue"]) is not bool:
287
+ raise ValueError("queue is true (wait for a held phone, the default) or false (refuse)")
288
+ if set(body) & harness_api.claimed_fields():
289
+ raise ValueError("consoleMember and consoleSession are set by Mobster, never by a request")
290
+ extras = {name: body[name] for name in body if name in fields and name not in core}
291
+ if extras.get("projectEnv") is not None and not self.has_app_session():
292
+ # Resolving local secrets needs the private app session, never an origin label.
293
+ return self.send_json({"error": "Start a task with project secrets in the Mobster app on this Mac.",
294
+ "code": "app_only"}, 403)
295
+ longform = extras.get("longform")
296
+ if isinstance(longform, dict) and longform.get("capUsd") is not None and not self.has_app_session():
297
+ # Raising a task's spending limit is the Mac app's: X-Mobster-Origin grants nothing.
298
+ return self.send_json({"error": APP_ONLY, "code": "app_only"}, 403)
299
+ if self.loosening(body):
300
+ return
301
+ origin = self.origin_label()
302
+ extras.update(self.origin_claim())
303
+ if body.get("teamGroup") is not None and body.get("phones") is None:
304
+ raise ValueError("teamGroup is set by the team's console, for phones only")
305
+ if body.get("phones") is not None or body.get("profiles") is not None:
306
+ return self.start_group(body, key, extras, origin)
307
+ created = self.runtime.create(body.get("appId"), body.get("goal"), "live", key,
308
+ output_schema=body.get("outputSchema"),
309
+ output_format=body.get("outputFormat", "auto"),
310
+ allowed_bundles=body.get("allowedBundles"), engine=body.get("engine"),
311
+ **({"device": body["device"]} if body.get("device") is not None else {}),
312
+ **({"pinned_apps": body["pinnedApps"]}
313
+ if body.get("pinnedApps") is not None else {}),
314
+ **({"extras": extras} if extras else {}),
315
+ **({"origin": origin} if origin != "api" else {}),
316
+ queue=body.get("queue", True))
317
+ run, replayed = created if key else (created, False)
318
+ return self.send_json({"run": run.public(), "replayed": replayed}, 200 if replayed else 201)
319
+ if path == "/api/settings":
320
+ # Asking less and spending more are the person's, in the Mac app (S6.6), as a task's capUsd is.
321
+ if {"askBeforeActing", "taskCostLimitUsd", "monthlyLimitUsd"} & set(body) and self.needs_app():
322
+ return
323
+ return self.send_json(self.runtime.update_settings(body))
324
+ if path == "/api/keys":
325
+ return self.send_json(self.runtime.save_keys(body))
326
+ if path == "/api/keys/test":
327
+ if set(body) != {"target"}:
328
+ raise ValueError("Send the target to test: anthropic, openai or helper")
329
+ return self.send_json(self.runtime.check_key(body["target"]))
330
+ if path == "/api/export":
331
+ return self.send_json(save_export(body))
332
+ if path in ("/api/device/control", "/api/device/teach") and self.needs_app():
333
+ return # as /api/devices/{id}/control and teach (device_action)
334
+ if path == "/api/device/control":
335
+ return self.send_json({"ok": True, "action": self.runtime.control_device(body)})
336
+ if path == "/api/device/teach":
337
+ return self.send_json(runtime_teach(self.runtime, body, None))
338
+ shared = re.fullmatch(r"/api/runs/([a-f0-9]{12})/share(?:/(preview)|/([A-Za-z0-9_-]{22})/revoke)?", path)
339
+ if shared:
340
+ token = body.pop("accessToken", None) if isinstance(body, dict) else None
341
+ if shared[2] == "preview":
342
+ if set(body) - {"people", "screens", "title"}:
343
+ raise ValueError("A share preview takes people, screens and title")
344
+ return self.send_json(self.runtime.share_preview(shared[1], body.get("people", "mask"),
345
+ body.get("screens", False), body.get("title")))
346
+ # Making or turning off a link is the Mac app's: it holds the account (as Remote's turn-on).
347
+ if self.needs_app():
348
+ return
349
+ if shared[3]:
350
+ if set(body):
351
+ raise ValueError("Turning a link off takes only the account's accessToken")
352
+ return self.send_json(self.runtime.unshare_run(shared[1], shared[3], token))
353
+ if set(body) - {"people", "screens", "title", "expiresDays"}:
354
+ raise ValueError("A share takes people, screens, title, expiresDays and the accessToken")
355
+ return self.send_json(self.runtime.share_run(shared[1], token, body.get("people", "mask"),
356
+ body.get("screens", False), body.get("title"),
357
+ body.get("expiresDays", 30)), 201)
358
+ artifact = re.fullmatch(r"/api/runs/([a-f0-9]{12})/artifact", path)
359
+ if artifact:
360
+ if set(body) - {"blocks"} != {"format"} or body["format"] not in ("pdf", "png", "md"):
361
+ raise ValueError("An artifact takes format (pdf, png or md) and optionally blocks")
362
+ return self.send_json(self.runtime.save_artifact(artifact[1], body["format"], body.get("blocks")))
363
+ blocked = re.fullmatch(r"/api/runs/([a-f0-9]{12})/blocks/([A-Za-z0-9_.:-]{1,64})/items/"
364
+ r"([A-Za-z0-9_.:-]{1,64})", path)
365
+ if blocked:
366
+ if (set(body) - {"text"} != {"decision"} or body["decision"] not in ("approve", "skip", "reopen")
367
+ or "text" in body and not isinstance(body["text"], str)):
368
+ raise ValueError("A decision on a result is approve, skip or reopen (and optionally the "
369
+ "edited text with approve)")
370
+ # Approving is the Mac app's, as an approval card's yes is (S6.6); skipping stays token-only.
371
+ if body["decision"] == "approve" and self.needs_app(APPROVE_IN_APP):
372
+ return
373
+ return self.send_json(self.runtime.decide_block_item(blocked[1], blocked[2], blocked[3],
374
+ body["decision"], body.get("text")))
375
+ match = re.fullmatch(r"/api/runs/([a-f0-9]{12})/(stop|approval|review|gif|eval)", path)
376
+ run = self.runtime.runs.get(match[1]) if match else None
377
+ if run and match[2] == "eval":
378
+ if body:
379
+ raise ValueError("Adding a task to your evals takes no body")
380
+ return self.send_json(self.runtime.add_to_evals(match[1]))
381
+ if run and match[2] == "gif":
382
+ theme, redact, mp4 = body.get("theme", "paper"), body.get("redact", False), body.get("mp4", False)
383
+ if (set(body) - {"theme", "redact", "mp4"} or theme not in ("paper", "night")
384
+ or type(redact) is not bool or type(mp4) is not bool):
385
+ raise ValueError("A GIF takes an optional theme (paper or night), redact and mp4 "
386
+ "(true or false)")
387
+ return self.send_json(self.runtime.save_run_gif(match[1], theme, redact, mp4))
388
+ if run and match[2] == "approval":
389
+ if (set(body) - {"choice", "instruction", "answer", "remember", "items", "text", "allowCost"}
390
+ != {"id", "approve"}
391
+ or "remember" in body and type(body["remember"]) is not bool
392
+ or "allowCost" in body and type(body["allowCost"]) is not bool
393
+ or not isinstance(body["id"], str)
394
+ or type(body["approve"]) is not bool
395
+ or "choice" in body and not isinstance(body["choice"], str)
396
+ or "instruction" in body and not isinstance(body["instruction"], str)
397
+ or "answer" in body and not isinstance(body["answer"], str)
398
+ or "items" in body and not isinstance(body["items"], list)
399
+ or "text" in body and not isinstance(body["text"], str)):
400
+ raise ValueError("An approval needs its id and approve: true or false (and optionally a "
401
+ "choice, an instruction with approve false, an answer to a question, or "
402
+ "the items it decides and an edited text)")
403
+ pending = run.approval
404
+ question = (pending is not None and pending.get("id") == body["id"]
405
+ and pending.get("kind") == "clarify")
406
+ # Approving or choosing needs the Mac app; a decline, a redirect or a clarifying answer is token-only.
407
+ if (body["approve"] or "choice" in body) and not question:
408
+ if run.origin in APPROVE_IN_APP_ORIGINS and not self.has_app_session():
409
+ return self.send_json({"error": APP_ONLY, "code": "app_only"}, 403)
410
+ if self.needs_app(APPROVE_IN_APP):
411
+ return
412
+ if "items" in body or "text" in body:
413
+ answered = run.decide_proposals(body["id"], body["approve"], body.get("items"),
414
+ body.get("text"))
415
+ else:
416
+ answered = run.answer_approval(body["id"], body["approve"], body.get("choice"),
417
+ body.get("instruction"),
418
+ **({"answer": body["answer"]} if "answer" in body else {}),
419
+ **({"remember": True} if body.get("remember") else {}),
420
+ **({"allow_cost": True} if body.get("allowCost") else {}))
421
+ if not answered:
422
+ decision = run.approval_decision(body["id"])
423
+ raise APIError(ALREADY_ANSWERED.get(decision, "This approval request is no longer waiting"),
424
+ 409, "approval_not_pending", **({"decision": decision} if decision else {}))
425
+ return self.send_json({"run": run.public()})
426
+ if run and match[2] == "review":
427
+ if set(body) != {"ok"} or type(body["ok"]) is not bool:
428
+ raise ValueError("A review needs ok: true or false")
429
+ return self.send_json({"run": self.runtime.review_run(run.id, body["ok"]).public()})
430
+ if run:
431
+ self.runtime.stop_run(run.id)
432
+ return self.send_json({"run": run.public()})
433
+ return self.send_json({"error": "Not found"}, 404)
434
+ except GraphError as exc:
435
+ return self.send_json({"error": str(exc), "code": exc.code, "errors": exc.errors}, 400)
436
+ except (ValueError, TypeError) as exc:
437
+ return self.send_json({"error": str(exc)}, 400)
438
+ except APIError as exc:
439
+ return self.send_api_error(exc)
440
+ except WorkflowDispatchUncertain as exc:
441
+ return self.send_json({"error": str(exc), "code": "workflow_dispatch_uncertain", "activeRunId": exc.run_id},
442
+ 503)
443
+ except JournalError:
444
+ return self.send_json({"error": "Mobster couldn't save this to its history on this Mac, and a task may "
445
+ "have started. Check its status before you try again.",
446
+ "code": "persistence_unavailable"}, 503)
447
+ except TimeoutError: # the request's socket (5 s, ``setup``)
448
+ return self.send_json({"error": TOOK_TOO_LONG, "code": "request_timeout"}, 408)
449
+ except env_secrets.KeychainError: # a key or the alert address, which never fall back to the file
450
+ return self.send_json({"error": env_secrets.REFUSED, "code": "keychain"}, 503)
451
+ except OSError:
452
+ return self.send_json({"error": FILE_TROUBLE,
453
+ "code": "io_error"}, 500)
@@ -0,0 +1,173 @@
1
+ """HTTP routes the tracks register, each under its owner's PREFIXES so it can never shadow a core route."""
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from typing import Any, Callable, Iterable, Mapping, Optional
6
+
7
+ ID = r"(?P<id>[a-f0-9]{12})"
8
+ DEVICE = r"(?P<device>[^/]{1,128})"
9
+ HEADER_ORIGINS = ("app", "tui", "cli", "mcp", "remote", "console")
10
+
11
+ PREFIXES = {
12
+ "harness": ("/api/runs/{id}/messages", "/api/runs/{id}/pause", "/api/runs/{id}/pickup", "/api/prewarm",
13
+ "/api/harness"),
14
+ "integrations": ("/api/integrations",),
15
+ "threads": ("/api/threads",),
16
+ "memory": ("/api/memory",),
17
+ "files": ("/api/attachments", "/api/phone/files"),
18
+ "wireless": ("/api/wireless", "/api/devices/{device}/wifi"),
19
+ "testkit": ("/api/tests",),
20
+ "remote": ("/api/remote",),
21
+ "accounts": ("/api/accounts",),
22
+ "learned": ("/api/replay",),
23
+ "longform": ("/api/longform", "/api/runs/{id}/longform"),
24
+ "outlets": ("/api/outlets", "/api/runs/{id}/send"),
25
+ "connectors": ("/api/connectors", "/api/runs/{id}/issue"),
26
+ "plugins": ("/api/plugins",),
27
+ "permissions": ("/api/permissions", "/api/approvals"),
28
+ "packs": ("/api/packs",),
29
+ "waiting": ("/api/waiting",),
30
+ "netxray": ("/api/xray/network",),
31
+ # In no track of its own, so it never answers 503.
32
+ "access": ("/api/access",),
33
+ "triggers": ("/api/triggers",),
34
+ "queue": ("/api/queue", "/api/transfers"),
35
+ "feedback": ("/api/feedback",),
36
+ "teams": ("/api/teams",),
37
+ "xray": ("/api/xray/app",),
38
+ "projectenv": ("/api/project/env",),
39
+ "projects": ("/api/projects",),
40
+ }
41
+ # A route answers 503 while its owner's track is off.
42
+ TRACK = {"harness": "harness", "integrations": "harness",
43
+ "threads": "threads", "memory": "memory", "files": "attachments",
44
+ "wireless": "wireless", "testkit": "testkit", "remote": "remote", "accounts": "accounts",
45
+ "learned": "learned", "longform": "longform", "outlets": "outlets",
46
+ "connectors": "connectors", "plugins": "plugins",
47
+ "permissions": "permissions", "packs": "packs", "waiting": "waiting", "netxray": "netxray",
48
+ "triggers": "triggers", "queue": "content_queue", "feedback": "feedback", "xray": "xray", "teams": "teams",
49
+ "projects": "projects"}
50
+ BODIES = ("json", "raw", "none")
51
+ JSON_MAX = 64_000
52
+ NEWER_DATA = "This was saved by a newer Mobster. Update Mobster to use it."
53
+
54
+
55
+ def expand(pattern):
56
+ return pattern.replace("{id}", ID).replace("{device}", DEVICE)
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class Route:
61
+ owner: str
62
+ method: str
63
+ pattern: str
64
+ handler: Callable[["Request"], "Response"]
65
+ body: str = "json"
66
+ max_bytes: int = JSON_MAX
67
+ stream: bool = False
68
+ idempotency: bool = False
69
+
70
+
71
+ @dataclass
72
+ class Request:
73
+ runtime: Any
74
+ method: str
75
+ path: str
76
+ params: dict
77
+ query: dict
78
+ headers: Mapping[str, str]
79
+ body: Any
80
+ content_type: Optional[str]
81
+ origin: str # a UI label only: it grants nothing
82
+ app_session: bool
83
+
84
+
85
+ class Response:
86
+ def __init__(self, status=200, *, value=None, data=None, content_type=None, events=None, keepalive_s=5.0,
87
+ filename=None):
88
+ self.status, self.value, self.data, self.content_type = status, value, data, content_type
89
+ self.events, self.keepalive_s = events, keepalive_s
90
+ self.filename = filename
91
+
92
+ @staticmethod
93
+ def json(value, status: int = 200) -> "Response":
94
+ return Response(status, value=value)
95
+
96
+ @staticmethod
97
+ def bytes(data: bytes, content_type: str, status: int = 200, filename: Optional[str] = None) -> "Response":
98
+ if not isinstance(data, (bytes, bytearray)):
99
+ raise TypeError("Response.bytes takes bytes")
100
+ return Response(status, data=bytes(data), content_type=content_type, filename=filename)
101
+
102
+ @staticmethod
103
+ def sse(events: Iterable[Optional[dict]], *, keepalive_s: float = 5.0) -> "Response":
104
+ return Response(200, events=events, keepalive_s=keepalive_s)
105
+
106
+ @staticmethod
107
+ def error(message: str, status: int, code: str, **details) -> "Response":
108
+ return Response(status, value={"error": message, "code": code, **details})
109
+
110
+ def is_stream(self):
111
+ return self.events is not None
112
+
113
+
114
+ _routes: list = []
115
+
116
+
117
+
118
+
119
+ def register(route: Route) -> None:
120
+ if route.owner not in PREFIXES:
121
+ raise ValueError(f"Unknown route owner {route.owner!r}")
122
+ if route.method not in ("GET", "POST", "DELETE"):
123
+ raise ValueError("A route's method is GET, POST or DELETE")
124
+ if route.body not in BODIES:
125
+ raise ValueError("A route's body is json, raw or none")
126
+ if route.body == "json" and not 0 < route.max_bytes <= JSON_MAX:
127
+ raise ValueError("A JSON body is at most 64 KB")
128
+ if route.body == "raw" and not 0 < route.max_bytes <= 64 * 1024 * 1024:
129
+ raise ValueError("A raw body states its cap, at most 64 MB")
130
+ if not callable(route.handler):
131
+ raise ValueError("A route needs a handler")
132
+ pattern = expand(route.pattern)
133
+ if not any(_inside(pattern, expand(prefix)) for prefix in PREFIXES[route.owner]):
134
+ raise ValueError(f"{route.pattern} is outside {route.owner}'s prefixes")
135
+ try:
136
+ re.compile(pattern)
137
+ except re.error as error:
138
+ raise ValueError(f"Invalid route pattern: {error}") from None
139
+ if any(r.method == route.method and expand(r.pattern) == pattern for r in _routes):
140
+ raise ValueError(f"{route.method} {route.pattern} is already registered")
141
+ _routes.append(route)
142
+
143
+
144
+ def _inside(pattern, prefix):
145
+ if not pattern.startswith(prefix):
146
+ return False
147
+ rest = pattern[len(prefix):]
148
+ return rest == "" or rest[0] in "/(?$"
149
+
150
+
151
+ def _owned(path, owner):
152
+ return any(re.fullmatch(expand(prefix) + r"(?:/.*)?", path) for prefix in PREFIXES[owner])
153
+
154
+
155
+ def match(method: str, path: str) -> Optional[tuple]:
156
+ for route in _routes:
157
+ if route.method != method:
158
+ continue
159
+ found = re.fullmatch(expand(route.pattern), path)
160
+ if found and _owned(path, route.owner):
161
+ return route, {k: v for k, v in found.groupdict().items() if v is not None}
162
+ return None
163
+
164
+
165
+ def track_off(route) -> bool:
166
+ from . import harness_api
167
+ return harness_api.disabled(TRACK.get(route.owner))
168
+
169
+
170
+
171
+
172
+ def reset_for_tests() -> None:
173
+ _routes.clear()
@@ -0,0 +1,4 @@
1
+ """The App API: every app on your iPhone as an API, over REST and MCP. Every endpoint reads: none taps, types, likes,
2
+ follows, sends, posts, buys or deletes (PackIO refuses an act's control)."""
3
+
4
+ DEFAULT_PORT = 8780