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
@@ -3,24 +3,36 @@ import dataclasses
3
3
  import logging
4
4
  import sys
5
5
  import tempfile
6
+ from contextlib import nullcontext
6
7
  from functools import partial
7
- from typing import Optional, TextIO
8
+ from pathlib import Path
9
+ from typing import Annotated, Optional, TextIO
8
10
 
9
- import click
11
+ import typer
12
+ from typer_injector import InjectingTyper
10
13
 
11
14
  from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing_manual_pairing
12
- from pymobiledevice3.cli.cli_common import BaseCommand, RSDCommand, print_json, prompt_device_list, sudo_required, \
13
- user_requested_colored_output
15
+ from pymobiledevice3.cli.cli_common import (
16
+ RSDServiceProviderDep,
17
+ print_json,
18
+ prompt_device_list,
19
+ sudo_required,
20
+ user_requested_colored_output,
21
+ )
14
22
  from pymobiledevice3.common import get_home_folder
15
23
  from pymobiledevice3.exceptions import NoDeviceConnectedError
16
24
  from pymobiledevice3.pair_records import PAIRING_RECORD_EXT, get_remote_pairing_record_filename
17
25
  from pymobiledevice3.remote.common import ConnectionType, TunnelProtocol
18
26
  from pymobiledevice3.remote.module_imports import MAX_IDLE_TIMEOUT, start_tunnel, verify_tunnel_imports
19
27
  from pymobiledevice3.remote.remote_service_discovery import RSD_PORT, RemoteServiceDiscoveryService
20
- from pymobiledevice3.remote.tunnel_service import RemotePairingManualPairingService, get_core_device_tunnel_services, \
21
- get_remote_pairing_tunnel_services
28
+ from pymobiledevice3.remote.tunnel_service import (
29
+ RemotePairingManualPairingService,
30
+ get_core_device_tunnel_services,
31
+ get_remote_pairing_tunnel_services,
32
+ )
22
33
  from pymobiledevice3.remote.utils import get_rsds
23
- from pymobiledevice3.tunneld import TUNNELD_DEFAULT_ADDRESS, TunneldRunner
34
+ from pymobiledevice3.tunneld.api import TUNNELD_DEFAULT_ADDRESS
35
+ from pymobiledevice3.tunneld.server import TunneldRunner
24
36
 
25
37
  logger = logging.getLogger(__name__)
26
38
 
@@ -28,133 +40,170 @@ logger = logging.getLogger(__name__)
28
40
  async def browse_rsd(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[dict]:
29
41
  devices = []
30
42
  for rsd in await get_rsds(timeout):
31
- devices.append({'address': rsd.service.address[0],
32
- 'port': RSD_PORT,
33
- 'UniqueDeviceID': rsd.peer_info['Properties']['UniqueDeviceID'],
34
- 'ProductType': rsd.peer_info['Properties']['ProductType'],
35
- 'OSVersion': rsd.peer_info['Properties']['OSVersion']})
43
+ assert rsd.peer_info is not None
44
+ devices.append({
45
+ "address": rsd.service.address[0],
46
+ "port": RSD_PORT,
47
+ "UniqueDeviceID": rsd.peer_info["Properties"]["UniqueDeviceID"],
48
+ "ProductType": rsd.peer_info["Properties"]["ProductType"],
49
+ "OSVersion": rsd.peer_info["Properties"]["OSVersion"],
50
+ })
36
51
  return devices
37
52
 
38
53
 
39
54
  async def browse_remotepairing(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[dict]:
40
55
  devices = []
41
56
  for remotepairing in await get_remote_pairing_tunnel_services(timeout):
42
- devices.append({'address': remotepairing.hostname,
43
- 'port': remotepairing.port,
44
- 'identifier': remotepairing.remote_identifier})
57
+ devices.append({
58
+ "address": remotepairing.hostname,
59
+ "port": remotepairing.port,
60
+ "identifier": remotepairing.remote_identifier,
61
+ })
45
62
  return devices
46
63
 
47
64
 
48
65
  async def cli_browse(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> None:
49
66
  print_json({
50
- 'usb': await browse_rsd(timeout),
51
- 'wifi': await browse_remotepairing(timeout),
67
+ "usb": await browse_rsd(timeout),
68
+ "wifi": await browse_remotepairing(timeout),
52
69
  })
53
70
 
54
71
 
55
- @click.group()
56
- def cli() -> None:
57
- pass
72
+ cli = InjectingTyper(
73
+ name="remote",
74
+ help="Create and browse RemoteXPC tunnels (RSD/tunneld) for developer services.",
75
+ no_args_is_help=True,
76
+ )
58
77
 
59
78
 
60
- @cli.group('remote')
61
- def remote_cli() -> None:
62
- """ Create RemoteXPC tunnels """
63
- pass
64
-
65
-
66
- @remote_cli.command('tunneld', cls=BaseCommand)
67
- @click.option('--host', default=TUNNELD_DEFAULT_ADDRESS[0])
68
- @click.option('--port', type=click.INT, default=TUNNELD_DEFAULT_ADDRESS[1])
69
- @click.option('-d', '--daemonize', is_flag=True)
70
- @click.option('-p', '--protocol', type=click.Choice([e.value for e in TunnelProtocol]),
71
- default=TunnelProtocol.QUIC.value)
72
- @click.option('--usb/--no-usb', default=True, help='Enable usb monitoring')
73
- @click.option('--wifi/--no-wifi', default=True, help='Enable wifi monitoring')
74
- @click.option('--usbmux/--no-usbmux', default=True, help='Enable usbmux monitoring')
75
- @click.option('--mobdev2/--no-mobdev2', default=True, help='Enable mobdev2 monitoring')
79
+ @cli.command("tunneld")
76
80
  @sudo_required
77
81
  def cli_tunneld(
78
- host: str, port: int, daemonize: bool, protocol: str, usb: bool, wifi: bool, usbmux: bool,
79
- mobdev2: bool) -> None:
80
- """ Start Tunneld service for remote tunneling """
82
+ host: Annotated[str, typer.Option(help="Address to bind the tunneld server to.")] = TUNNELD_DEFAULT_ADDRESS[0],
83
+ port: Annotated[int, typer.Option(help="Port to bind the tunneld server to.")] = TUNNELD_DEFAULT_ADDRESS[1],
84
+ daemonize: Annotated[bool, typer.Option("--daemonize", "-d", help="Run tunneld in the background.")] = False,
85
+ protocol: Annotated[
86
+ TunnelProtocol,
87
+ typer.Option(
88
+ "--protocol",
89
+ "-p",
90
+ case_sensitive=False,
91
+ help="Transport protocol for tunneld (default: TCP on Python >=3.13, otherwise QUIC).",
92
+ ),
93
+ ] = TunnelProtocol.DEFAULT,
94
+ usb: Annotated[bool, typer.Option(help="Enable USB monitoring")] = True,
95
+ wifi: Annotated[bool, typer.Option(help="Enable WiFi monitoring")] = True,
96
+ usbmux: Annotated[bool, typer.Option(help="Enable usbmux monitoring")] = True,
97
+ mobdev2: Annotated[bool, typer.Option(help="Enable mobdev2 monitoring")] = True,
98
+ ) -> None:
99
+ """Start Tunneld service for remote tunneling"""
81
100
  if not verify_tunnel_imports():
82
101
  return
83
- protocol = TunnelProtocol(protocol)
84
- tunneld_runner = partial(TunneldRunner.create, host, port, protocol=protocol, usb_monitor=usb, wifi_monitor=wifi,
85
- usbmux_monitor=usbmux, mobdev2_monitor=mobdev2)
102
+ tunneld_runner = partial(
103
+ TunneldRunner.create,
104
+ host,
105
+ port,
106
+ protocol=protocol,
107
+ usb_monitor=usb,
108
+ wifi_monitor=wifi,
109
+ usbmux_monitor=usbmux,
110
+ mobdev2_monitor=mobdev2,
111
+ )
86
112
  if daemonize:
87
113
  try:
88
114
  from daemonize import Daemonize
89
- except ImportError:
90
- raise NotImplementedError('daemonizing is only supported on unix platforms')
91
- with tempfile.NamedTemporaryFile('wt') as pid_file:
92
- daemon = Daemonize(app=f'Tunneld {host}:{port}', pid=pid_file.name,
93
- action=tunneld_runner)
94
- logger.info(f'starting Tunneld {host}:{port}')
115
+ except ImportError as e:
116
+ raise NotImplementedError("daemonizing is only supported on unix platforms") from e
117
+ with tempfile.NamedTemporaryFile("wt") as pid_file:
118
+ daemon = Daemonize(app=f"Tunneld {host}:{port}", pid=pid_file.name, action=tunneld_runner)
119
+ logger.info(f"starting Tunneld {host}:{port}")
95
120
  daemon.start()
96
121
  else:
97
122
  tunneld_runner()
98
123
 
99
124
 
100
- @remote_cli.command('browse', cls=BaseCommand)
101
- @click.option('--timeout', type=click.FLOAT, default=DEFAULT_BONJOUR_TIMEOUT, help='Bonjour timeout (in seconds)')
102
- def browse(timeout: float) -> None:
103
- """ browse RemoteXPC devices using bonjour """
125
+ @cli.command("browse")
126
+ def browse(
127
+ timeout: Annotated[float, typer.Option(help="Bonjour timeout (in seconds)")] = DEFAULT_BONJOUR_TIMEOUT,
128
+ ) -> None:
129
+ """browse RemoteXPC devices using bonjour"""
104
130
  asyncio.run(cli_browse(timeout), debug=True)
105
131
 
106
132
 
107
- @remote_cli.command('rsd-info', cls=RSDCommand)
108
- def rsd_info(service_provider: RemoteServiceDiscoveryService):
109
- """ show info extracted from RSD peer """
133
+ @cli.command("rsd-info")
134
+ def rsd_info(service_provider: RSDServiceProviderDep) -> None:
135
+ """show info extracted from RSD peer"""
110
136
  print_json(service_provider.peer_info)
111
137
 
112
138
 
113
139
  async def tunnel_task(
114
- service, secrets: Optional[TextIO] = None, script_mode: bool = False,
115
- max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.QUIC) -> None:
140
+ service,
141
+ secrets: Optional[TextIO] = None,
142
+ script_mode: bool = False,
143
+ max_idle_timeout: float = MAX_IDLE_TIMEOUT,
144
+ protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
145
+ ) -> None:
116
146
  async with start_tunnel(
117
- service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol) as tunnel_result:
118
- logger.info('tunnel created')
147
+ service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol
148
+ ) as tunnel_result:
149
+ logger.info("tunnel created")
119
150
  if script_mode:
120
- print(f'{tunnel_result.address} {tunnel_result.port}')
151
+ print(f"{tunnel_result.address} {tunnel_result.port}")
121
152
  else:
122
153
  if user_requested_colored_output():
123
154
  if secrets is not None:
124
- print(click.style('Secrets: ', bold=True, fg='magenta') +
125
- click.style(secrets.name, bold=True, fg='white'))
126
- print(click.style('Identifier: ', bold=True, fg='yellow') +
127
- click.style(service.remote_identifier, bold=True, fg='white'))
128
- print(click.style('Interface: ', bold=True, fg='yellow') +
129
- click.style(tunnel_result.interface, bold=True, fg='white'))
130
- print(click.style('Protocol: ', bold=True, fg='yellow') +
131
- click.style(tunnel_result.protocol, bold=True, fg='white'))
132
- print(click.style('RSD Address: ', bold=True, fg='yellow') +
133
- click.style(tunnel_result.address, bold=True, fg='white'))
134
- print(click.style('RSD Port: ', bold=True, fg='yellow') +
135
- click.style(tunnel_result.port, bold=True, fg='white'))
136
- print(click.style('Use the follow connection option:\n', bold=True, fg='yellow') +
137
- click.style(f'--rsd {tunnel_result.address} {tunnel_result.port}', bold=True, fg='cyan'))
155
+ print(
156
+ typer.style("Secrets: ", bold=True, fg="magenta")
157
+ + typer.style(secrets.name, bold=True, fg="white")
158
+ )
159
+ print(
160
+ typer.style("Identifier: ", bold=True, fg="yellow")
161
+ + typer.style(service.remote_identifier, bold=True, fg="white")
162
+ )
163
+ print(
164
+ typer.style("Interface: ", bold=True, fg="yellow")
165
+ + typer.style(tunnel_result.interface, bold=True, fg="white")
166
+ )
167
+ print(
168
+ typer.style("Protocol: ", bold=True, fg="yellow")
169
+ + typer.style(tunnel_result.protocol, bold=True, fg="white")
170
+ )
171
+ print(
172
+ typer.style("RSD Address: ", bold=True, fg="yellow")
173
+ + typer.style(tunnel_result.address, bold=True, fg="white")
174
+ )
175
+ print(
176
+ typer.style("RSD Port: ", bold=True, fg="yellow")
177
+ + typer.style(tunnel_result.port, bold=True, fg="white")
178
+ )
179
+ print(
180
+ typer.style("Use the follow connection option:\n", bold=True, fg="yellow")
181
+ + typer.style(f"--rsd {tunnel_result.address} {tunnel_result.port}", bold=True, fg="cyan")
182
+ )
138
183
  else:
139
184
  if secrets is not None:
140
- print(f'Secrets: {secrets.name}')
141
- print(f'Identifier: {service.remote_identifier}')
142
- print(f'Interface: {tunnel_result.interface}')
143
- print(f'Protocol: {tunnel_result.protocol}')
144
- print(f'RSD Address: {tunnel_result.address}')
145
- print(f'RSD Port: {tunnel_result.port}')
146
- print(f'Use the follow connection option:\n'
147
- f'--rsd {tunnel_result.address} {tunnel_result.port}')
185
+ print(f"Secrets: {secrets.name}")
186
+ print(f"Identifier: {service.remote_identifier}")
187
+ print(f"Interface: {tunnel_result.interface}")
188
+ print(f"Protocol: {tunnel_result.protocol}")
189
+ print(f"RSD Address: {tunnel_result.address}")
190
+ print(f"RSD Port: {tunnel_result.port}")
191
+ print(f"Use the follow connection option:\n--rsd {tunnel_result.address} {tunnel_result.port}")
148
192
  sys.stdout.flush()
149
193
  await tunnel_result.client.wait_closed()
150
- logger.info('tunnel was closed')
194
+ logger.info("tunnel was closed")
151
195
 
152
196
 
153
197
  async def start_tunnel_task(
154
- connection_type: ConnectionType, secrets: TextIO, udid: Optional[str] = None, script_mode: bool = False,
155
- max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.QUIC) -> None:
198
+ connection_type: ConnectionType,
199
+ secrets: Optional[TextIO],
200
+ udid: Optional[str] = None,
201
+ script_mode: bool = False,
202
+ max_idle_timeout: float = MAX_IDLE_TIMEOUT,
203
+ protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
204
+ ) -> None:
156
205
  if start_tunnel is None:
157
- raise NotImplementedError('failed to start the tunnel on your platform')
206
+ raise NotImplementedError("failed to start the tunnel on your platform")
158
207
  get_tunnel_services = {
159
208
  connection_type.USB: get_core_device_tunnel_services,
160
209
  connection_type.WIFI: get_remote_pairing_tunnel_services,
@@ -170,33 +219,64 @@ async def start_tunnel_task(
170
219
  # several devices were found, show prompt if none explicitly selected
171
220
  service = prompt_device_list(tunnel_services)
172
221
 
173
- await tunnel_task(service, secrets=secrets, script_mode=script_mode, max_idle_timeout=max_idle_timeout,
174
- protocol=protocol)
175
-
176
-
177
- @remote_cli.command('start-tunnel', cls=BaseCommand)
178
- @click.option('-t', '--connection-type', type=click.Choice([e.value for e in ConnectionType], case_sensitive=False),
179
- default=ConnectionType.USB.value)
180
- @click.option('--udid', help='UDID for a specific device to look for')
181
- @click.option('--secrets', type=click.File('wt'), help='TLS keyfile for decrypting with Wireshark')
182
- @click.option('--script-mode', is_flag=True,
183
- help='Show only HOST and port number to allow easy parsing from external shell scripts')
184
- @click.option('--max-idle-timeout', type=click.FLOAT, default=MAX_IDLE_TIMEOUT,
185
- help='Maximum QUIC idle time (ping interval)')
186
- @click.option('-p', '--protocol',
187
- type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
188
- default=TunnelProtocol.QUIC.value)
222
+ await tunnel_task(
223
+ service, secrets=secrets, script_mode=script_mode, max_idle_timeout=max_idle_timeout, protocol=protocol
224
+ )
225
+
226
+
227
+ @cli.command("start-tunnel")
189
228
  @sudo_required
190
229
  def cli_start_tunnel(
191
- connection_type: ConnectionType, udid: Optional[str], secrets: TextIO, script_mode: bool,
192
- max_idle_timeout: float, protocol: str) -> None:
193
- """ start tunnel """
230
+ connection_type: Annotated[
231
+ ConnectionType,
232
+ typer.Option(
233
+ "--connection-type",
234
+ "-t",
235
+ case_sensitive=False,
236
+ help="Connection interface to tunnel (USB, WiFi, etc.).",
237
+ ),
238
+ ] = ConnectionType.USB,
239
+ udid: Annotated[
240
+ Optional[str],
241
+ typer.Option(help="UDID for a specific device to look for"),
242
+ ] = None,
243
+ secrets: Annotated[
244
+ Optional[Path],
245
+ typer.Option(help="File to write TLS secrets for Wireshark decryption."),
246
+ ] = None,
247
+ script_mode: Annotated[
248
+ bool,
249
+ typer.Option(help="Print only HOST and port for scripts instead of formatted output."),
250
+ ] = False,
251
+ max_idle_timeout: Annotated[
252
+ float,
253
+ typer.Option(help="Maximum idle time before QUIC keepalive pings are sent."),
254
+ ] = MAX_IDLE_TIMEOUT,
255
+ protocol: Annotated[
256
+ TunnelProtocol,
257
+ typer.Option(
258
+ "--protocol",
259
+ "-p",
260
+ case_sensitive=False,
261
+ help="Transport protocol for the tunnel (default: TCP on Python >=3.13, otherwise QUIC).",
262
+ ),
263
+ ] = TunnelProtocol.DEFAULT,
264
+ ) -> None:
265
+ """start tunnel"""
194
266
  if not verify_tunnel_imports():
195
267
  return
196
- asyncio.run(
197
- start_tunnel_task(
198
- ConnectionType(connection_type), secrets, udid, script_mode, max_idle_timeout=max_idle_timeout,
199
- protocol=TunnelProtocol(protocol)), debug=True)
268
+ with secrets.open("wt") if secrets is not None else nullcontext() as secrets_file:
269
+ asyncio.run(
270
+ start_tunnel_task(
271
+ connection_type,
272
+ secrets_file,
273
+ udid,
274
+ script_mode,
275
+ max_idle_timeout=max_idle_timeout,
276
+ protocol=protocol,
277
+ ),
278
+ debug=True,
279
+ )
200
280
 
201
281
 
202
282
  @dataclasses.dataclass
@@ -207,44 +287,53 @@ class RemotePairingManualPairingDevice:
207
287
  identifier: str
208
288
 
209
289
 
210
- async def start_remote_pair_task(device_name: str) -> None:
290
+ async def start_remote_pair_task(device_name: Optional[str]) -> None:
211
291
  if start_tunnel is None:
212
- raise NotImplementedError('failed to start the tunnel on your platform')
292
+ raise NotImplementedError("failed to start the tunnel on your platform")
213
293
 
214
294
  devices: list[RemotePairingManualPairingDevice] = []
215
295
  for answer in await browse_remotepairing_manual_pairing():
216
- current_device_name = answer.properties[b'name'].decode()
296
+ current_device_name = answer.properties[b"name"].decode()
217
297
 
218
298
  if device_name is not None and current_device_name != device_name:
219
299
  continue
220
300
 
221
- for ip in answer.ips:
222
- devices.append(RemotePairingManualPairingDevice(ip=ip, port=answer.port, device_name=current_device_name,
223
- identifier=answer.properties[b'identifier'].decode()))
301
+ for address in answer.addresses:
302
+ devices.append(
303
+ RemotePairingManualPairingDevice(
304
+ ip=address.full_ip,
305
+ port=answer.port,
306
+ device_name=current_device_name,
307
+ identifier=answer.properties[b"identifier"].decode(),
308
+ )
309
+ )
224
310
 
225
311
  if len(devices) > 0:
226
312
  device = prompt_device_list(devices)
227
313
  else:
228
- logger.error('No devices were found during bonjour browse')
314
+ logger.error("No devices were found during bonjour browse")
229
315
  return
230
316
 
231
317
  async with RemotePairingManualPairingService(device.identifier, device.ip, device.port) as service:
232
318
  await service.connect(autopair=True)
233
319
 
234
320
 
235
- @remote_cli.command('pair', cls=BaseCommand)
236
- @click.option('--name', help='Device name for a specific device to look for')
237
- def cli_pair(name: Optional[str]) -> None:
238
- """ start remote pairing for devices which allow """
321
+ @cli.command("pair")
322
+ def cli_pair(
323
+ name: Annotated[
324
+ Optional[str],
325
+ typer.Option(help="Device name for a specific device to look for"),
326
+ ] = None,
327
+ ) -> None:
328
+ """start remote pairing for devices which allow"""
239
329
  asyncio.run(start_remote_pair_task(name), debug=True)
240
330
 
241
331
 
242
- @remote_cli.command('delete-pair', cls=BaseCommand)
243
- @click.argument('udid')
332
+ @cli.command("delete-pair")
244
333
  @sudo_required
245
- def cli_delete_pair(udid: str):
246
- """ delete a pairing record """
247
- pair_record_path = get_home_folder() / f'{get_remote_pairing_record_filename(udid)}.{PAIRING_RECORD_EXT}'
334
+ def cli_delete_pair(udid: str) -> None:
335
+ """delete a pairing record"""
336
+ pair_record_path = get_home_folder() / f"{get_remote_pairing_record_filename(udid)}.{PAIRING_RECORD_EXT}"
248
337
  pair_record_path.unlink()
249
338
 
250
339
 
@@ -253,8 +342,7 @@ async def cli_service_task(service_provider: RemoteServiceDiscoveryService, serv
253
342
  service.shell()
254
343
 
255
344
 
256
- @remote_cli.command('service', cls=RSDCommand)
257
- @click.argument('service_name')
258
- def cli_service(service_provider: RemoteServiceDiscoveryService, service_name: str) -> None:
259
- """ start an ipython shell for interacting with given service """
345
+ @cli.command("service")
346
+ def cli_service(service_provider: RSDServiceProviderDep, service_name: str) -> None:
347
+ """start an ipython shell for interacting with given service"""
260
348
  asyncio.run(cli_service_task(service_provider, service_name), debug=True)