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
@@ -2,45 +2,48 @@ import logging
2
2
  import os
3
3
  import posixpath
4
4
  import re
5
- from typing import Optional, TextIO
6
-
7
- import click
8
-
9
- from pymobiledevice3.cli.cli_common import Command, get_last_used_terminal_formatting, user_requested_colored_output
10
- from pymobiledevice3.lockdown import LockdownClient
5
+ from contextlib import nullcontext
6
+ from pathlib import Path
7
+ from typing import Annotated, Optional, TextIO
8
+
9
+ import typer
10
+ from typer_injector import InjectingTyper
11
+
12
+ from pymobiledevice3.cli.cli_common import (
13
+ ServiceProviderDep,
14
+ get_last_used_terminal_formatting,
15
+ user_requested_colored_output,
16
+ )
11
17
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
12
- from pymobiledevice3.services.os_trace import OsTraceService, SyslogLogLevel
18
+ from pymobiledevice3.services.os_trace import OsTraceService, SyslogEntry, SyslogLogLevel
13
19
  from pymobiledevice3.services.syslog import SyslogService
14
20
 
15
21
  logger = logging.getLogger(__name__)
16
22
 
23
+ cli = InjectingTyper(
24
+ name="syslog",
25
+ help="Watch syslog messages",
26
+ no_args_is_help=True,
27
+ )
17
28
 
18
- @click.group()
19
- def cli() -> None:
20
- pass
21
-
22
-
23
- @cli.group()
24
- def syslog() -> None:
25
- """ Watch syslog messages """
26
- pass
27
29
 
28
-
29
- @syslog.command('live-old', cls=Command)
30
- def syslog_live_old(service_provider: LockdownClient):
31
- """ view live syslog lines in raw bytes form from old relay """
30
+ @cli.command("live-old")
31
+ def syslog_live_old(service_provider: ServiceProviderDep) -> None:
32
+ """view live syslog lines in raw bytes form from old relay"""
32
33
  for line in SyslogService(service_provider=service_provider).watch():
33
34
  print(line)
34
35
 
35
36
 
36
- def format_line(color, pid, syslog_entry, include_label):
37
+ def format_line(
38
+ color: bool, pid: int, syslog_entry: SyslogEntry, include_label: bool, image_offset: bool = False
39
+ ) -> Optional[str]:
37
40
  log_level_colors = {
38
- SyslogLogLevel.NOTICE.name: 'white',
39
- SyslogLogLevel.INFO.name: 'white',
40
- SyslogLogLevel.DEBUG.name: 'green',
41
- SyslogLogLevel.ERROR.name: 'red',
42
- SyslogLogLevel.FAULT.name: 'red',
43
- SyslogLogLevel.USER_ACTION.name: 'white',
41
+ SyslogLogLevel.NOTICE.name: "white",
42
+ SyslogLogLevel.INFO.name: "white",
43
+ SyslogLogLevel.DEBUG.name: "green",
44
+ SyslogLogLevel.ERROR.name: "red",
45
+ SyslogLogLevel.FAULT.name: "red",
46
+ SyslogLogLevel.USER_ACTION.name: "white",
44
47
  }
45
48
 
46
49
  syslog_pid = syslog_entry.pid
@@ -50,55 +53,75 @@ def format_line(color, pid, syslog_entry, include_label):
50
53
  image_name = posixpath.basename(syslog_entry.image_name)
51
54
  message = syslog_entry.message
52
55
  process_name = posixpath.basename(filename)
53
- label = ''
56
+ image_offset_str = f"+0x{syslog_entry.image_offset:x}" if image_offset and image_name else ""
57
+ label = ""
54
58
 
55
59
  if (pid != -1) and (syslog_pid != pid):
56
60
  return None
57
61
 
58
62
  if syslog_entry.label is not None:
59
- label = f'[{syslog_entry.label.subsystem}][{syslog_entry.label.category}]'
63
+ label = f"[{syslog_entry.label.subsystem}][{syslog_entry.label.category}]"
60
64
 
61
65
  if color:
62
- timestamp = click.style(str(timestamp), 'green')
63
- process_name = click.style(process_name, 'magenta')
66
+ timestamp = typer.style(str(timestamp), "green")
67
+ process_name = typer.style(process_name, "magenta")
64
68
  if len(image_name) > 0:
65
- image_name = click.style(image_name, 'magenta')
66
- syslog_pid = click.style(syslog_pid, 'cyan')
69
+ image_name = typer.style(image_name, "magenta")
70
+ if image_offset:
71
+ image_offset_str = typer.style(image_offset_str, "blue")
72
+ syslog_pid = typer.style(syslog_pid, "cyan")
67
73
  log_level_color = log_level_colors[level]
68
- level = click.style(level, log_level_color)
69
- label = click.style(label, 'cyan')
70
- message = click.style(message, log_level_color)
74
+ level = typer.style(level, log_level_color)
75
+ label = typer.style(label, "cyan")
76
+ message = typer.style(message, log_level_color)
71
77
 
72
- line_format = '{timestamp} {process_name}{{{image_name}}}[{pid}] <{level}>: {message}'
78
+ line_format = "{timestamp} {process_name}{{{image_name}{image_offset_str}}}[{pid}] <{level}>: {message}"
73
79
 
74
80
  if include_label:
75
- line_format += f' {label}'
76
-
77
- line = line_format.format(timestamp=timestamp, process_name=process_name, image_name=image_name, pid=syslog_pid,
78
- level=level, message=message)
81
+ line_format += f" {label}"
82
+
83
+ line = line_format.format(
84
+ timestamp=timestamp,
85
+ process_name=process_name,
86
+ image_name=image_name,
87
+ pid=syslog_pid,
88
+ level=level,
89
+ message=message,
90
+ image_offset_str=image_offset_str,
91
+ )
79
92
 
80
93
  return line
81
94
 
82
95
 
83
96
  def syslog_live(
84
- service_provider: LockdownServiceProvider, out: Optional[TextIO], pid: Optional[int],
85
- process_name: Optional[str], match: list[str], match_insensitive: list[str], include_label: bool,
86
- regex: list[str], insensitive_regex: list[str]) -> None:
87
- match_regex = [re.compile(f'.*({r}).*', re.DOTALL) for r in regex]
88
- match_regex += [re.compile(f'.*({r}).*', re.IGNORECASE | re.DOTALL) for r in insensitive_regex]
97
+ service_provider: LockdownServiceProvider,
98
+ out: Optional[TextIO],
99
+ pid: int,
100
+ process_name: Optional[str],
101
+ match: list[str],
102
+ match_insensitive: list[str],
103
+ include_label: bool,
104
+ regex: list[str],
105
+ insensitive_regex: list[str],
106
+ image_offset: bool = False,
107
+ ) -> None:
108
+ match_regex = [re.compile(f".*({r}).*", re.DOTALL) for r in regex]
109
+ match_regex += [re.compile(f".*({r}).*", re.IGNORECASE | re.DOTALL) for r in insensitive_regex]
89
110
 
90
111
  def replace(m):
91
- if len(m.groups()):
92
- return line.replace(m.group(1), click.style(m.group(1), bold=True, underline=True))
93
- return None
112
+ if len(m.groups()) and line:
113
+ return line.replace(m.group(1), typer.style(m.group(1), bold=True, underline=True))
114
+ return ""
94
115
 
95
116
  for syslog_entry in OsTraceService(lockdown=service_provider).syslog(pid=pid):
96
- if process_name:
97
- if posixpath.basename(syslog_entry.filename) != process_name:
98
- continue
117
+ if process_name and posixpath.basename(syslog_entry.filename) != process_name:
118
+ continue
119
+
120
+ line_no_style = format_line(False, pid, syslog_entry, include_label, image_offset)
121
+ line = format_line(user_requested_colored_output(), pid, syslog_entry, include_label, image_offset)
99
122
 
100
- line_no_style = format_line(False, pid, syslog_entry, include_label)
101
- line = format_line(user_requested_colored_output(), pid, syslog_entry, include_label)
123
+ if line_no_style is None or line is None:
124
+ continue
102
125
 
103
126
  skip = False
104
127
 
@@ -110,7 +133,7 @@ def syslog_live(
110
133
  break
111
134
  else:
112
135
  if user_requested_colored_output():
113
- match_line = match_line.replace(m, click.style(m, bold=True, underline=True))
136
+ match_line = match_line.replace(m, typer.style(m, bold=True, underline=True))
114
137
  line = match_line
115
138
 
116
139
  if match_insensitive is not None:
@@ -124,8 +147,12 @@ def syslog_live(
124
147
  start = line.lower().index(m)
125
148
  end = start + len(m)
126
149
  last_color_formatting = get_last_used_terminal_formatting(line[:start])
127
- line = line[:start] + click.style(line[start:end], bold=True,
128
- underline=True) + last_color_formatting + line[end:]
150
+ line = (
151
+ line[:start]
152
+ + typer.style(line[start:end], bold=True, underline=True)
153
+ + last_color_formatting
154
+ + line[end:]
155
+ )
129
156
 
130
157
  if match_regex:
131
158
  skip = True
@@ -147,31 +174,108 @@ def syslog_live(
147
174
  print(line_no_style, file=out, flush=True)
148
175
 
149
176
 
150
- @syslog.command('live', cls=Command)
151
- @click.option('-o', '--out', type=click.File('wt'), help='log file')
152
- @click.option('--pid', type=click.INT, default=-1, help='pid to filter. -1 for all')
153
- @click.option('-pn', '--process-name', help='process name to filter')
154
- @click.option('-m', '--match', multiple=True, help='match expression')
155
- @click.option('-mi', '--match-insensitive', multiple=True, help='insensitive match expression')
156
- @click.option('include_label', '--label', is_flag=True, help='should include label')
157
- @click.option('-e', '--regex', multiple=True, help='filter only lines matching given regex')
158
- @click.option('-ei', '--insensitive-regex', multiple=True, help='filter only lines matching given regex (insensitive)')
177
+ @cli.command("live")
159
178
  def cli_syslog_live(
160
- service_provider: LockdownServiceProvider, out: Optional[TextIO], pid: Optional[int],
161
- process_name: Optional[str], match: list[str], match_insensitive: list[str], include_label: bool,
162
- regex: list[str], insensitive_regex: list[str]) -> None:
163
- """ view live syslog lines """
164
-
165
- syslog_live(service_provider, out, pid, process_name, match, match_insensitive, include_label, regex,
166
- insensitive_regex)
167
-
168
-
169
- @syslog.command('collect', cls=Command)
170
- @click.argument('out', type=click.Path(exists=False, dir_okay=True, file_okay=False))
171
- @click.option('--size-limit', type=click.INT)
172
- @click.option('--age-limit', type=click.INT)
173
- @click.option('--start-time', type=click.INT)
174
- def syslog_collect(service_provider: LockdownClient, out, size_limit, age_limit, start_time):
179
+ service_provider: ServiceProviderDep,
180
+ out: Annotated[
181
+ Optional[Path],
182
+ typer.Option(
183
+ "--out",
184
+ "-o",
185
+ help="log file",
186
+ ),
187
+ ] = None,
188
+ pid: Annotated[
189
+ int,
190
+ typer.Option(help="pid to filter. -1 for all"),
191
+ ] = -1,
192
+ process_name: Annotated[
193
+ Optional[str],
194
+ typer.Option(
195
+ "--process-name",
196
+ "-pn",
197
+ help="process name to filter",
198
+ ),
199
+ ] = None,
200
+ match: Annotated[
201
+ Optional[list[str]],
202
+ typer.Option(
203
+ "--match",
204
+ "-m",
205
+ help="match expression",
206
+ ),
207
+ ] = None,
208
+ match_insensitive: Annotated[
209
+ Optional[list[str]],
210
+ typer.Option(
211
+ "--match-insensitive",
212
+ "-mi",
213
+ help="case-insensitive match expression",
214
+ ),
215
+ ] = None,
216
+ include_label: Annotated[
217
+ bool,
218
+ typer.Option(
219
+ "--label",
220
+ help="should include label",
221
+ ),
222
+ ] = False,
223
+ regex: Annotated[
224
+ Optional[list[str]],
225
+ typer.Option(
226
+ "--regex",
227
+ "-e",
228
+ help="filter only lines matching given regex",
229
+ ),
230
+ ] = None,
231
+ insensitive_regex: Annotated[
232
+ Optional[list[str]],
233
+ typer.Option(
234
+ "--insensitive-regex",
235
+ "-ei",
236
+ help="filter only lines matching given regex (insensitive)",
237
+ ),
238
+ ] = None,
239
+ image_offset: Annotated[
240
+ bool,
241
+ typer.Option(
242
+ "--image-offset",
243
+ "-io",
244
+ help="Include image offset in log line",
245
+ ),
246
+ ] = False,
247
+ ) -> None:
248
+ """view live syslog lines"""
249
+
250
+ with out.open("wt") if out else nullcontext() as out_file:
251
+ syslog_live(
252
+ service_provider,
253
+ out_file,
254
+ pid,
255
+ process_name,
256
+ match or [],
257
+ match_insensitive or [],
258
+ include_label,
259
+ regex or [],
260
+ insensitive_regex or [],
261
+ )
262
+
263
+
264
+ @cli.command("collect")
265
+ def syslog_collect(
266
+ service_provider: ServiceProviderDep,
267
+ out: Annotated[
268
+ Path,
269
+ typer.Argument(
270
+ exists=False,
271
+ dir_okay=True,
272
+ file_okay=False,
273
+ ),
274
+ ],
275
+ size_limit: int,
276
+ age_limit: int,
277
+ start_time: int,
278
+ ) -> None:
175
279
  """
176
280
  Collect the system logs into a .logarchive that can be viewed later with tools such as log or Console.
177
281
  If the filename doesn't exist, system_logs.logarchive will be created in the given directory.
@@ -179,9 +283,12 @@ def syslog_collect(service_provider: LockdownClient, out, size_limit, age_limit,
179
283
  if not os.path.exists(out):
180
284
  os.makedirs(out)
181
285
 
182
- if not out.endswith('.logarchive'):
183
- logger.warning('given out path doesn\'t end with a .logarchive - consider renaming to be able to view '
184
- 'the file with the likes of the Console.app and the `log show` utilities')
286
+ if out.suffix != ".logarchive":
287
+ logger.warning(
288
+ "given out path doesn't end with a .logarchive - consider renaming to be able to view "
289
+ "the file with the likes of the Console.app and the `log show` utilities"
290
+ )
185
291
 
186
- OsTraceService(lockdown=service_provider).collect(out, size_limit=size_limit, age_limit=age_limit,
187
- start_time=start_time)
292
+ OsTraceService(lockdown=service_provider).collect(
293
+ str(out), size_limit=size_limit, age_limit=age_limit, start_time=start_time
294
+ )
@@ -1,56 +1,95 @@
1
1
  import logging
2
2
  import tempfile
3
+ from typing import Annotated, Optional
3
4
 
4
- import click
5
+ import typer
6
+ from typer_injector import InjectingTyper
5
7
 
6
8
  from pymobiledevice3 import usbmux
7
- from pymobiledevice3.cli.cli_common import USBMUX_OPTION_HELP, BaseCommand, print_json
9
+ from pymobiledevice3.cli.cli_common import USBMUX_OPTION_HELP, print_json
8
10
  from pymobiledevice3.lockdown import create_using_usbmux
9
11
  from pymobiledevice3.tcp_forwarder import UsbmuxTcpForwarder
10
12
 
11
13
  logger = logging.getLogger(__name__)
12
14
 
13
15
 
14
- @click.group()
15
- def cli() -> None:
16
- pass
17
-
18
-
19
- @cli.group('usbmux')
20
- def usbmux_cli() -> None:
21
- """ List devices or forward a TCP port """
22
- pass
23
-
24
-
25
- @usbmux_cli.command('forward')
26
- @click.option('usbmux_address', '--usbmux', help=USBMUX_OPTION_HELP)
27
- @click.argument('src_port', type=click.IntRange(1, 0xffff))
28
- @click.argument('dst_port', type=click.IntRange(1, 0xffff))
29
- @click.option('--serial', help='device serial number')
30
- @click.option('-d', '--daemonize', is_flag=True)
31
- def usbmux_forward(usbmux_address: str, src_port: int, dst_port: int, serial: str, daemonize: bool):
32
- """ forward tcp port """
16
+ cli = InjectingTyper(
17
+ name="usbmux",
18
+ help="Inspect usbmuxd-connected devices and forward TCP ports to them.",
19
+ no_args_is_help=True,
20
+ )
21
+
22
+
23
+ @cli.command("forward")
24
+ def usbmux_forward(
25
+ src_port: Annotated[
26
+ int,
27
+ typer.Argument(min=1, max=0xFFFF),
28
+ ],
29
+ dst_port: Annotated[
30
+ int,
31
+ typer.Argument(min=1, max=0xFFFF),
32
+ ],
33
+ *,
34
+ usbmux_address: Annotated[
35
+ Optional[str],
36
+ typer.Option(
37
+ "--usbmux",
38
+ help=USBMUX_OPTION_HELP,
39
+ ),
40
+ ] = None,
41
+ serial: Annotated[
42
+ str,
43
+ typer.Option(help="Device serial/UDID to forward traffic to."),
44
+ ],
45
+ daemonize: Annotated[
46
+ bool,
47
+ typer.Option("--daemonize", "-d", help="Run the forwarder in the background."),
48
+ ] = False,
49
+ ) -> None:
50
+ """Forward a local TCP port to the device via usbmuxd."""
33
51
  forwarder = UsbmuxTcpForwarder(serial, dst_port, src_port, usbmux_address=usbmux_address)
34
52
 
35
53
  if daemonize:
36
54
  try:
37
55
  from daemonize import Daemonize
38
- except ImportError:
39
- raise NotImplementedError('daemonizing is only supported on unix platforms')
56
+ except ImportError as e:
57
+ raise NotImplementedError("daemonizing is only supported on unix platforms") from e
40
58
 
41
- with tempfile.NamedTemporaryFile('wt') as pid_file:
42
- daemon = Daemonize(app=f'forwarder {src_port}->{dst_port}', pid=pid_file.name, action=forwarder.start)
59
+ with tempfile.NamedTemporaryFile("wt") as pid_file:
60
+ daemon = Daemonize(app=f"forwarder {src_port}->{dst_port}", pid=pid_file.name, action=forwarder.start)
43
61
  daemon.start()
44
62
  else:
45
63
  forwarder.start()
46
64
 
47
65
 
48
- @usbmux_cli.command('list', cls=BaseCommand)
49
- @click.option('usbmux_address', '--usbmux', help=USBMUX_OPTION_HELP)
50
- @click.option('-u', '--usb', is_flag=True, help='show only usb devices')
51
- @click.option('-n', '--network', is_flag=True, help='show only network devices')
52
- def usbmux_list(usbmux_address: str, usb: bool, network: bool) -> None:
53
- """ list connected devices """
66
+ @cli.command("list")
67
+ def usbmux_list(
68
+ usbmux_address: Annotated[
69
+ Optional[str],
70
+ typer.Option(
71
+ "--usbmux",
72
+ help=USBMUX_OPTION_HELP,
73
+ ),
74
+ ] = None,
75
+ usb: Annotated[
76
+ bool,
77
+ typer.Option(
78
+ "--usb",
79
+ "-u",
80
+ help="show only USB devices",
81
+ ),
82
+ ] = False,
83
+ network: Annotated[
84
+ bool,
85
+ typer.Option(
86
+ "--network",
87
+ "-n",
88
+ help="show only network devices",
89
+ ),
90
+ ] = False,
91
+ ) -> None:
92
+ """List devices known to usbmuxd (USB and Wi-Fi)."""
54
93
  connected_devices = []
55
94
  for device in usbmux.list_devices(usbmux_address=usbmux_address):
56
95
  udid = device.serial
@@ -61,8 +100,9 @@ def usbmux_list(usbmux_address: str, usb: bool, network: bool) -> None:
61
100
  if network and not device.is_network:
62
101
  continue
63
102
 
64
- lockdown = create_using_usbmux(udid, autopair=False, connection_type=device.connection_type,
65
- usbmux_address=usbmux_address)
103
+ lockdown = create_using_usbmux(
104
+ udid, autopair=False, connection_type=device.connection_type, usbmux_address=usbmux_address
105
+ )
66
106
  connected_devices.append(lockdown.short_info)
67
107
 
68
108
  print_json(connected_devices)
@@ -1,16 +1,14 @@
1
- import click
1
+ from typer import Typer
2
2
 
3
-
4
- @click.group()
5
- def cli() -> None:
6
- pass
3
+ cli = Typer()
7
4
 
8
5
 
9
6
  @cli.command()
10
7
  def version() -> None:
11
- """ Query pymobiledevice3 version """
8
+ """Query pymobiledevice3 version"""
12
9
  try:
13
10
  from pymobiledevice3._version import __version__
11
+
14
12
  print(__version__)
15
13
  except ImportError:
16
- print('version could not be determined. please first install/build the package')
14
+ print("version could not be determined. please first install/build the package")