pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__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 (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/ca.py CHANGED
@@ -1,6 +1,6 @@
1
- from datetime import datetime, timedelta
1
+ from datetime import datetime, timedelta, timezone
2
2
  from pathlib import Path
3
- from typing import Optional
3
+ from typing import Optional, Union
4
4
 
5
5
  from cryptography import x509
6
6
  from cryptography.hazmat.primitives import hashes, serialization
@@ -10,8 +10,242 @@ from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption,
10
10
  from cryptography.x509 import Certificate
11
11
  from cryptography.x509.oid import NameOID
12
12
 
13
+ _SERIAL = 1
14
+
15
+
16
+ def select_hash_algorithm(device_version: Union[tuple[int, int, int], str, None]) -> hashes.HashAlgorithm:
17
+ """
18
+ Choose hash algorithm to match libimobiledevice (idevicepair) logic.
19
+
20
+ :param device_version: Device version tuple (major, minor, patch) or "a.b.c" string.
21
+ If None, defaults to SHA-256 (modern).
22
+ :returns: SHA-1 if version < 4.0.0, else SHA-256.
23
+ """
24
+ if device_version is None:
25
+ return hashes.SHA256()
26
+ parts = tuple(int(x) for x in device_version.split(".")) if isinstance(device_version, str) else device_version
27
+ return hashes.SHA1() if parts < (4, 0, 0) else hashes.SHA256()
28
+
29
+
30
+ def get_validity_bounds(years: int = 10) -> tuple[datetime, datetime]:
31
+ """
32
+ Compute notBefore / notAfter validity window.
33
+
34
+ :param years: Number of years for certificate validity.
35
+ :returns: (not_before, not_after) in UTC.
36
+ """
37
+ now = datetime.now(timezone.utc)
38
+ return now - timedelta(minutes=1), now + timedelta(days=365 * years)
39
+
40
+
41
+ def serialize_cert_pem(cert: Certificate) -> bytes:
42
+ """
43
+ Serialize an X.509 certificate in PEM format.
44
+
45
+ :param cert: Certificate object.
46
+ :returns: PEM-encoded certificate bytes.
47
+ """
48
+ return cert.public_bytes(Encoding.PEM)
49
+
50
+
51
+ def serialize_private_key_pkcs8_pem(key: RSAPrivateKey) -> bytes:
52
+ """
53
+ Serialize a private key in PKCS#8 PEM format (like OpenSSL's PEM_write_bio_PrivateKey).
54
+
55
+ :param key: RSA private key.
56
+ :returns: PEM-encoded PKCS#8 key bytes (unencrypted).
57
+ """
58
+ return key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
59
+
60
+
61
+ # =======================================
62
+ # Certificate builders (empty DN, v3, KU)
63
+ # =======================================
64
+
65
+
66
+ def build_root_certificate(root_key: RSAPrivateKey, alg: hashes.HashAlgorithm) -> Certificate:
67
+ """
68
+ Build a self-signed root (CA) certificate:
69
+ - Empty subject/issuer (x509.Name([]))
70
+ - Serial = 1
71
+ - X.509 v3 with BasicConstraints CA:TRUE (critical)
72
+ - Signed with root_key using the chosen hash
73
+
74
+ :param root_key: RSA private key for the root CA.
75
+ :param alg: Hash algorithm (SHA-1 or SHA-256).
76
+ :returns: Root CA certificate.
77
+ """
78
+ not_before, not_after = get_validity_bounds()
79
+ empty = x509.Name([])
80
+ builder = (
81
+ x509.CertificateBuilder()
82
+ .subject_name(empty)
83
+ .issuer_name(empty)
84
+ .public_key(root_key.public_key())
85
+ .serial_number(_SERIAL)
86
+ .not_valid_before(not_before)
87
+ .not_valid_after(not_after)
88
+ .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
89
+ )
90
+ return builder.sign(root_key, alg)
91
+
92
+
93
+ def build_host_certificate(
94
+ host_key: RSAPrivateKey,
95
+ root_cert: Certificate,
96
+ root_key: RSAPrivateKey,
97
+ alg: hashes.HashAlgorithm,
98
+ ) -> Certificate:
99
+ """
100
+ Build the host (leaf) certificate signed by the root:
101
+ - Empty subject
102
+ - Issuer = root's (empty) subject
103
+ - Serial = 1
104
+ - BasicConstraints CA:FALSE (critical)
105
+ - KeyUsage: digitalSignature, keyEncipherment (critical)
106
+ - Signed with root_key
107
+
108
+ :param host_key: Host RSA private key (leaf).
109
+ :param root_cert: Root CA certificate.
110
+ :param root_key: Root RSA private key.
111
+ :param alg: Hash algorithm (SHA-1 or SHA-256).
112
+ :returns: Host certificate (leaf).
113
+ """
114
+ not_before, not_after = get_validity_bounds()
115
+ builder = (
116
+ x509.CertificateBuilder()
117
+ .subject_name(x509.Name([]))
118
+ .issuer_name(root_cert.subject) # empty
119
+ .public_key(host_key.public_key())
120
+ .serial_number(_SERIAL)
121
+ .not_valid_before(not_before)
122
+ .not_valid_after(not_after)
123
+ .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
124
+ .add_extension(
125
+ x509.KeyUsage(
126
+ digital_signature=True,
127
+ key_encipherment=True,
128
+ key_cert_sign=False,
129
+ crl_sign=False,
130
+ content_commitment=False,
131
+ data_encipherment=False,
132
+ key_agreement=False,
133
+ encipher_only=False,
134
+ decipher_only=False,
135
+ ),
136
+ critical=True,
137
+ )
138
+ )
139
+ return builder.sign(root_key, alg)
140
+
141
+
142
+ def build_device_certificate(
143
+ device_public_key: RSAPublicKey,
144
+ root_cert: Certificate,
145
+ root_key: RSAPrivateKey,
146
+ alg: hashes.HashAlgorithm,
147
+ ) -> Certificate:
148
+ """
149
+ Build the device certificate (leaf) signed by the root:
150
+ - Empty subject
151
+ - Issuer = root's (empty) subject
152
+ - Serial = 1
153
+ - BasicConstraints CA:FALSE (critical)
154
+ - KeyUsage: digitalSignature, keyEncipherment (critical)
155
+ - SubjectKeyIdentifier = hash
156
+ - Signed with root_key
157
+
158
+ :param device_public_key: Device's RSA public key (as advertised by lockdown).
159
+ :param root_cert: Root CA certificate.
160
+ :param root_key: Root RSA private key.
161
+ :param alg: Hash algorithm (SHA-1 or SHA-256).
162
+ :returns: Device certificate (leaf).
163
+ """
164
+ not_before, not_after = get_validity_bounds()
165
+ builder = (
166
+ x509.CertificateBuilder()
167
+ .subject_name(x509.Name([]))
168
+ .issuer_name(root_cert.subject) # empty
169
+ .public_key(device_public_key)
170
+ .serial_number(_SERIAL)
171
+ .not_valid_before(not_before)
172
+ .not_valid_after(not_after)
173
+ .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
174
+ .add_extension(
175
+ x509.KeyUsage(
176
+ digital_signature=True,
177
+ key_encipherment=True,
178
+ key_cert_sign=False,
179
+ crl_sign=False,
180
+ content_commitment=False,
181
+ data_encipherment=False,
182
+ key_agreement=False,
183
+ encipher_only=False,
184
+ decipher_only=False,
185
+ ),
186
+ critical=True,
187
+ )
188
+ .add_extension(x509.SubjectKeyIdentifier.from_public_key(device_public_key), critical=False)
189
+ )
190
+ return builder.sign(root_key, alg)
191
+
192
+
193
+ # ==========================================
194
+ # Public API for your pairing flow (renamed)
195
+ # ==========================================
196
+
197
+
198
+ def generate_pairing_cert_chain(
199
+ device_public_key_pem: bytes,
200
+ private_key: Optional[RSAPrivateKey] = None,
201
+ device_version: Union[tuple[int, int, int], str, None] = (4, 0, 0),
202
+ ) -> tuple[bytes, bytes, bytes, bytes, bytes]:
203
+ """
204
+ Generate a root→host certificate chain and a device certificate that mirror the
205
+ libimobiledevice C behavior (empty DN, serial=1, BC/KU/SKI, SHA1 flip for < 4.0).
206
+
207
+ :param device_public_key_pem: Device RSA public key in PEM ("RSA PUBLIC KEY") format.
208
+ :param private_key: Optional host RSA private key to reuse; if None, a new one is generated.
209
+ :param device_version: Version to select hash (tuple or "a.b.c"). < 4.0.0 => SHA-1; else SHA-256.
210
+ :returns: (host_cert_pem, host_key_pem, device_cert_pem, root_cert_pem, root_key_pem)
211
+ """
212
+ alg = select_hash_algorithm(device_version)
213
+
214
+ # Root CA (self-signed)
215
+ root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
216
+ root_cert = build_root_certificate(root_key, alg)
217
+
218
+ # Host leaf (reuse provided key if given)
219
+ host_key = private_key or rsa.generate_private_key(public_exponent=65537, key_size=2048)
220
+ host_cert = build_host_certificate(host_key, root_cert, root_key, alg)
221
+
222
+ # Device leaf (public key provided by the device)
223
+ dev_pub = load_pem_public_key(device_public_key_pem)
224
+ if not isinstance(dev_pub, RSAPublicKey):
225
+ raise TypeError("device_public_key_pem must be an RSA PUBLIC KEY in PEM format")
226
+ device_cert = build_device_certificate(dev_pub, root_cert, root_key, alg)
227
+
228
+ return (
229
+ serialize_cert_pem(host_cert),
230
+ serialize_private_key_pkcs8_pem(host_key),
231
+ serialize_cert_pem(device_cert),
232
+ serialize_cert_pem(root_cert),
233
+ serialize_private_key_pkcs8_pem(root_key),
234
+ )
235
+
13
236
 
14
237
  def make_cert(key: RSAPrivateKey, public_key: RSAPublicKey, common_name: Optional[str] = None) -> Certificate:
238
+ """
239
+ Create a simple self-signed certificate for the provided key.
240
+
241
+ NOTE: This is not suitable for pairing (it sets subject/issuer and lacks C-style fields).
242
+ It is preserved as-is for your keybag usage.
243
+
244
+ :param key: RSA private key for signing.
245
+ :param public_key: RSA public key to embed in the certificate.
246
+ :param common_name: Optional CN to include in subject/issuer.
247
+ :returns: Self-signed certificate.
248
+ """
15
249
  attributes = [x509.NameAttribute(NameOID.COMMON_NAME, common_name)] if common_name else []
16
250
  subject = issuer = x509.Name(attributes)
17
251
  cert = x509.CertificateBuilder()
@@ -27,27 +261,30 @@ def make_cert(key: RSAPrivateKey, public_key: RSAPublicKey, common_name: Optiona
27
261
  return cert
28
262
 
29
263
 
30
- def dump_cert(cert):
31
- return cert.public_bytes(Encoding.PEM)
32
-
33
-
34
- def ca_do_everything(device_public_key, private_key: Optional[rsa.RSAPrivateKey] = None):
35
- if private_key is None:
36
- private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
264
+ def dump_cert(cert: Certificate) -> bytes:
265
+ """
266
+ Serialize a certificate in PEM format.
37
267
 
38
- cert = make_cert(private_key, private_key.public_key())
39
- dev_key = load_pem_public_key(device_public_key)
40
- dev_cert = make_cert(private_key, dev_key, 'Device')
41
- return dump_cert(cert), private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()), dump_cert(
42
- dev_cert)
268
+ :param cert: Certificate object.
269
+ :returns: PEM-encoded certificate bytes.
270
+ """
271
+ return cert.public_bytes(Encoding.PEM)
43
272
 
44
273
 
45
274
  def create_keybag_file(file: Path, common_name: str) -> None:
275
+ """
276
+ Write a private key and a simple self-signed certificate to a file (PEM concatenated).
277
+
278
+ :param file: Destination file path.
279
+ :param common_name: Common Name to embed in the self-signed certificate.
280
+ """
46
281
  private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
47
282
  cer = make_cert(private_key, private_key.public_key(), common_name)
48
283
  file.write_bytes(
49
284
  private_key.private_bytes(
50
285
  encoding=serialization.Encoding.PEM,
51
286
  format=PrivateFormat.TraditionalOpenSSL,
52
- encryption_algorithm=serialization.NoEncryption()
53
- ) + cer.public_bytes(encoding=serialization.Encoding.PEM))
287
+ encryption_algorithm=serialization.NoEncryption(),
288
+ )
289
+ + cer.public_bytes(encoding=serialization.Encoding.PEM)
290
+ )
@@ -1,38 +1,46 @@
1
- import click
1
+ from typing import Annotated
2
2
 
3
- from pymobiledevice3.cli.cli_common import Command
4
- from pymobiledevice3.lockdown import LockdownClient
5
- from pymobiledevice3.services.mobile_activation import MobileActivationService
6
-
7
-
8
- @click.group()
9
- def cli() -> None:
10
- pass
3
+ import typer
4
+ from typer_injector import InjectingTyper
11
5
 
6
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
7
+ from pymobiledevice3.services.mobile_activation import MobileActivationService
12
8
 
13
- @cli.group()
14
- def activation() -> None:
15
- """ Perform iCloud activation/deactivation or query the current state """
16
- pass
9
+ cli = InjectingTyper(
10
+ name="activation",
11
+ help="Perform iCloud activation/deactivation or query the current state",
12
+ no_args_is_help=True,
13
+ )
17
14
 
18
15
 
19
- @activation.command(cls=Command)
20
- def state(service_provider: LockdownClient):
21
- """ Get current activation state """
16
+ @cli.command()
17
+ def state(service_provider: ServiceProviderDep) -> None:
18
+ """Get current activation state"""
22
19
  print(MobileActivationService(service_provider).state)
23
20
 
24
21
 
25
- @activation.command(cls=Command)
26
- @click.option('--now', is_flag=True, help='when --offline is used, dont wait for next nonce cycle')
27
- def activate(service_provider: LockdownClient, now):
28
- """ Activate device """
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"""
29
31
  activation_service = MobileActivationService(service_provider)
30
32
  if not now:
31
33
  activation_service.wait_for_activation_session()
32
34
  activation_service.activate()
33
35
 
34
36
 
35
- @activation.command(cls=Command)
36
- def deactivate(service_provider: LockdownClient):
37
- """ Deactivate device """
37
+ @cli.command()
38
+ def deactivate(service_provider: ServiceProviderDep) -> None:
39
+ """Deactivate device"""
38
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")
@@ -1,55 +1,64 @@
1
- import click
2
-
3
- from pymobiledevice3.cli.cli_common import Command
4
- from pymobiledevice3.lockdown import LockdownClient
5
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
6
- from pymobiledevice3.services.afc import AfcService, AfcShell
1
+ from pathlib import Path
2
+ from typing import Annotated
7
3
 
4
+ import typer
5
+ from typer_injector import InjectingTyper
8
6
 
9
- @click.group()
10
- def cli() -> None:
11
- pass
12
-
7
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
8
+ from pymobiledevice3.services.afc import AfcService, AfcShell
13
9
 
14
- @cli.group()
15
- def afc() -> None:
16
- """ Manage device multimedia files """
17
- pass
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
+ )
18
15
 
19
16
 
20
- @afc.command('shell', cls=Command)
21
- def afc_shell(service_provider: LockdownClient):
22
- """ open an AFC shell rooted at /var/mobile/Media """
17
+ @cli.command("shell")
18
+ def afc_shell(service_provider: ServiceProviderDep) -> None:
19
+ """Open an interactive AFC shell rooted at /var/mobile/Media."""
23
20
  AfcShell.create(service_provider)
24
21
 
25
22
 
26
- @afc.command('pull', cls=Command)
27
- @click.argument('remote_file', type=click.Path(exists=False))
28
- @click.argument('local_file', type=click.Path(exists=False))
29
- def afc_pull(service_provider: LockdownServiceProvider, remote_file: str, local_file: str) -> None:
30
- """ pull remote file from /var/mobile/Media """
31
- AfcService(lockdown=service_provider).pull(remote_file, local_file)
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)
32
39
 
33
40
 
34
- @afc.command('push', cls=Command)
35
- @click.argument('local_file', type=click.Path(exists=False))
36
- @click.argument('remote_file', type=click.Path(exists=False))
37
- def afc_push(service_provider: LockdownServiceProvider, local_file: str, remote_file: str) -> None:
38
- """ push local file into /var/mobile/Media """
39
- AfcService(lockdown=service_provider).push(local_file, remote_file)
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))
40
45
 
41
46
 
42
- @afc.command('ls', cls=Command)
43
- @click.argument('remote_file', type=click.Path(exists=False))
44
- @click.option('-r', '--recursive', is_flag=True)
45
- def afc_ls(service_provider: LockdownClient, remote_file, recursive):
46
- """ perform a dirlist rooted at /var/mobile/Media """
47
- for path in AfcService(lockdown=service_provider).dirlist(remote_file, -1 if recursive else 1):
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):
48
58
  print(path)
49
59
 
50
60
 
51
- @afc.command('rm', cls=Command)
52
- @click.argument('remote_file', type=click.Path(exists=False))
53
- def afc_rm(service_provider: LockdownClient, remote_file):
54
- """ remove a file rooted at /var/mobile/Media """
55
- AfcService(lockdown=service_provider).rm(remote_file)
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))
@@ -1,38 +1,33 @@
1
1
  import logging
2
2
 
3
- import click
3
+ from typer_injector import InjectingTyper
4
4
 
5
- from pymobiledevice3.cli.cli_common import Command, print_json
6
- from pymobiledevice3.lockdown import LockdownClient
5
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
7
6
  from pymobiledevice3.services.amfi import AmfiService
8
7
 
9
8
  logger = logging.getLogger(__name__)
10
9
 
11
10
 
12
- @click.group()
13
- def cli() -> None:
14
- pass
11
+ cli = InjectingTyper(
12
+ name="amfi",
13
+ help="Enable developer-mode or query its state",
14
+ no_args_is_help=True,
15
+ )
15
16
 
16
17
 
17
- @cli.group()
18
- def amfi() -> None:
19
- """ Enable developer-mode or query its state """
20
- pass
21
-
22
-
23
- @amfi.command(cls=Command)
24
- def reveal_developer_mode(service_provider: LockdownClient):
25
- """ reveal developer mode option in device's UI """
18
+ @cli.command()
19
+ def reveal_developer_mode(service_provider: ServiceProviderDep) -> None:
20
+ """reveal developer mode option in device's UI"""
26
21
  AmfiService(service_provider).reveal_developer_mode_option_in_ui()
27
22
 
28
23
 
29
- @amfi.command(cls=Command)
30
- def enable_developer_mode(service_provider: LockdownClient):
31
- """ enable developer mode """
24
+ @cli.command()
25
+ def enable_developer_mode(service_provider: ServiceProviderDep) -> None:
26
+ """enable developer mode"""
32
27
  AmfiService(service_provider).enable_developer_mode()
33
28
 
34
29
 
35
- @amfi.command(cls=Command)
36
- def developer_mode_status(service_provider: LockdownClient):
37
- """ query developer mode status """
30
+ @cli.command()
31
+ def developer_mode_status(service_provider: ServiceProviderDep) -> None:
32
+ """query developer mode status"""
38
33
  print_json(service_provider.developer_mode_status)