pymobiledevice3 6.2.0__tar.gz → 7.0.1__tar.gz

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 (264) hide show
  1. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/workflows/python-app.yml +1 -1
  2. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/PKG-INFO +3 -2
  3. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/__main__.py +139 -44
  4. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/_version.py +3 -3
  5. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/bonjour.py +19 -20
  6. pymobiledevice3-7.0.1/pymobiledevice3/cli/activation.py +46 -0
  7. pymobiledevice3-7.0.1/pymobiledevice3/cli/afc.py +64 -0
  8. pymobiledevice3-7.0.1/pymobiledevice3/cli/amfi.py +33 -0
  9. pymobiledevice3-7.0.1/pymobiledevice3/cli/apps.py +104 -0
  10. pymobiledevice3-7.0.1/pymobiledevice3/cli/backup.py +241 -0
  11. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/bonjour.py +31 -29
  12. pymobiledevice3-7.0.1/pymobiledevice3/cli/cli_common.py +313 -0
  13. pymobiledevice3-7.0.1/pymobiledevice3/cli/companion_proxy.py +22 -0
  14. pymobiledevice3-7.0.1/pymobiledevice3/cli/crash.py +131 -0
  15. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/__init__.py +62 -0
  16. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  17. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  18. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/arbitration.py +50 -0
  19. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/condition.py +33 -0
  20. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/core_device.py +294 -0
  21. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/debugserver.py +244 -0
  22. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/dvt/__init__.py +387 -0
  23. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  24. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  25. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  26. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  27. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  28. pymobiledevice3-7.0.1/pymobiledevice3/cli/developer/simulate_location.py +51 -0
  29. pymobiledevice3-7.0.1/pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  30. pymobiledevice3-7.0.1/pymobiledevice3/cli/diagnostics/battery.py +47 -0
  31. pymobiledevice3-7.0.1/pymobiledevice3/cli/idam.py +42 -0
  32. pymobiledevice3-7.0.1/pymobiledevice3/cli/lockdown.py +190 -0
  33. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/mounter.py +99 -57
  34. pymobiledevice3-7.0.1/pymobiledevice3/cli/notification.py +68 -0
  35. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/pcap.py +36 -20
  36. pymobiledevice3-7.0.1/pymobiledevice3/cli/power_assertion.py +26 -0
  37. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/processes.py +11 -17
  38. pymobiledevice3-7.0.1/pymobiledevice3/cli/profile.py +236 -0
  39. pymobiledevice3-7.0.1/pymobiledevice3/cli/provision.py +59 -0
  40. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/remote.py +108 -99
  41. pymobiledevice3-7.0.1/pymobiledevice3/cli/restore.py +252 -0
  42. pymobiledevice3-7.0.1/pymobiledevice3/cli/springboard.py +90 -0
  43. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/syslog.py +138 -74
  44. pymobiledevice3-7.0.1/pymobiledevice3/cli/usbmux.py +108 -0
  45. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/version.py +2 -5
  46. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/webinspector.py +149 -103
  47. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/exceptions.py +4 -1
  48. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/lockdown.py +1 -1
  49. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/remote_service_discovery.py +14 -10
  50. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/device.py +28 -4
  51. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/service_connection.py +1 -1
  52. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/mobilebackup2.py +4 -1
  53. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/screenshot.py +2 -2
  54. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/automation_session.py +4 -2
  55. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
  56. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/element.py +3 -3
  57. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3.egg-info/PKG-INFO +3 -2
  58. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3.egg-info/SOURCES.txt +16 -3
  59. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3.egg-info/requires.txt +2 -1
  60. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pyproject.toml +20 -6
  61. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pytest.ini +1 -0
  62. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/requirements.txt +2 -1
  63. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/cli/test_cli.py +4 -4
  64. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/conftest.py +3 -7
  65. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/instruments/test_core_profile_session.py +2 -1
  66. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/instruments/test_dvt_secure_socket_proxy.py +13 -14
  67. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/instruments/test_fetch_symbols.py +4 -5
  68. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/instruments/test_location.py +4 -5
  69. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/instruments/test_screenshot.py +2 -1
  70. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_accessibility.py +4 -3
  71. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_afc.py +31 -30
  72. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_apps.py +3 -2
  73. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_backup2.py +3 -3
  74. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_bonjour.py +4 -3
  75. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_crash_reports.py +9 -9
  76. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_house_arrest.py +1 -1
  77. pymobiledevice3-7.0.1/tests/services/test_list_devices.py +6 -0
  78. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_lockdown.py +3 -1
  79. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_pcapd.py +2 -1
  80. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_springboard_services_relay.py +4 -3
  81. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_start_tunnel.py +1 -1
  82. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_syslog_relay.py +2 -1
  83. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_tcp_forwarder.py +2 -2
  84. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_web_protocol/test_element.py +3 -2
  85. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_webinspector.py +2 -1
  86. pymobiledevice3-6.2.0/pymobiledevice3/cli/activation.py +0 -44
  87. pymobiledevice3-6.2.0/pymobiledevice3/cli/afc.py +0 -56
  88. pymobiledevice3-6.2.0/pymobiledevice3/cli/amfi.py +0 -38
  89. pymobiledevice3-6.2.0/pymobiledevice3/cli/apps.py +0 -98
  90. pymobiledevice3-6.2.0/pymobiledevice3/cli/backup.py +0 -200
  91. pymobiledevice3-6.2.0/pymobiledevice3/cli/cli_common.py +0 -366
  92. pymobiledevice3-6.2.0/pymobiledevice3/cli/companion_proxy.py +0 -22
  93. pymobiledevice3-6.2.0/pymobiledevice3/cli/completions.py +0 -50
  94. pymobiledevice3-6.2.0/pymobiledevice3/cli/crash.py +0 -88
  95. pymobiledevice3-6.2.0/pymobiledevice3/cli/developer.py +0 -1645
  96. pymobiledevice3-6.2.0/pymobiledevice3/cli/diagnostics.py +0 -110
  97. pymobiledevice3-6.2.0/pymobiledevice3/cli/idam.py +0 -46
  98. pymobiledevice3-6.2.0/pymobiledevice3/cli/lockdown.py +0 -195
  99. pymobiledevice3-6.2.0/pymobiledevice3/cli/notification.py +0 -56
  100. pymobiledevice3-6.2.0/pymobiledevice3/cli/power_assertion.py +0 -27
  101. pymobiledevice3-6.2.0/pymobiledevice3/cli/profile.py +0 -191
  102. pymobiledevice3-6.2.0/pymobiledevice3/cli/provision.py +0 -58
  103. pymobiledevice3-6.2.0/pymobiledevice3/cli/restore.py +0 -247
  104. pymobiledevice3-6.2.0/pymobiledevice3/cli/springboard.py +0 -90
  105. pymobiledevice3-6.2.0/pymobiledevice3/cli/usbmux.py +0 -69
  106. pymobiledevice3-6.2.0/tests/services/test_list_devices.py +0 -5
  107. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.gitattributes +0 -0
  108. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/FUNDING.yml +0 -0
  109. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  110. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  111. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  112. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/pull_request_template.md +0 -0
  113. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/workflows/codeql.yml +0 -0
  114. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/workflows/generate-executable.py +0 -0
  115. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/workflows/markdown-lint.yml +0 -0
  116. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.github/workflows/python-publish.yml +0 -0
  117. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.gitignore +0 -0
  118. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/.pre-commit-config.yaml +0 -0
  119. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/CODE_OF_CONDUCT.md +0 -0
  120. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/CONTRIBUTING.md +0 -0
  121. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/LICENSE +0 -0
  122. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/README.md +0 -0
  123. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/example.gif +0 -0
  124. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/markdownlint-config.json +0 -0
  125. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/DTServices-14.2.txt +0 -0
  126. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/DTServices-14.5.txt +0 -0
  127. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/RemoteXPC.md +0 -0
  128. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/plist_sniffer.py +0 -0
  129. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/pyinstaller.md +0 -0
  130. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/remotexpc_sniffer.py +0 -0
  131. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/understanding_idevice_protocol_layers.md +0 -0
  132. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/misc/usbmux_sniff.sh +0 -0
  133. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/__init__.py +0 -0
  134. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/ca.py +0 -0
  135. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/cli/__init__.py +0 -0
  136. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/common.py +0 -0
  137. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/irecv.py +0 -0
  138. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/irecv_devices.py +0 -0
  139. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/lockdown_service_provider.py +0 -0
  140. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/osu/__init__.py +0 -0
  141. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/osu/os_utils.py +0 -0
  142. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/osu/posix_util.py +0 -0
  143. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/osu/win_util.py +0 -0
  144. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/pair_records.py +0 -0
  145. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/__init__.py +0 -0
  146. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/common.py +0 -0
  147. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/core_device/__init__.py +0 -0
  148. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/core_device/app_service.py +0 -0
  149. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/core_device/core_device_service.py +0 -0
  150. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/core_device/device_info.py +0 -0
  151. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/core_device/diagnostics_service.py +0 -0
  152. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/core_device/file_service.py +0 -0
  153. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/module_imports.py +0 -0
  154. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/remote_service.py +0 -0
  155. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/remotexpc.py +0 -0
  156. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/tunnel_service.py +0 -0
  157. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/utils.py +0 -0
  158. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/remote/xpc_message.py +0 -0
  159. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/__init__.py +0 -0
  160. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/dsc_uuid_map.json +0 -0
  161. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/dsc_uuid_map.py +0 -0
  162. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/firmware_notifications.py +0 -0
  163. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/notifications.txt +0 -0
  164. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/element_attribute.js +0 -0
  165. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/element_clear.js +0 -0
  166. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/enter_fullscreen.js +0 -0
  167. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/find_nodes.js +0 -0
  168. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/focus.js +0 -0
  169. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/get_attribute.js +0 -0
  170. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/is_displayed.js +0 -0
  171. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/is_editable.js +0 -0
  172. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/resources/webinspector/is_enabled.js +0 -0
  173. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/__init__.py +0 -0
  174. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/asr.py +0 -0
  175. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/base_restore.py +0 -0
  176. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/consts.py +0 -0
  177. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/fdr.py +0 -0
  178. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/ftab.py +0 -0
  179. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/img4.py +0 -0
  180. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/mbn.py +0 -0
  181. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/recovery.py +0 -0
  182. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/restore.py +0 -0
  183. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/restore_options.py +0 -0
  184. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/restored_client.py +0 -0
  185. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/restore/tss.py +0 -0
  186. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/__init__.py +0 -0
  187. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/accessibilityaudit.py +0 -0
  188. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/afc.py +0 -0
  189. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/amfi.py +0 -0
  190. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/companion.py +0 -0
  191. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/crash_reports.py +0 -0
  192. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/debugserver_applist.py +0 -0
  193. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/device_arbitration.py +0 -0
  194. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/device_link.py +0 -0
  195. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/diagnostics.py +0 -0
  196. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dtfetchsymbols.py +0 -0
  197. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/__init__.py +0 -0
  198. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +0 -0
  199. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +0 -0
  200. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/__init__.py +0 -0
  201. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +0 -0
  202. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/application_listing.py +0 -0
  203. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/condition_inducer.py +0 -0
  204. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +0 -0
  205. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/device_info.py +0 -0
  206. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/energy_monitor.py +0 -0
  207. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/graphics.py +0 -0
  208. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/location_simulation.py +0 -0
  209. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/location_simulation_base.py +0 -0
  210. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/network_monitor.py +0 -0
  211. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/notifications.py +0 -0
  212. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/process_control.py +0 -0
  213. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/screenshot.py +0 -0
  214. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/instruments/sysmontap.py +0 -0
  215. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/dvt/testmanaged/xcuitest.py +0 -0
  216. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/file_relay.py +0 -0
  217. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/heartbeat.py +0 -0
  218. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/house_arrest.py +0 -0
  219. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/idam.py +0 -0
  220. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/installation_proxy.py +0 -0
  221. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/lockdown_service.py +0 -0
  222. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/misagent.py +0 -0
  223. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/mobile_activation.py +0 -0
  224. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/mobile_config.py +0 -0
  225. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/mobile_image_mounter.py +0 -0
  226. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/notification_proxy.py +0 -0
  227. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/os_trace.py +0 -0
  228. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/pcapd.py +0 -0
  229. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/power_assertion.py +0 -0
  230. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/preboard.py +0 -0
  231. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/remote_fetch_symbols.py +0 -0
  232. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/remote_server.py +0 -0
  233. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/restore_service.py +0 -0
  234. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/simulate_location.py +0 -0
  235. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/springboard.py +0 -0
  236. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/syslog.py +0 -0
  237. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/__init__.py +0 -0
  238. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/alert.py +0 -0
  239. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/cdp_server.py +0 -0
  240. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/cdp_target.py +0 -0
  241. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/driver.py +0 -0
  242. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/inspector_session.py +0 -0
  243. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/selenium_api.py +0 -0
  244. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/session_protocol.py +0 -0
  245. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/web_protocol/switch_to.py +0 -0
  246. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/services/webinspector.py +0 -0
  247. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/tcp_forwarder.py +0 -0
  248. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/tunneld/__init__.py +0 -0
  249. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/tunneld/api.py +0 -0
  250. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/tunneld/server.py +0 -0
  251. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/usbmux.py +0 -0
  252. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3/utils.py +0 -0
  253. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3.egg-info/dependency_links.txt +0 -0
  254. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3.egg-info/entry_points.txt +0 -0
  255. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/pymobiledevice3.egg-info/top_level.txt +0 -0
  256. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/setup.cfg +0 -0
  257. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/__init__.py +0 -0
  258. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/cli/__init__.py +0 -0
  259. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/__init__.py +0 -0
  260. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/instruments/__init__.py +0 -0
  261. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_web_protocol/__init__.py +0 -0
  262. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_web_protocol/common.py +0 -0
  263. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_web_protocol/conftest.py +0 -0
  264. {pymobiledevice3-6.2.0 → pymobiledevice3-7.0.1}/tests/services/test_web_protocol/test_driver.py +0 -0
@@ -22,7 +22,7 @@ jobs:
22
22
 
23
23
  strategy:
24
24
  matrix:
25
- python-version: [ 3.9, "3.10", "3.11", "3.12", "3.13", "3.14" ]
25
+ python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ]
26
26
  os: [ ubuntu-latest, macos-latest, windows-latest ]
27
27
 
28
28
  steps:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymobiledevice3
3
- Version: 6.2.0
3
+ Version: 7.0.1
4
4
  Summary: Pure python3 implementation for working with iDevices (iPhone, etc...)
5
5
  Author-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
6
6
  Maintainer-email: doronz88 <doron88@gmail.com>, matan <matan1008@gmail.com>
@@ -23,7 +23,6 @@ License-File: LICENSE
23
23
  Requires-Dist: construct>=2.9.29
24
24
  Requires-Dist: construct-typing>=0.7.0
25
25
  Requires-Dist: asn1
26
- Requires-Dist: click
27
26
  Requires-Dist: coloredlogs
28
27
  Requires-Dist: IPython
29
28
  Requires-Dist: bpylist2>=4.0.1
@@ -61,6 +60,8 @@ Requires-Dist: sslpsk-pmd3>=1.0.3; python_version < "3.13"
61
60
  Requires-Dist: python-pcapng>=2.1.1
62
61
  Requires-Dist: plumbum
63
62
  Requires-Dist: pyimg4>=0.8.8
63
+ Requires-Dist: typer>=0.20.0
64
+ Requires-Dist: typer-injector>=0.2.0
64
65
  Provides-Extra: test
65
66
  Requires-Dist: pytest; extra == "test"
66
67
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import difflib
3
+ import importlib
3
4
  import logging
4
5
  import os
5
6
  import re
@@ -7,13 +8,18 @@ import sys
7
8
  import textwrap
8
9
  import traceback
9
10
  import warnings
10
- from typing import Union
11
+ from collections.abc import Sequence
12
+ from typing import Annotated, Optional, Union
11
13
 
12
14
  import click
13
15
  import coloredlogs
16
+ import typer
17
+ import typer.core
14
18
  from packaging.version import Version
19
+ from typer.core import TyperGroup
20
+ from typer_injector import InjectingTyper
15
21
 
16
- from pymobiledevice3.cli.cli_common import TUNNEL_ENV_VAR, isatty
22
+ from pymobiledevice3.cli.cli_common import TUNNEL_ENV_VAR, isatty, set_color_flag, set_verbosity
17
23
  from pymobiledevice3.exceptions import (
18
24
  AccessDeniedError,
19
25
  CloudConfigurationAlreadyPresentError,
@@ -38,6 +44,7 @@ from pymobiledevice3.exceptions import (
38
44
  QuicProtocolNotSupportedError,
39
45
  RSDRequiredError,
40
46
  SetProhibitedError,
47
+ StartServiceError,
41
48
  TunneldConnectionError,
42
49
  UserDeniedPairingError,
43
50
  )
@@ -112,25 +119,26 @@ CLI_GROUPS = {
112
119
  "webinspector": "webinspector",
113
120
  "idam": "idam",
114
121
  "version": "version",
115
- "install-completions": "completions",
116
122
  }
117
123
 
118
124
  # Set if used the `--reconnect` option
119
125
  RECONNECT = False
120
126
 
121
127
 
122
- class Pmd3Cli(click.Group):
123
- def list_commands(self, ctx):
124
- return CLI_GROUPS.keys()
128
+ class Pmd3TyperGroup(TyperGroup):
129
+ def list_commands(self, ctx: click.Context) -> list[str]:
130
+ # Order is preserved by dict insertion; adjust if you want alphabetical
131
+ return list(CLI_GROUPS.keys())
125
132
 
126
- def get_command(self, ctx: click.Context, name: str) -> click.Command:
127
- if name not in CLI_GROUPS:
128
- self.handle_invalid_command(ctx, name)
129
- return self.import_and_get_command(ctx, name)
133
+ def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command:
134
+ if cmd_name not in CLI_GROUPS:
135
+ self.handle_invalid_command(ctx, cmd_name)
136
+ return self.import_and_get_command(ctx, cmd_name)
130
137
 
131
- def handle_invalid_command(self, ctx: click.Context, name: str) -> None:
138
+ def handle_invalid_command(self, ctx, name: str) -> None:
132
139
  suggested_commands = self.search_commands(name)
133
140
  suggestion = self.format_suggestions(suggested_commands)
141
+ # ctx.fail raises a ClickException underneath, which Typer displays nicely
134
142
  ctx.fail(f"No such command {name!r}{suggestion}")
135
143
 
136
144
  @staticmethod
@@ -138,70 +146,109 @@ class Pmd3Cli(click.Group):
138
146
  if not suggestions:
139
147
  return ""
140
148
  cmds = textwrap.indent("\n".join(suggestions), " " * 4)
141
- return f"\nDid you mean this?\n{cmds}"
149
+ return f"\nDid you mean:\n{cmds}"
142
150
 
143
151
  @staticmethod
144
152
  def import_and_get_command(ctx: click.Context, name: str) -> click.Command:
145
153
  module_name = f"pymobiledevice3.cli.{CLI_GROUPS[name]}"
146
- mod = __import__(module_name, None, None, ["cli"])
147
- command = mod.cli.get_command(ctx, name)
148
- if not command:
149
- command_name = mod.cli.list_commands(ctx)[0]
150
- command = mod.cli.get_command(ctx, command_name)
151
- return command
154
+ mod = importlib.import_module(module_name)
155
+ # submodules expose a Typer Group named "cli"
156
+ cli: typer.Typer = mod.cli
157
+ return typer.main.get_command(cli)
152
158
 
153
159
  @staticmethod
154
160
  def highlight_keyword(text: str, keyword: str) -> str:
155
- return re.sub(f"({keyword})", click.style("\\1", bold=True), text, flags=re.IGNORECASE)
161
+ return re.sub(f"({keyword})", typer.style("\\1", bold=True), text, flags=re.IGNORECASE)
156
162
 
157
163
  @staticmethod
158
- def collect_commands(command: click.Command) -> Union[str, list[str]]:
159
- commands = []
160
- if isinstance(command, click.Group):
161
- for _k, v in command.commands.items():
162
- cmd = Pmd3Cli.collect_commands(v)
163
- if isinstance(cmd, list):
164
- commands.extend([f"{command.name} {c}" for c in cmd])
164
+ def collect_commands(command: Union[TyperGroup, click.Command]) -> Union[str, list[str]]:
165
+ if isinstance(command, TyperGroup): # group
166
+ cmds = []
167
+ for v in command.commands.values():
168
+ child = Pmd3TyperGroup.collect_commands(v)
169
+ if isinstance(child, list):
170
+ cmds.extend([f"{command.name} {c}" for c in child])
165
171
  else:
166
- commands.append(f"{command.name} {cmd}")
167
- return commands
168
- return f"{command.name}"
172
+ cmds.append(f"{command.name} {child}")
173
+ return cmds
174
+ return command.name or ""
169
175
 
170
176
  @staticmethod
171
177
  def search_commands(pattern: str) -> list[str]:
172
- all_commands = Pmd3Cli.load_all_commands()
178
+ all_commands = Pmd3TyperGroup.load_all_commands()
173
179
  matched = sorted(filter(lambda cmd: re.search(pattern, cmd), all_commands))
174
180
  if not matched:
175
181
  matched = difflib.get_close_matches(pattern, all_commands, n=20, cutoff=0.4)
176
182
  if isatty():
177
- matched = [Pmd3Cli.highlight_keyword(cmd, pattern) for cmd in matched]
183
+ matched = [Pmd3TyperGroup.highlight_keyword(cmd, pattern) for cmd in matched]
178
184
  return matched
179
185
 
180
186
  @staticmethod
181
187
  def load_all_commands() -> list[str]:
182
- all_commands = []
188
+ all_commands: list[str] = []
183
189
  for key in CLI_GROUPS:
184
190
  module_name = f"pymobiledevice3.cli.{CLI_GROUPS[key]}"
185
- mod = __import__(module_name, None, None, ["cli"])
186
- cmd = Pmd3Cli.collect_commands(mod.cli.commands[key])
191
+ mod = importlib.import_module(module_name)
192
+ if isinstance(mod.cli, typer.Typer):
193
+ cmd = Pmd3TyperGroup.collect_commands(typer.main.get_group(mod.cli))
194
+ else:
195
+ cmd = Pmd3TyperGroup.collect_commands(mod.cli.commands[key])
187
196
  if isinstance(cmd, list):
188
197
  all_commands.extend(cmd)
189
198
  else:
190
199
  all_commands.append(cmd)
191
200
  return all_commands
192
201
 
202
+ def resolve_command(
203
+ self, ctx: click.Context, args: list[str]
204
+ ) -> tuple[Optional[str], Optional[click.Command], list[str]]:
205
+ return super().resolve_command(ctx, args)
206
+
207
+
208
+ app = InjectingTyper(
209
+ cls=Pmd3TyperGroup,
210
+ context_settings=CONTEXT_SETTINGS,
211
+ no_args_is_help=True,
212
+ # add_completion=False,
213
+ rich_markup_mode="markdown",
214
+ help=(
215
+ "Swiss-army CLI for pairing, inspecting, backing up, and automating iOS devices.\n\n"
216
+ "Docs and examples: https://github.com/doronz88/pymobiledevice3"
217
+ ),
218
+ )
219
+
193
220
 
194
- @click.command(cls=Pmd3Cli, context_settings=CONTEXT_SETTINGS)
195
- @click.option("--reconnect", is_flag=True, default=False, help="Reconnect to device when disconnected.")
196
- def cli(reconnect: bool) -> None:
221
+ @app.callback()
222
+ def _root(
223
+ reconnect: Annotated[
224
+ bool,
225
+ typer.Option(
226
+ "--reconnect",
227
+ help="Automatically reconnect if the device disconnects mid-command.",
228
+ show_default=False,
229
+ ),
230
+ ] = False,
231
+ verbosity: Annotated[
232
+ int,
233
+ typer.Option(
234
+ "--verbose",
235
+ "-v",
236
+ count=True,
237
+ help="Increase logging verbosity (repeat for more detail).",
238
+ ),
239
+ ] = 0,
240
+ color: Annotated[
241
+ bool,
242
+ typer.Option(help="Colorize output; disable with --no-color for plain logs."),
243
+ ] = True,
244
+ ) -> None:
197
245
  """
198
- \b
199
- Interact with a connected iDevice (iPhone, iPad, ...)
200
- For more information please look at:
201
- https://github.com/doronz88/pymobiledevice3
246
+ Top-level options for pymobiledevice3.
202
247
  """
203
248
  global RECONNECT
204
249
  RECONNECT = reconnect
250
+ set_verbosity(verbosity)
251
+ set_color_flag(color)
205
252
 
206
253
 
207
254
  def device_might_need_tunneld(identifier: str) -> bool:
@@ -221,13 +268,53 @@ def device_might_need_tunneld(identifier: str) -> bool:
221
268
  return Version(lockdown.product_version) >= Version("17.0")
222
269
 
223
270
 
271
+ class PossiblyMisplacedOption(click.NoSuchOption):
272
+ def __init__(
273
+ self,
274
+ option_name: str,
275
+ message: Optional[str] = None,
276
+ possibilities: Optional[Sequence[str]] = None,
277
+ ctx: Optional[click.Context] = None,
278
+ suggested_ctx: Optional[click.Context] = None,
279
+ ) -> None:
280
+ super().__init__(option_name, message, possibilities, ctx)
281
+ if suggested_ctx is not None:
282
+ if ctx is not None:
283
+ self.message += f" for subcommand: {ctx.command_path}"
284
+
285
+ suggestion = f"{suggested_ctx.command_path} {option_name}"
286
+ suggestion += ctx.command_path.removeprefix(suggested_ctx.command_path) if ctx is not None else " ..."
287
+
288
+ self.message += f"\nDid you mean: {suggestion}?"
289
+
290
+ @staticmethod
291
+ def from_no_such_option(e: click.NoSuchOption) -> "PossiblyMisplacedOption":
292
+ ctx = e.ctx
293
+ while ctx:
294
+ for param in ctx.command.params:
295
+ if isinstance(param, typer.core.TyperOption) and (
296
+ e.option_name in param.opts or e.option_name in param.secondary_opts
297
+ ):
298
+ break
299
+ else:
300
+ ctx = ctx.parent
301
+ continue
302
+ break
303
+
304
+ return PossiblyMisplacedOption(e.option_name, e.message, e.possibilities, e.ctx, ctx)
305
+
306
+
224
307
  def invoke_cli_with_error_handling() -> bool:
225
308
  """
226
309
  Invoke the command line interface and return `True` if the failure reason of the command was that the device was
227
310
  disconnected.
228
311
  """
229
312
  try:
230
- cli()
313
+ # Typer apps are callable; this executes the CLI with current sys.argv
314
+ try:
315
+ app(standalone_mode=False)
316
+ except click.NoSuchOption as e:
317
+ raise PossiblyMisplacedOption.from_no_such_option(e) from e
231
318
  except NoDeviceConnectedError:
232
319
  logger.error("Device is not connected")
233
320
  return True
@@ -277,9 +364,10 @@ def invoke_cli_with_error_handling() -> bool:
277
364
  logger.warning("Got an InvalidServiceError. Trying again over tunneld since it is a developer command")
278
365
  should_retry_over_tunneld = True
279
366
  if should_retry_over_tunneld:
280
- # use a single space because click will ignore envvars of empty strings
367
+ # use a single space because Typer/Click will ignore envvars of empty strings
281
368
  os.environ[TUNNEL_ENV_VAR] = e.identifier or " "
282
- return main()
369
+ main()
370
+ return False
283
371
  logger.error(INVALID_SERVICE_MESSAGE)
284
372
  except PasswordRequiredError:
285
373
  logger.error("Device is password protected. Please unlock and retry")
@@ -315,6 +403,13 @@ def invoke_cli_with_error_handling() -> bool:
315
403
  )
316
404
  except QuicProtocolNotSupportedError:
317
405
  logger.error("Encountered a QUIC protocol error.")
406
+ except StartServiceError as e:
407
+ logger.error(f"Failed to start: {e.service_name} with. Received error: {e.message}.")
408
+ except click.ClickException as e:
409
+ from typer import rich_utils
410
+
411
+ rich_utils.rich_format_error(e)
412
+ sys.exit(e.exit_code)
318
413
 
319
414
  return False
320
415
 
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '6.2.0'
32
- __version_tuple__ = version_tuple = (6, 2, 0)
31
+ __version__ = version = '7.0.1'
32
+ __version_tuple__ = version_tuple = (7, 0, 1)
33
33
 
34
- __commit_id__ = commit_id = 'g54f93648f'
34
+ __commit_id__ = commit_id = 'g7ca380f05'
@@ -300,10 +300,9 @@ async def browse_service(service_type: str, timeout: float = 4.0) -> list[Servic
300
300
  adapters = _Adapters()
301
301
 
302
302
  ptr_targets: set[str] = set()
303
- srv_map: dict[str, dict] = {}
303
+ srv_map: dict[str, list[dict]] = defaultdict(list) # instance_name -> list of {"target", "port"}
304
304
  txt_map: dict[str, dict] = {}
305
- # host -> list[(ip, iface)]
306
- host_addrs: dict[str, list[Address]] = defaultdict(list)
305
+ host_addrs: dict[str, list[Address]] = defaultdict(list) # host -> list[(ip, iface)]
307
306
 
308
307
  def _record_addr(rr_name: str, ip_str: str, pkt_addr):
309
308
  # Determine family and possible scopeid from the packet that delivered this RR
@@ -314,7 +313,7 @@ async def browse_service(service_type: str, timeout: float = 4.0) -> list[Servic
314
313
  iface = adapters.pick_iface_for_ip(ip_str, family, scopeid)
315
314
  if iface is None:
316
315
  return
317
- # avoid duplicates for the same host/ip
316
+ # Avoid duplicates for the same host/ip
318
317
  existing = host_addrs[rr_name]
319
318
  if not any(a.ip == ip_str for a in existing):
320
319
  existing.append(Address(ip=ip_str, iface=iface))
@@ -333,11 +332,10 @@ async def browse_service(service_type: str, timeout: float = 4.0) -> list[Servic
333
332
  if t == QTYPE_PTR and rr.get("name") == service_type:
334
333
  ptr_targets.add(rr.get("ptrdname"))
335
334
  elif t == QTYPE_SRV:
336
- srv_map[rr["name"]] = {
337
- "target": rr.get("target"),
338
- "port": rr.get("port"),
339
- }
335
+ srv_map[rr["name"]].append({"target": rr.get("target"), "port": rr.get("port")})
340
336
  elif t == QTYPE_TXT:
337
+ # TODO: This could possibly mix the properties of multiple TXT records for the same instance.
338
+ # However, it's currently unused.
341
339
  txt_map[rr["name"]] = rr.get("txt", {})
342
340
  elif (t == QTYPE_A and rr.get("address")) or (t == QTYPE_AAAA and rr.get("address")):
343
341
  _record_addr(rr["name"], rr["address"], pkt_addr)
@@ -348,20 +346,21 @@ async def browse_service(service_type: str, timeout: float = 4.0) -> list[Servic
348
346
  # Assemble dataclasses
349
347
  results: list[ServiceInstance] = []
350
348
  for inst in sorted(ptr_targets):
351
- srv = srv_map.get(inst, {})
352
- target = srv.get("target")
353
- host = (target[:-1] if target and target.endswith(".") else target) or None
354
- addrs = host_addrs.get(target, []) if target else []
349
+ srv_entries = srv_map.get(inst, [])
355
350
  props = txt_map.get(inst, {})
356
- results.append(
357
- ServiceInstance(
358
- instance=inst,
359
- host=host,
360
- port=srv.get("port"),
361
- addresses=addrs,
362
- properties=props,
351
+ for srv in srv_entries:
352
+ target = srv.get("target")
353
+ host = (target[:-1] if target and target.endswith(".") else target) or None
354
+ addrs = host_addrs.get(target, []) if target else []
355
+ results.append(
356
+ ServiceInstance(
357
+ instance=inst,
358
+ host=host,
359
+ port=srv.get("port"),
360
+ addresses=addrs,
361
+ properties=props,
362
+ )
363
363
  )
364
- )
365
364
  return results
366
365
 
367
366
 
@@ -0,0 +1,46 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+ from typer_injector import InjectingTyper
5
+
6
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
7
+ from pymobiledevice3.services.mobile_activation import MobileActivationService
8
+
9
+ cli = InjectingTyper(
10
+ name="activation",
11
+ help="Perform iCloud activation/deactivation or query the current state",
12
+ no_args_is_help=True,
13
+ )
14
+
15
+
16
+ @cli.command()
17
+ def state(service_provider: ServiceProviderDep) -> None:
18
+ """Get current activation state"""
19
+ print(MobileActivationService(service_provider).state)
20
+
21
+
22
+ @cli.command()
23
+ def activate(
24
+ service_provider: ServiceProviderDep,
25
+ now: Annotated[
26
+ bool,
27
+ typer.Option(help="do not wait for next nonce cycle"),
28
+ ] = False,
29
+ ) -> None:
30
+ """Activate device"""
31
+ activation_service = MobileActivationService(service_provider)
32
+ if not now:
33
+ activation_service.wait_for_activation_session()
34
+ activation_service.activate()
35
+
36
+
37
+ @cli.command()
38
+ def deactivate(service_provider: ServiceProviderDep) -> None:
39
+ """Deactivate device"""
40
+ MobileActivationService(service_provider).deactivate()
41
+
42
+
43
+ @cli.command()
44
+ def itunes(service_provider: ServiceProviderDep) -> None:
45
+ """Tell the device that it has been connected to iTunes (useful for < iOS 4)"""
46
+ service_provider.set_value(True, key="iTunesHasConnected")
@@ -0,0 +1,64 @@
1
+ from pathlib import Path
2
+ from typing import Annotated
3
+
4
+ import typer
5
+ from typer_injector import InjectingTyper
6
+
7
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
8
+ from pymobiledevice3.services.afc import AfcService, AfcShell
9
+
10
+ cli = InjectingTyper(
11
+ name="afc",
12
+ help="Browse, push, and pull files via the AFC service (/var/mobile/Media).",
13
+ no_args_is_help=True,
14
+ )
15
+
16
+
17
+ @cli.command("shell")
18
+ def afc_shell(service_provider: ServiceProviderDep) -> None:
19
+ """Open an interactive AFC shell rooted at /var/mobile/Media."""
20
+ AfcShell.create(service_provider)
21
+
22
+
23
+ @cli.command("pull")
24
+ def afc_pull(
25
+ service_provider: ServiceProviderDep,
26
+ remote_file: Path,
27
+ local_file: Path,
28
+ ignore_errors: Annotated[
29
+ bool,
30
+ typer.Option(
31
+ "--ignore-errors",
32
+ "-i",
33
+ help="Continue downloading even if some files error (best-effort pull).",
34
+ ),
35
+ ],
36
+ ) -> None:
37
+ """Download a remote path under /var/mobile/Media to the local filesystem."""
38
+ AfcService(lockdown=service_provider).pull(str(remote_file), str(local_file), ignore_errors=ignore_errors)
39
+
40
+
41
+ @cli.command("push")
42
+ def afc_push(service_provider: ServiceProviderDep, local_file: Path, remote_file: Path) -> None:
43
+ """Upload a local file into /var/mobile/Media."""
44
+ AfcService(lockdown=service_provider).push(str(local_file), str(remote_file))
45
+
46
+
47
+ @cli.command("ls")
48
+ def afc_ls(
49
+ service_provider: ServiceProviderDep,
50
+ remote_file: Path,
51
+ recursive: Annotated[
52
+ bool,
53
+ typer.Option("--recursive", "-r", help="Recurse into subdirectories when listing."),
54
+ ] = False,
55
+ ) -> None:
56
+ """List files under /var/mobile/Media (optionally recursively)."""
57
+ for path in AfcService(lockdown=service_provider).dirlist(str(remote_file), -1 if recursive else 1):
58
+ print(path)
59
+
60
+
61
+ @cli.command("rm")
62
+ def afc_rm(service_provider: ServiceProviderDep, remote_file: Path) -> None:
63
+ """Delete a file under /var/mobile/Media."""
64
+ AfcService(lockdown=service_provider).rm(str(remote_file))
@@ -0,0 +1,33 @@
1
+ import logging
2
+
3
+ from typer_injector import InjectingTyper
4
+
5
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
6
+ from pymobiledevice3.services.amfi import AmfiService
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ cli = InjectingTyper(
12
+ name="amfi",
13
+ help="Enable developer-mode or query its state",
14
+ no_args_is_help=True,
15
+ )
16
+
17
+
18
+ @cli.command()
19
+ def reveal_developer_mode(service_provider: ServiceProviderDep) -> None:
20
+ """reveal developer mode option in device's UI"""
21
+ AmfiService(service_provider).reveal_developer_mode_option_in_ui()
22
+
23
+
24
+ @cli.command()
25
+ def enable_developer_mode(service_provider: ServiceProviderDep) -> None:
26
+ """enable developer mode"""
27
+ AmfiService(service_provider).enable_developer_mode()
28
+
29
+
30
+ @cli.command()
31
+ def developer_mode_status(service_provider: ServiceProviderDep) -> None:
32
+ """query developer mode status"""
33
+ print_json(service_provider.developer_mode_status)
@@ -0,0 +1,104 @@
1
+ from pathlib import Path
2
+ from typing import Annotated, Literal
3
+
4
+ import typer
5
+ from typer_injector import InjectingTyper
6
+
7
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
8
+ from pymobiledevice3.services.house_arrest import HouseArrestService
9
+ from pymobiledevice3.services.installation_proxy import InstallationProxyService
10
+
11
+ cli = InjectingTyper(
12
+ name="apps",
13
+ help="List, query, install, uninstall, and inspect apps on the device.",
14
+ no_args_is_help=True,
15
+ )
16
+
17
+
18
+ @cli.command("list")
19
+ def apps_list(
20
+ service_provider: ServiceProviderDep,
21
+ app_type: Annotated[
22
+ Literal["System", "User", "Hidden", "Any"],
23
+ typer.Option(
24
+ "--type",
25
+ "-t",
26
+ help="Filter by application type (System/User/Hidden/Any).",
27
+ ),
28
+ ] = "Any",
29
+ calculate_sizes: Annotated[
30
+ bool,
31
+ typer.Option(help="Include app size information (slower)."),
32
+ ] = False,
33
+ ) -> None:
34
+ """List installed apps."""
35
+ print_json(
36
+ InstallationProxyService(lockdown=service_provider).get_apps(
37
+ application_type=app_type, calculate_sizes=calculate_sizes
38
+ )
39
+ )
40
+
41
+
42
+ @cli.command("query")
43
+ def apps_query(
44
+ service_provider: ServiceProviderDep,
45
+ bundle_identifiers: list[str],
46
+ calculate_sizes: Annotated[
47
+ bool,
48
+ typer.Option(help="Include app size information (slower)."),
49
+ ] = False,
50
+ ) -> None:
51
+ """Return metadata for specific bundle identifiers."""
52
+ print_json(
53
+ InstallationProxyService(lockdown=service_provider).get_apps(
54
+ calculate_sizes=calculate_sizes, bundle_identifiers=bundle_identifiers
55
+ )
56
+ )
57
+
58
+
59
+ @cli.command("uninstall")
60
+ def uninstall(service_provider: ServiceProviderDep, bundle_id: str) -> None:
61
+ """Uninstall an app by bundle identifier."""
62
+ InstallationProxyService(lockdown=service_provider).uninstall(bundle_id)
63
+
64
+
65
+ @cli.command("install")
66
+ def install(
67
+ service_provider: ServiceProviderDep,
68
+ package: Annotated[
69
+ Path,
70
+ typer.Argument(exists=True),
71
+ ],
72
+ developer: Annotated[
73
+ bool,
74
+ typer.Option(help="Install developer package"),
75
+ ] = False,
76
+ ) -> None:
77
+ """Install a local .ipa/.app/.ipcc package."""
78
+ InstallationProxyService(lockdown=service_provider).install_from_local(package, developer=developer)
79
+
80
+
81
+ @cli.command("afc")
82
+ def afc(
83
+ service_provider: ServiceProviderDep, bundle_id: str, documents: Annotated[bool, typer.Option()] = False
84
+ ) -> None:
85
+ """Open an AFC shell into the app container; pass --documents for Documents-only."""
86
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id, documents_only=documents).shell()
87
+
88
+
89
+ @cli.command("pull")
90
+ def pull(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path, local_file: Path) -> None:
91
+ """Pull a file from an app container to a local path."""
92
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).pull(str(remote_file), str(local_file))
93
+
94
+
95
+ @cli.command("push")
96
+ def push(service_provider: ServiceProviderDep, bundle_id: str, local_file: Path, remote_file: Path) -> None:
97
+ """Push a local file into an app container."""
98
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).push(str(local_file), str(remote_file))
99
+
100
+
101
+ @cli.command("rm")
102
+ def rm(service_provider: ServiceProviderDep, bundle_id: str, remote_file: Path) -> None:
103
+ """Delete a file from an app container."""
104
+ HouseArrestService(lockdown=service_provider, bundle_id=bundle_id).rm(str(remote_file))