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,246 @@
1
+ // transfer: hands files from this Mac to an iPhone without a cable, for mobile_agent/phone_io/handoff.py.
2
+ //
3
+ // transfer airdrop --to NAME [--timeout S] FILE...
4
+ // AirDrop's own panel (NSSharingService), and a press on the one recipient whose name is exactly NAME. Another
5
+ // name, two devices with that name, or none in time: Cancel is pressed and nothing is sent.
6
+ // {"ok":true,"sent":N,"seconds":S} | {"ok":false,"code":"not_found"|"ambiguous","seen":[names]}
7
+ // transfer drop --x FX --y FY FILE...
8
+ // Drags the files from a 32-point spot of this helper's onto the iPhone Mirroring window, at a point given as
9
+ // fractions of the window, as a person drags from Finder. iPhone Mirroring hands them to the app in front on
10
+ // the phone. {"ok":true,"operation":N} once the window took them.
11
+ //
12
+ // One JSON line on standard output. Errors: {"ok":false,"code","error"}: "usage", "permission" (Accessibility is
13
+ // missing), "no_window" (iPhone Mirroring isn't showing the phone), "refused" (the window didn't take the drop),
14
+ // "failed" (AirDrop said it failed or the person on the phone declined), "timeout". Nothing is retried here.
15
+ //
16
+ // The Mac app carries it as Contents/Resources/transfer; a source checkout builds it with
17
+ // native_helpers.build("transfer").
18
+
19
+ import AppKit
20
+ import ApplicationServices
21
+ import CoreGraphics
22
+ import Foundation
23
+
24
+ let mirroringBundle = "com.apple.ScreenContinuity"
25
+
26
+ func emit(_ value: [String: Any]) -> Never {
27
+ let data = (try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys])) ?? Data("{}".utf8)
28
+ FileHandle.standardOutput.write(data)
29
+ FileHandle.standardOutput.write(Data("\n".utf8))
30
+ exit(value["ok"] as? Bool == true ? 0 : 1)
31
+ }
32
+
33
+ func fail(_ code: String, _ message: String, _ extra: [String: Any] = [:]) -> Never {
34
+ var out: [String: Any] = ["ok": false, "code": code, "error": message]
35
+ for (key, value) in extra { out[key] = value }
36
+ emit(out)
37
+ }
38
+
39
+ // MARK: - arguments
40
+
41
+ var arguments = Array(CommandLine.arguments.dropFirst())
42
+ guard let op = arguments.first, ["airdrop", "drop"].contains(op) else {
43
+ fail("usage", "transfer airdrop --to NAME FILE... | transfer drop --x FX --y FY FILE...")
44
+ }
45
+ arguments.removeFirst()
46
+ var options: [String: String] = [:]
47
+ var paths: [String] = []
48
+ var index = 0
49
+ while index < arguments.count {
50
+ let word = arguments[index]
51
+ if word.hasPrefix("--"), index + 1 < arguments.count {
52
+ options[String(word.dropFirst(2))] = arguments[index + 1]
53
+ index += 2
54
+ } else {
55
+ paths.append(word)
56
+ index += 1
57
+ }
58
+ }
59
+ let files = paths.map { URL(fileURLWithPath: $0) }
60
+ if files.isEmpty || files.contains(where: { !FileManager.default.isReadableFile(atPath: $0.path) }) {
61
+ fail("usage", "Name files that exist and can be read")
62
+ }
63
+ if !AXIsProcessTrusted() {
64
+ fail("permission", "Mobster needs Accessibility to do this. Turn it on in System Settings › Privacy & Security › Accessibility.")
65
+ }
66
+
67
+ let app = NSApplication.shared
68
+ app.setActivationPolicy(.accessory)
69
+
70
+ // MARK: - AirDrop
71
+
72
+ func attribute(_ element: AXUIElement, _ name: String) -> AnyObject? {
73
+ var value: AnyObject?
74
+ return AXUIElementCopyAttributeValue(element, name as CFString, &value) == .success ? value : nil
75
+ }
76
+
77
+ /// The AirDrop panel's recipients ("dd.nodeBody" buttons, named by their description) and its Cancel button.
78
+ func panel() -> (recipients: [(name: String, element: AXUIElement)], cancel: AXUIElement?) {
79
+ var recipients: [(String, AXUIElement)] = []
80
+ var cancel: AXUIElement?
81
+ func walk(_ node: AXUIElement, _ depth: Int) {
82
+ if depth > 16 { return }
83
+ let role = attribute(node, kAXRoleAttribute) as? String ?? ""
84
+ let name = attribute(node, kAXDescriptionAttribute) as? String ?? ""
85
+ let identifier = attribute(node, kAXIdentifierAttribute) as? String ?? ""
86
+ if role == kAXButtonRole {
87
+ if identifier == "dd.nodeBody", !name.isEmpty { recipients.append((name, node)) }
88
+ if identifier.isEmpty, name == "Cancel" { cancel = node }
89
+ }
90
+ for child in (attribute(node, kAXChildrenAttribute) as? [AXUIElement]) ?? [] { walk(child, depth + 1) }
91
+ }
92
+ walk(AXUIElementCreateApplication(getpid()), 0)
93
+ return (recipients, cancel)
94
+ }
95
+
96
+ final class Sharing: NSObject, NSSharingServiceDelegate {
97
+ let started = Date()
98
+ func sharingService(_ sharingService: NSSharingService, didShareItems items: [Any]) {
99
+ emit(["ok": true, "sent": items.count, "seconds": (Date().timeIntervalSince(started) * 10).rounded() / 10])
100
+ }
101
+ func sharingService(_ sharingService: NSSharingService, didFailToShareItems items: [Any], error: Error) {
102
+ let cancelled = (error as NSError).code == NSUserCancelledError
103
+ fail(cancelled ? "cancelled" : "failed", cancelled ? "The AirDrop was cancelled" : error.localizedDescription)
104
+ }
105
+ }
106
+
107
+ func airdrop() {
108
+ guard let recipient = options["to"], !recipient.isEmpty else { fail("usage", "Name the iPhone with --to") }
109
+ let waitForRecipient = Double(options["timeout"] ?? "30") ?? 30
110
+ let waitForTransfer = Double(options["transfer-timeout"] ?? "900") ?? 900
111
+ guard let service = NSSharingService(named: .sendViaAirDrop), service.canPerform(withItems: files) else {
112
+ fail("failed", "AirDrop isn't available on this Mac. Turn on Wi-Fi and Bluetooth.")
113
+ }
114
+ let delegate = Sharing()
115
+ service.delegate = delegate
116
+ service.perform(withItems: files)
117
+ var pressed = false
118
+ var seen: Set<String> = []
119
+ let opened = Date()
120
+ Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
121
+ let (recipients, cancel) = panel()
122
+ seen.formUnion(recipients.map(\.name))
123
+ let matching = recipients.filter { $0.name == recipient }
124
+ if !pressed && matching.count > 1 {
125
+ if let cancel { AXUIElementPerformAction(cancel, kAXPressAction as CFString) }
126
+ fail("ambiguous", "More than one device nearby is called \(recipient)", ["seen": Array(seen).sorted()])
127
+ }
128
+ if !pressed, let only = matching.first {
129
+ // Wait one more beat: a second device with the same name can show up a moment later.
130
+ if Date().timeIntervalSince(opened) < 2.5 { return }
131
+ pressed = AXUIElementPerformAction(only.element, kAXPressAction as CFString) == .success
132
+ if !pressed { fail("failed", "AirDrop didn't take the press on \(recipient)") }
133
+ timer.invalidate()
134
+ DispatchQueue.main.asyncAfter(deadline: .now() + waitForTransfer) {
135
+ fail("timeout", "AirDrop didn't finish within \(Int(waitForTransfer)) seconds")
136
+ }
137
+ return
138
+ }
139
+ if Date().timeIntervalSince(opened) > waitForRecipient {
140
+ if let cancel { AXUIElementPerformAction(cancel, kAXPressAction as CFString) }
141
+ fail("not_found", "AirDrop didn't show \(recipient) within \(Int(waitForRecipient)) seconds",
142
+ ["seen": Array(seen).sorted()])
143
+ }
144
+ }
145
+ }
146
+
147
+ // MARK: - a drop on iPhone Mirroring
148
+
149
+ func post(_ type: CGEventType, _ point: CGPoint) {
150
+ CGEvent(mouseEventSource: nil, mouseType: type, mouseCursorPosition: point, mouseButton: .left)?.post(tap: .cghidEventTap)
151
+ }
152
+
153
+ final class DragSource: NSView, NSDraggingSource {
154
+ var finished: ((NSDragOperation) -> Void)?
155
+ func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
156
+ .copy
157
+ }
158
+ override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
159
+ override func mouseDown(with event: NSEvent) {
160
+ let items = files.map { url -> NSDraggingItem in
161
+ let item = NSDraggingItem(pasteboardWriter: url as NSURL)
162
+ item.setDraggingFrame(bounds, contents: NSWorkspace.shared.icon(forFile: url.path))
163
+ return item
164
+ }
165
+ beginDraggingSession(with: items, event: event, source: self)
166
+ }
167
+ func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
168
+ finished?(operation)
169
+ }
170
+ }
171
+
172
+ func mirroringWindow() -> (app: NSRunningApplication, frame: CGRect)? {
173
+ guard let running = NSRunningApplication.runningApplications(withBundleIdentifier: mirroringBundle).first else {
174
+ return nil
175
+ }
176
+ let windows = (CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID)
177
+ as? [[String: Any]]) ?? []
178
+ for window in windows where (window[kCGWindowOwnerPID as String] as? pid_t) == running.processIdentifier
179
+ && (window[kCGWindowLayer as String] as? Int) == 0 {
180
+ guard let bounds = window[kCGWindowBounds as String] as? [String: Double] else { continue }
181
+ let frame = CGRect(x: bounds["X"] ?? 0, y: bounds["Y"] ?? 0, width: bounds["Width"] ?? 0,
182
+ height: bounds["Height"] ?? 0)
183
+ if frame.width > 150, frame.height > 300 { return (running, frame) }
184
+ }
185
+ return nil
186
+ }
187
+
188
+ func drop() {
189
+ guard let fx = Double(options["x"] ?? ""), let fy = Double(options["y"] ?? ""), (0...1).contains(fx),
190
+ (0...1).contains(fy) else {
191
+ fail("usage", "--x and --y are fractions of the window, 0 to 1")
192
+ }
193
+ guard let (mirroring, frame) = mirroringWindow() else {
194
+ fail("no_window", "iPhone Mirroring isn't showing the phone")
195
+ }
196
+ let front = NSWorkspace.shared.frontmostApplication
197
+ mirroring.activate()
198
+ let target = CGPoint(x: frame.minX + frame.width * fx, y: frame.minY + frame.height * fy)
199
+ // The drag starts beside the window (left of it, or right when it sits at the screen's left edge).
200
+ let screen = NSScreen.screens.first?.frame ?? CGRect(x: 0, y: 0, width: 1440, height: 900)
201
+ let startX = frame.minX > 80 ? frame.minX - 48 : min(frame.maxX + 48, screen.width - 24)
202
+ let start = CGPoint(x: startX, y: target.y)
203
+ let window = NSWindow(contentRect: NSRect(x: start.x - 16, y: screen.height - start.y - 16, width: 32, height: 32),
204
+ styleMask: .borderless, backing: .buffered, defer: false)
205
+ window.level = .screenSaver
206
+ window.isOpaque = false
207
+ window.backgroundColor = NSColor.white.withAlphaComponent(0.02)
208
+ let source = DragSource(frame: NSRect(x: 0, y: 0, width: 32, height: 32))
209
+ window.contentView = source
210
+ window.orderFrontRegardless()
211
+ let saved = CGEvent(source: nil)?.location
212
+ func finish(_ value: [String: Any]) -> Never {
213
+ if let saved { CGWarpMouseCursorPosition(saved) }
214
+ if let front, front.bundleIdentifier != mirroringBundle { front.activate() }
215
+ emit(value)
216
+ }
217
+ source.finished = { operation in
218
+ if operation.isEmpty {
219
+ finish(["ok": false, "code": "refused", "error": "iPhone Mirroring didn't take the files"])
220
+ }
221
+ finish(["ok": true, "operation": Int(operation.rawValue)])
222
+ }
223
+ DispatchQueue.global().asyncAfter(deadline: .now() + 0.4) {
224
+ post(.mouseMoved, start)
225
+ usleep(60_000)
226
+ post(.leftMouseDown, start)
227
+ usleep(120_000)
228
+ for step in 1...30 {
229
+ let t = Double(step) / 30
230
+ post(.leftMouseDragged, CGPoint(x: start.x + (target.x - start.x) * t, y: start.y + (target.y - start.y) * t))
231
+ usleep(20_000)
232
+ }
233
+ // Held over the window a moment so it can say it takes them, then let go.
234
+ for _ in 0..<12 {
235
+ post(.leftMouseDragged, target)
236
+ usleep(50_000)
237
+ }
238
+ post(.leftMouseUp, target)
239
+ }
240
+ DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
241
+ finish(["ok": false, "code": "timeout", "error": "The drag didn't finish"])
242
+ }
243
+ }
244
+
245
+ DispatchQueue.main.async { op == "airdrop" ? airdrop() : drop() }
246
+ app.run()
@@ -0,0 +1,22 @@
1
+ import Foundation
2
+ import Vision
3
+
4
+ // Reads one image on stdin. Output rects use normalized, top-left coordinates.
5
+ do {
6
+ let data = FileHandle.standardInput.readDataToEndOfFile()
7
+ let request = VNRecognizeTextRequest()
8
+ request.recognitionLevel = .fast
9
+ request.usesLanguageCorrection = false
10
+ let handler = VNImageRequestHandler(data: data, options: [:])
11
+ try handler.perform([request])
12
+ let rows: [[String: Any]] = (request.results ?? []).compactMap { observation in
13
+ guard let candidate = observation.topCandidates(1).first else { return nil }
14
+ let box = observation.boundingBox
15
+ return ["text": candidate.string, "confidence": candidate.confidence,
16
+ "rect": [box.minX, 1 - box.maxY, box.width, box.height]]
17
+ }
18
+ FileHandle.standardOutput.write(try JSONSerialization.data(withJSONObject: rows))
19
+ } catch {
20
+ FileHandle.standardError.write(Data("OCR failed\n".utf8))
21
+ exit(1)
22
+ }
@@ -0,0 +1,337 @@
1
+ """Typed user-policy questions; inferred output never grants action authority."""
2
+
3
+ import re
4
+ from dataclasses import replace
5
+ from enum import StrEnum
6
+
7
+
8
+ class RiskTier(StrEnum):
9
+ """Named dispatch risk for confidence-gated actions."""
10
+ SCROLL = "scroll"
11
+ NAVIGATION = "navigation"
12
+ TYPE = "type"
13
+ SIDE_EFFECT = "side_effect"
14
+
15
+
16
+ # AUTHORITY_RULES spirit: send/post/buy/follow/like are never implicit.
17
+ SIDE_EFFECT_REQUEST = re.compile(
18
+ r"\b(send|post|like|follow|buy|purchase|order|pay|submit|publish|comment|share|"
19
+ r"delete|remove|confirm|checkout|transfer|message|email|install|upload|redeem|"
20
+ r"book|reserve|donate|tip|subscribe|set|save|create|update|apply|"
21
+ r"sign[ -]?up|log[ -]?in)\b",
22
+ re.I)
23
+
24
+ SIDE_EFFECT_CONTROL = re.compile(
25
+ r"\b(send|post|like|follow|buy|purchase|order|pay|submit|save|publish|comment|"
26
+ r"share|delete|remove|confirm|checkout|done|transfer|donate|tip|subscribe|"
27
+ r"accept|approve|install|upload|redeem|book|reserve|create|sign[ -]?up|log[ -]?in)\b",
28
+ re.I)
29
+
30
+
31
+ # Roles whose tap only moves between screens.
32
+ NAVIGATION_TAP_ROLES = frozenset({"Button", "Cell", "Link", "StaticText", "Image", "Icon", "Other"})
33
+ # Verbs that make even a plain button state-changing; any match keeps the verifier.
34
+ STATE_CHANGING_LABEL = re.compile(
35
+ r"\b(erase|reset|turn\s+(on|off)|enable|disable|sign\s*(in|out|up)|log\s*(in|out)|restart|"
36
+ r"shut\s*down|power|clear|delete|remove|allow|deny|block|unblock|forget|disconnect|connect|"
37
+ r"pair|unpair|update|install|download|upgrade|agree|accept|decline|join|leave|end|stop|start|"
38
+ r"call|dial|emergency|sos|pay|buy|purchase|send|share|trust|approve|confirm|submit|save|"
39
+ r"done|add|create|new|edit|change|set|reply|post|follow|like|subscribe|unsubscribe|report|"
40
+ r"archive|mute|unmute|hide|lock|unlock|restore|backup|sync|transfer|verify)\b",
41
+ re.I)
42
+
43
+
44
+ # Keys of an on-screen keypad (Calculator, a PIN or dial pad): each press is input, not an effect to de-duplicate.
45
+ KEYPAD_KEY = re.compile(r"^(\d|[+\-×x*÷/=.%,]|plus|minus|add|subtract|multiply|times|divide|equals|decimal|point|"
46
+ r"percent|clear|all clear|delete|backspace|negate|change sign|"
47
+ r"zero|one|two|three|four|five|six|seven|eight|nine)$", re.I)
48
+
49
+
50
+ def is_keypad_key(operation, role, label):
51
+ return operation == "TAP" and role in ("Key", "Button") and bool(KEYPAD_KEY.match((label or "").strip()))
52
+
53
+
54
+ # SpringBoard's passcode keypad: lockscreen.py handles the lock screen, and every model tap there is refused.
55
+ SPRINGBOARD_BUNDLE = "com.apple.springboard"
56
+ _DIGIT_KEY = re.compile(r"^\s*(\d)(?!\d)")
57
+ _PASSCODE_MARKER = re.compile(r"\b(enter passcode|passcode|emergency|touch id|face id)\b", re.I)
58
+
59
+
60
+ def springboard_keypad(snapshot):
61
+ """True when ``snapshot`` is SpringBoard showing a digit keypad. Errs toward True: it only refuses."""
62
+ if getattr(snapshot, "bundle_id", "") != SPRINGBOARD_BUNDLE:
63
+ return False
64
+ digits, marked = set(), False
65
+ for element in getattr(snapshot, "elements", ()) or ():
66
+ label = element.label or ""
67
+ found = _DIGIT_KEY.match(label)
68
+ if found and element.role in ("Button", "Key", "Other") and len(label) <= 12:
69
+ digits.add(found.group(1))
70
+ elif _PASSCODE_MARKER.search(label):
71
+ marked = True
72
+ return len(digits) >= 10 or len(digits) >= 6 and marked
73
+
74
+
75
+ # Controls whose tap commits something the user would want to see first: money, messages, publishing and deletion.
76
+ COMMIT_CONTROL_WORDS = (
77
+ "send", "post", "publish", "reply", "comment", "share", "tweet", "buy", "purchase", "order", "pay", "checkout",
78
+ "place order", "transfer", "donate", "tip", "subscribe", "book", "reserve", "delete", "erase", "remove", "reset",
79
+ "install", "upload", "redeem", "call", "dial", "facetime", "follow", "sign[ -]?up", "submit", "confirm")
80
+ COMMIT_CONTROL = re.compile(r"\b(" + "|".join(COMMIT_CONTROL_WORDS) + r")\b", re.I)
81
+ # Leading words of a label that can name a commit.
82
+ GUARD_WORDS = 4
83
+ # Controls that only open a sheet of choices (whose committing choice is guarded itself).
84
+ SHEET_OPENERS = frozenset({"share", "share…", "share..."})
85
+
86
+
87
+ APPROVAL_OPERATIONS = frozenset({"TAP", "SUBMIT", "TYPE_SUBMIT"})
88
+
89
+ # Commits beyond COMMIT_CONTROL's words, for "ask before acting" only; a lookahead spares a control that opens.
90
+ APPROVAL_COMMIT = re.compile(
91
+ r"\b(?:request(?!\s+(?:desktop|mobile)\b)|(?:move\s+to\s+)?trash|"
92
+ r"clear(?!\s+(?:text|search|query|field|filters?|selection)\b)|accept|decline|join|leave|"
93
+ r"block|report|unsubscribe|unfollow|unmatch|archive|mark\s+as\s+(?:junk|spam)|rent|"
94
+ r"cancel\s+(?:ride|trip|order|subscription|booking|reservation|membership|plan|appointment|delivery)|"
95
+ r"end\s+(?:call|trip|ride)|hang\s+up)\b"
96
+ r"|^\s*get\b(?!\s+(?:started|directions|help|info|more|support|details|tickets|the\s+app)\b)",
97
+ re.I)
98
+ # The approval card's act for each of those words ("request", "trash"...).
99
+ APPROVAL_ACT_ALIASES = {"move to trash": "trash", "mark as junk": "junk", "mark as spam": "junk", "hang up": "end call"}
100
+
101
+
102
+ # Roles whose label is what they show, not what a tap on them does.
103
+ CONTENT_ROLES = frozenset({"Cell", "StaticText", "TextField", "TextView", "SecureTextField", "SearchField", "Image",
104
+ "Icon"})
105
+ # A tap on a field only focuses it, whatever its label ("Order more coffee filters", "Comment").
106
+ EDITABLE_ROLES = frozenset({"TextField", "TextView", "SecureTextField", "SearchField"})
107
+ # The state iOS appends to a reminder's row ("Buy oat milk, Incomplete"): that label is always content.
108
+ ROW_STATE = re.compile(r",\s*(?:Incomplete|Completed)$", re.I)
109
+ # Acts a row names as often as a control ("Call the dentist"): in content, only in labels of CONTROL_WORDS words.
110
+ TALK_ACTS = frozenset({"call", "dial", "facetime", "reply", "comment", "follow"})
111
+ CONTROL_WORDS = 2
112
+ # The checkout button's own words, which COMMIT_CONTROL's "place order" does not read.
113
+ PLACE_ORDER = re.compile(r"^place\s+(?:(?:your|the|this|my)\s+)?order\b", re.I)
114
+
115
+
116
+ # Bare labels that only open a composer or a sheet (a tap on them sends nothing).
117
+ TAP_OPENERS = frozenset({"share", "share…", "share...", "comment", "comments", "reply"})
118
+ UNSEND = re.compile(r"\b(?:unsend|undo send)\b", re.I)
119
+
120
+
121
+ def commit_label(label, role=None):
122
+ """The commit a control's label names, or None; a spurious ask on a plain row is the safe failure."""
123
+ label = " ".join((label or "").split())
124
+ if ROW_STATE.search(label):
125
+ return None
126
+ folded = label.casefold()
127
+ if role is not None and folded in TAP_OPENERS:
128
+ return None # it opens a composer or a sheet, whose own Send or Post asks
129
+ unsend = UNSEND.search(label)
130
+ if unsend and role not in EDITABLE_ROLES:
131
+ return unsend.group(0)
132
+ match = COMMIT_CONTROL.search(label) or APPROVAL_COMMIT.search(label)
133
+ if role not in CONTENT_ROLES:
134
+ return match.group(0) if match else None
135
+ if role in EDITABLE_ROLES or "," in label:
136
+ return None
137
+ first = PLACE_ORDER.match(label) or COMMIT_CONTROL.match(label) or APPROVAL_COMMIT.match(label)
138
+ if first is None or _act(first.group(0)) in TALK_ACTS and len(label.split()) > CONTROL_WORDS:
139
+ return None
140
+ return first.group(0)
141
+
142
+
143
+ # Roles that hold other elements rather than act on a tap: panes, bars, sheets. Text inside one is not its label.
144
+ CONTAINER_ROLES = frozenset({"Application", "Window", "ScrollView", "Table", "CollectionView", "WebView", "Group",
145
+ "NavigationBar", "TabBar", "Toolbar", "StatusBar", "Keyboard", "Alert", "Sheet",
146
+ "Menu", "MenuBar"})
147
+
148
+
149
+ def _holds(outer, point):
150
+ x, y, w, h = outer.rect
151
+ return x - 1e-6 <= point[0] <= x + w + 1e-6 and y - 1e-6 <= point[1] <= y + h + 1e-6
152
+
153
+
154
+ def same_words(a, b):
155
+ return " ".join((a or "").split()).casefold() == " ".join((b or "").split()).casefold()
156
+
157
+
158
+ def approval_subject(elements, target):
159
+ """The element "ask before acting" reads for a tap on ``target``; a commit is never read away."""
160
+ if target is None or target.role not in CONTENT_ROLES or target.role in EDITABLE_ROLES:
161
+ return target
162
+ point = target.tap_point
163
+ around = [e for e in elements if e is not target and e.id != target.id and _holds(e, point)]
164
+ area = lambda e: e.rect[2] * e.rect[3]
165
+ controls = sorted((e for e in around if e.role not in CONTENT_ROLES and e.role not in CONTAINER_ROLES),
166
+ key=lambda e: (not same_words(e.label, target.label), area(e)))
167
+ if controls:
168
+ control = controls[0]
169
+ if not control.label.strip():
170
+ # An unlabelled button is named by the text on it.
171
+ return replace(control, label=target.label)
172
+ if commit_label(control.label, control.role) or not commit_label(target.label, target.role):
173
+ return control
174
+ return target
175
+ rows = sorted((e for e in around if e.role == "Cell"), key=area)
176
+ if rows and ("," in rows[0].label or ROW_STATE.search(" ".join(rows[0].label.split()))):
177
+ return rows[0]
178
+ return target
179
+
180
+
181
+ def label_role(operation, role):
182
+ """The role ``commit_label`` reads a target's label with: its own for a tap, none for a submit."""
183
+ return role if operation == "TAP" else None
184
+
185
+
186
+ def approval_kind(operation, label, *, risk_tier, uncertain=False, role=None):
187
+ """What "ask before acting" asks: "commit", "unsure" or None; scrolling and typing without Return never commit."""
188
+ if operation in APPROVAL_OPERATIONS and (
189
+ commit_label(label, label_role(operation, role))
190
+ or operation in {"SUBMIT", "TYPE_SUBMIT"} and risk_tier == RiskTier.SIDE_EFFECT.value):
191
+ return "commit"
192
+ return "unsure" if uncertain else None
193
+
194
+
195
+ # The act a commit approval names, from the control's label or else the request, and its title.
196
+ APPROVAL_TITLES = {
197
+ "send": "Send this message?", "reply": "Send this reply?", "comment": "Post this comment?",
198
+ "post": "Post this?", "publish": "Publish this?", "tweet": "Post this?", "share": "Share this?",
199
+ "buy": "Buy this?", "purchase": "Buy this?", "order": "Place this order?", "place order": "Place this order?",
200
+ "checkout": "Check out?", "pay": "Make this payment?", "transfer": "Make this transfer?",
201
+ "donate": "Make this donation?", "tip": "Leave this tip?", "subscribe": "Subscribe?",
202
+ "book": "Make this booking?", "reserve": "Make this reservation?", "delete": "Delete this?",
203
+ "erase": "Erase this?", "remove": "Remove this?", "reset": "Reset this?", "install": "Install this?",
204
+ "upload": "Upload this?", "redeem": "Redeem this?", "call": "Place this call?", "dial": "Place this call?",
205
+ "facetime": "Start this FaceTime call?", "follow": "Follow this account?", "sign up": "Sign up?",
206
+ "submit": "Submit this?", "confirm": "Confirm this?",
207
+ "request": "Send this request?", "trash": "Move this to the trash?", "clear": "Clear these?",
208
+ "accept": "Accept this?", "decline": "Decline this?", "join": "Join this?", "leave": "Leave this?",
209
+ "block": "Block this contact?", "report": "Report this?", "unsubscribe": "Unsubscribe?",
210
+ "unfollow": "Unfollow this account?", "unmatch": "Unmatch?", "archive": "Archive this?",
211
+ "junk": "Mark this as junk?", "rent": "Rent this?", "get": "Get this app?", "end call": "End this call?",
212
+ "react": "Send this reaction?", "unsend": "Unsend this message?",
213
+ }
214
+ MESSAGE_ACTS = frozenset({"send", "reply", "comment", "post", "publish", "tweet", "share"})
215
+ # Acts whose card names who it goes to ("Send this message to Sam?").
216
+ RECIPIENT_TITLES = {"send": "Send this message to {}?", "reply": "Send this reply to {}?"}
217
+
218
+
219
+ def _act(match):
220
+ act = re.sub(r"[\s-]+", " ", match.casefold()).strip()
221
+ if PLACE_ORDER.match(act):
222
+ return "place order"
223
+ if act.replace(" ", "") == "signup":
224
+ return "sign up"
225
+ if act in ("unsend", "undo send"):
226
+ return "unsend"
227
+ if act.startswith("cancel ") or act.startswith("end "):
228
+ return "end call" if act == "end call" else act
229
+ return APPROVAL_ACT_ALIASES.get(act, act)
230
+
231
+
232
+ def commit_act(label, request="", role=None, operation="TAP"):
233
+ """The commit a control performs, as one lowercase verb ("send", "place order"), or "submit"."""
234
+ named = commit_label(label, label_role(operation, role))
235
+ if named:
236
+ return _act(named)
237
+ match = COMMIT_CONTROL.search(asked_part(request))
238
+ return _act(match.group(0)) if match else "submit"
239
+
240
+
241
+ def approval_title(act, *, unsure=False, recipient=None):
242
+ """The approval card's title: the act as a question ("Send this message to Sam?")."""
243
+ if unsure:
244
+ return "Not sure about this step"
245
+ if recipient and act in RECIPIENT_TITLES:
246
+ return RECIPIENT_TITLES[act].format(recipient)
247
+ if act and act.startswith("cancel "):
248
+ return f"Cancel this {act.split(' ', 1)[1]}?"
249
+ return APPROVAL_TITLES.get(act, "Do this now?")
250
+
251
+
252
+ def action_risk_tier(operation, target_label="", goal=""):
253
+ """Risk tier for one action. Side-effect parse follows the AUTHORITY_RULES spirit."""
254
+ if operation == "TYPE_SUBMIT":
255
+ # Typing plus Return: a search or address stays TYPE-tier; a send is a side effect.
256
+ if SIDE_EFFECT_CONTROL.search(target_label or "") or SIDE_EFFECT_REQUEST.search(goal or ""):
257
+ return RiskTier.SIDE_EFFECT
258
+ return RiskTier.TYPE
259
+ if operation in {"TYPE", "INCREMENT", "DECREMENT"}:
260
+ return RiskTier.TYPE
261
+ if operation in {"SWIPE_UP", "SWIPE_DOWN"}:
262
+ return RiskTier.SCROLL
263
+ # Horizontal swipes can page media or reveal row actions, so they stay navigation.
264
+ if operation in {"SWIPE_LEFT", "SWIPE_RIGHT", "BACK", "HOME",
265
+ "VOLUME_UP", "VOLUME_DOWN", "LAUNCH_APP"}:
266
+ return RiskTier.NAVIGATION
267
+ if SIDE_EFFECT_CONTROL.search(target_label or "") or SIDE_EFFECT_REQUEST.search(goal or ""):
268
+ return RiskTier.SIDE_EFFECT
269
+ return RiskTier.NAVIGATION
270
+
271
+
272
+ # The verifier's UNCLEAR ends a run; the request's own words are evidence that the act it names is the right one.
273
+ PROHIBITION_SENTENCE = re.compile(r"[^.!?]*\b(?:do not|don't|never|without|only look)\b[^.!?]*(?:[.!?]|$)", re.I)
274
+ CHANGE_REQUEST = re.compile(
275
+ r"\b(add|log|create|write|compose|draft|send|reply|message|share|post|book|reserve|order|buy|purchase|"
276
+ r"pay|transfer|set|schedule|change|update|edit|rename|move|archive|delete|remove|mark|flag|star|save|"
277
+ r"favorite|like|follow|connect|invite|accept|decline|join|upload|enable|disable|turn on|turn off|apply)\b",
278
+ re.I)
279
+ _TEXT_ASKED = re.compile(r"\b(write|type|enter|message|reply|note|comment|compose|add|search|rename|label|name|"
280
+ r"title|caption|describe|log|fill|draft|email|text)\b", re.I)
281
+
282
+
283
+ def asked_part(request):
284
+ """The request without its prohibitions ("Do not delete anything" asks for no deletion)."""
285
+ return PROHIBITION_SENTENCE.sub(" ", request or "")
286
+
287
+
288
+ def asks_for_changes(request):
289
+ return bool(CHANGE_REQUEST.search(asked_part(request)))
290
+
291
+
292
+ def _inflections(word):
293
+ word = word.casefold()
294
+ stem = word[:-1] if word.endswith("e") else word
295
+ return {word, word + "s", word + "d", word + "ed", stem + "ing", stem + "ed", word + "ment", stem + "ion"}
296
+
297
+
298
+ # Acts that are the same request in other words.
299
+ ACT_SYNONYMS = (
300
+ {"order", "checkout", "purchase", "buy", "pay", "place"},
301
+ {"book", "reserve", "reservation", "confirm"},
302
+ {"send", "message", "post", "reply", "share"},
303
+ {"request", "split", "charge"},
304
+ {"add", "create", "new", "save", "log"},
305
+ {"archive", "file"},
306
+ {"delete", "remove", "trash"},
307
+ )
308
+
309
+
310
+ def synonyms(act):
311
+ """Every form of the other words for the same act (see ACT_SYNONYMS)."""
312
+ out = set()
313
+ for group in ACT_SYNONYMS:
314
+ if act in group or act.rstrip("s") in group:
315
+ for word in group:
316
+ out |= _inflections(word)
317
+ return out
318
+
319
+
320
+ def requested_by(request, operation, role, label):
321
+ """Whether this action is one the request's own words ask for (never for a look-only request)."""
322
+ if not asks_for_changes(request):
323
+ return False
324
+ asked = asked_part(request).casefold()
325
+ words = set(re.findall(r"[a-z]+", asked))
326
+ if operation in {"TYPE", "TYPE_SUBMIT", "SUBMIT"} and role == "SearchField":
327
+ return True
328
+ if operation in {"TYPE", "TYPE_SUBMIT"}:
329
+ return bool(_TEXT_ASKED.search(asked))
330
+ if operation not in {"TAP", "SUBMIT"} or role not in NAVIGATION_TAP_ROLES:
331
+ return False
332
+ acts = {m.group(0).casefold() for m in re.finditer(r"[A-Za-z]+", label or "")
333
+ if STATE_CHANGING_LABEL.fullmatch(m.group(0)) or SIDE_EFFECT_CONTROL.fullmatch(m.group(0))
334
+ or COMMIT_CONTROL.fullmatch(m.group(0)) or CHANGE_REQUEST.fullmatch(m.group(0))}
335
+ if not acts:
336
+ return True
337
+ return all((_inflections(act) | synonyms(act)) & words for act in acts)
@@ -0,0 +1,34 @@
1
+ """Pro without Xcode, for a paid Apple Developer team (PRO-WITHOUT-XCODE.md, 18 Oct 2026).
2
+
3
+ Mobster signs its helper (the WebDriverAgent runner) on this Mac and installs it with open-source device tooling,
4
+ so a phone-station Mac needs no Xcode:
5
+
6
+ - ``writes``: the three App Store Connect writes (register an iPhone, create a development certificate, create a
7
+ development profile) as a card the person approves, with the ledger (``ledger``) that makes each attempt durable.
8
+ - ``identity``: the certificate signing request and the signing key, kept in a Keychain only Mobster opens.
9
+ - ``resign``: ``codesign`` of the prebuilt runner with the team's profile, and the check that it verifies.
10
+ - ``tooling``: install and launch the signed runner with pymobiledevice3 (go-ios as the fallback), never ``xcodebuild``.
11
+ - ``bundle``: Path C, a signed runner and its profile exported from one Mac and imported on another.
12
+
13
+ Off unless ``MOBSTER_EXPERIMENTAL_NO_XCODE=1``. Free Apple Accounts keep today's Xcode flow. Nothing here uses
14
+ Apple's private sign-in endpoints or changes how the phone looks to an app.
15
+ """
16
+
17
+ import os
18
+ from pathlib import Path
19
+
20
+ FLAG = "MOBSTER_EXPERIMENTAL_NO_XCODE"
21
+
22
+
23
+ def enabled(environ=None):
24
+ """Whether signing without Xcode is on (``MOBSTER_EXPERIMENTAL_NO_XCODE=1``)."""
25
+ environ = os.environ if environ is None else environ
26
+ return str(environ.get(FLAG, "")).strip().casefold() in ("1", "true", "yes", "on")
27
+
28
+
29
+ def state_dir(data_dir=None):
30
+ """Where this feature keeps its files (the ledger, the certificate, the signed runners): a folder only you can
31
+ open, in Mobster's data folder."""
32
+ from .. import env_file, paths
33
+
34
+ return env_file.private_dir(Path(data_dir) if data_dir is not None else paths.user_data_dir()) / "team-signing"