pymobiledevice3 6.2.0__py3-none-any.whl → 7.0.0__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 (61) hide show
  1. pymobiledevice3/__main__.py +136 -44
  2. pymobiledevice3/_version.py +2 -2
  3. pymobiledevice3/bonjour.py +19 -20
  4. pymobiledevice3/cli/activation.py +24 -22
  5. pymobiledevice3/cli/afc.py +49 -41
  6. pymobiledevice3/cli/amfi.py +13 -18
  7. pymobiledevice3/cli/apps.py +71 -65
  8. pymobiledevice3/cli/backup.py +134 -93
  9. pymobiledevice3/cli/bonjour.py +31 -29
  10. pymobiledevice3/cli/cli_common.py +179 -232
  11. pymobiledevice3/cli/companion_proxy.py +12 -12
  12. pymobiledevice3/cli/crash.py +95 -52
  13. pymobiledevice3/cli/developer/__init__.py +62 -0
  14. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  15. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  16. pymobiledevice3/cli/developer/arbitration.py +50 -0
  17. pymobiledevice3/cli/developer/condition.py +33 -0
  18. pymobiledevice3/cli/developer/core_device.py +294 -0
  19. pymobiledevice3/cli/developer/debugserver.py +244 -0
  20. pymobiledevice3/cli/developer/dvt/__init__.py +387 -0
  21. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  22. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  23. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  24. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  25. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  26. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  27. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  28. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  29. pymobiledevice3/cli/idam.py +18 -22
  30. pymobiledevice3/cli/lockdown.py +70 -75
  31. pymobiledevice3/cli/mounter.py +99 -57
  32. pymobiledevice3/cli/notification.py +38 -26
  33. pymobiledevice3/cli/pcap.py +36 -20
  34. pymobiledevice3/cli/power_assertion.py +15 -16
  35. pymobiledevice3/cli/processes.py +11 -17
  36. pymobiledevice3/cli/profile.py +120 -75
  37. pymobiledevice3/cli/provision.py +27 -26
  38. pymobiledevice3/cli/remote.py +108 -99
  39. pymobiledevice3/cli/restore.py +134 -129
  40. pymobiledevice3/cli/springboard.py +50 -50
  41. pymobiledevice3/cli/syslog.py +138 -74
  42. pymobiledevice3/cli/usbmux.py +66 -27
  43. pymobiledevice3/cli/version.py +2 -5
  44. pymobiledevice3/cli/webinspector.py +149 -103
  45. pymobiledevice3/remote/remote_service_discovery.py +11 -10
  46. pymobiledevice3/restore/device.py +28 -4
  47. pymobiledevice3/service_connection.py +1 -1
  48. pymobiledevice3/services/mobilebackup2.py +4 -1
  49. pymobiledevice3/services/screenshot.py +2 -2
  50. pymobiledevice3/services/web_protocol/automation_session.py +4 -2
  51. pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
  52. pymobiledevice3/services/web_protocol/element.py +3 -3
  53. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/METADATA +3 -2
  54. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/RECORD +58 -45
  55. pymobiledevice3/cli/completions.py +0 -50
  56. pymobiledevice3/cli/developer.py +0 -1645
  57. pymobiledevice3/cli/diagnostics.py +0 -110
  58. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/WHEEL +0 -0
  59. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/entry_points.txt +0 -0
  60. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/licenses/LICENSE +0 -0
  61. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,19 @@
1
1
  from datetime import datetime
2
- from typing import IO, Optional
2
+ from pathlib import Path
3
+ from typing import Annotated, Optional
3
4
 
4
- import click
5
+ import typer
5
6
  from pygments import formatters, highlight, lexers
7
+ from typer_injector import InjectingTyper
6
8
 
7
- from pymobiledevice3.cli.cli_common import Command, print_hex, user_requested_colored_output
8
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
9
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_hex, user_requested_colored_output
9
10
  from pymobiledevice3.services.pcapd import PcapdService
10
11
 
11
-
12
- @click.group()
13
- def cli() -> None:
14
- pass
12
+ cli = InjectingTyper(
13
+ name="pcap",
14
+ help="Sniff device traffic via pcapd and optionally save to a .pcap file.",
15
+ no_args_is_help=True,
16
+ )
15
17
 
16
18
 
17
19
  def print_packet_header(packet, color: bool) -> None:
@@ -37,25 +39,39 @@ def print_packet(packet, color: Optional[bool] = None):
37
39
  return packet
38
40
 
39
41
 
40
- @cli.command(cls=Command)
41
- @click.argument("out", type=click.File("wb"), required=False)
42
- @click.option("-c", "--count", type=click.INT, default=-1, help="Number of packets to sniff. Omit to endless sniff.")
43
- @click.option("--process", default=None, help="Process to filter. Omit for all.")
44
- @click.option("-i", "--interface", default=None, help="Interface name to filter. Omit for all.")
42
+ @cli.command()
45
43
  def pcap(
46
- service_provider: LockdownServiceProvider,
47
- out: Optional[IO],
48
- count: int,
49
- process: Optional[str],
50
- interface: Optional[str],
44
+ service_provider: ServiceProviderDep,
45
+ out: Optional[Path] = None,
46
+ count: Annotated[
47
+ int,
48
+ typer.Option(
49
+ "--count",
50
+ "-c",
51
+ help="Number of packets to sniff. Omit to endless sniff.",
52
+ ),
53
+ ] = -1,
54
+ process: Annotated[
55
+ Optional[str],
56
+ typer.Option(help="Process to filter. Omit for all."),
57
+ ] = None,
58
+ interface: Annotated[
59
+ Optional[str],
60
+ typer.Option(
61
+ "--interface",
62
+ "-i",
63
+ help="Interface name to filter. Omit for all.",
64
+ ),
65
+ ] = None,
51
66
  ) -> None:
52
- """Sniff device traffic"""
67
+ """Sniff device traffic."""
53
68
  service = PcapdService(lockdown=service_provider)
54
69
  packets_generator = service.watch(packets_count=count, process=process, interface_name=interface)
55
70
 
56
71
  if out is not None:
57
72
  packets_generator_with_print = (print_packet(p) for p in packets_generator)
58
- service.write_to_pcap(out, packets_generator_with_print)
73
+ with out.open("wb") as out_file:
74
+ service.write_to_pcap(out_file, packets_generator_with_print)
59
75
  return
60
76
 
61
77
  for packet in packets_generator:
@@ -1,26 +1,25 @@
1
1
  import time
2
+ from typing import Literal, Optional
2
3
 
3
- import click
4
+ from typer_injector import InjectingTyper
4
5
 
5
- from pymobiledevice3.cli.cli_common import Command
6
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
6
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
7
7
  from pymobiledevice3.services.power_assertion import PowerAssertionService
8
8
 
9
-
10
- @click.group()
11
- def cli() -> None:
12
- pass
9
+ cli = InjectingTyper(
10
+ name="power-assertion",
11
+ no_args_is_help=True,
12
+ )
13
13
 
14
14
 
15
- @cli.command("power-assertion", cls=Command)
16
- @click.argument(
17
- "assertion_type",
18
- type=click.Choice(["AMDPowerAssertionTypeWirelessSync", "PreventUserIdleSystemSleep", "PreventSystemSleep"]),
19
- )
20
- @click.argument("name")
21
- @click.argument("timeout", type=click.INT)
22
- @click.argument("details", required=False)
23
- def power_assertion(service_provider: LockdownServiceProvider, assertion_type, name, timeout, details) -> None:
15
+ @cli.command("power-assertion")
16
+ def power_assertion(
17
+ service_provider: ServiceProviderDep,
18
+ assertion_type: Literal["AMDPowerAssertionTypeWirelessSync", "PreventUserIdleSystemSleep", "PreventSystemSleep"],
19
+ name: str,
20
+ timeout: int,
21
+ details: Optional[str] = None,
22
+ ) -> None:
24
23
  """Create a power assertion"""
25
24
  with PowerAssertionService(service_provider).create_power_assertion(assertion_type, name, timeout, details):
26
25
  print("> Hit Ctrl+C to exit")
@@ -1,34 +1,28 @@
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.os_trace import OsTraceService
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="processes",
13
+ help="View process list using diagnosticsd API",
14
+ no_args_is_help=True,
15
+ )
15
16
 
16
17
 
17
- @cli.group()
18
- def processes() -> None:
19
- """View process list using diagnosticsd API"""
20
- pass
21
-
22
-
23
- @processes.command("ps", cls=Command)
24
- def processes_ps(service_provider: LockdownClient):
18
+ @cli.command("ps")
19
+ def processes_ps(service_provider: ServiceProviderDep) -> None:
25
20
  """show process list"""
26
21
  print_json(OsTraceService(lockdown=service_provider).get_pid_list().get("Payload"))
27
22
 
28
23
 
29
- @processes.command("pgrep", cls=Command)
30
- @click.argument("expression")
31
- def processes_pgrep(service_provider: LockdownClient, expression):
24
+ @cli.command("pgrep")
25
+ def processes_pgrep(service_provider: ServiceProviderDep, expression: str) -> None:
32
26
  """try to match processes pid by given expression (like pgrep)"""
33
27
  processes_list = OsTraceService(lockdown=service_provider).get_pid_list().get("Payload")
34
28
  for pid, process_info in processes_list.items():
@@ -2,41 +2,45 @@ import logging
2
2
  import plistlib
3
3
  import tempfile
4
4
  from pathlib import Path
5
- from typing import IO, Optional
5
+ from typing import Annotated, Literal, Optional
6
6
 
7
- import click
7
+ import typer
8
+ from typer_injector import InjectingTyper
8
9
 
9
10
  from pymobiledevice3.ca import create_keybag_file
10
- from pymobiledevice3.cli.cli_common import Command, print_json
11
- from pymobiledevice3.lockdown import LockdownClient
12
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
11
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
13
12
  from pymobiledevice3.services.mobile_activation import MobileActivationService
14
13
  from pymobiledevice3.services.mobile_config import MobileConfigService
15
14
 
16
15
  logger = logging.getLogger(__name__)
17
16
 
18
17
 
19
- @click.group()
20
- def cli() -> None:
21
- pass
22
-
23
-
24
- @cli.group("profile")
25
- def profile_group() -> None:
26
- """Managed installed profiles or install SSL certificates"""
27
- pass
18
+ cli = InjectingTyper(
19
+ name="profile",
20
+ help="Manage installed profiles or install SSL certificates",
21
+ no_args_is_help=True,
22
+ )
28
23
 
29
24
 
30
- @profile_group.command("list", cls=Command)
31
- def profile_list(service_provider: LockdownClient):
25
+ @cli.command("list")
26
+ def profile_list(service_provider: ServiceProviderDep) -> None:
32
27
  """List installed profiles"""
33
28
  print_json(MobileConfigService(lockdown=service_provider).get_profile_list())
34
29
 
35
30
 
36
- @profile_group.command("install", cls=Command)
37
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
38
- @click.argument("profiles", nargs=-1, type=click.File("rb"))
39
- def profile_install(service_provider: LockdownServiceProvider, keybag: Optional[str], profiles: list[IO]) -> None:
31
+ @cli.command("install")
32
+ def profile_install(
33
+ service_provider: ServiceProviderDep,
34
+ profiles: list[Path],
35
+ keybag: Annotated[
36
+ Optional[Path],
37
+ typer.Option(
38
+ exists=True,
39
+ file_okay=True,
40
+ dir_okay=False,
41
+ ),
42
+ ] = None,
43
+ ) -> None:
40
44
  """
41
45
  Install given profiles
42
46
 
@@ -44,61 +48,58 @@ def profile_install(service_provider: LockdownServiceProvider, keybag: Optional[
44
48
  """
45
49
  service = MobileConfigService(lockdown=service_provider)
46
50
  for profile in profiles:
47
- logger.info(f"installing {profile.name}")
51
+ logger.info(f"installing {profile}")
48
52
  if keybag is not None:
49
- service.install_profile_silent(Path(keybag), profile.read())
53
+ service.install_profile_silent(Path(keybag), profile.read_bytes())
50
54
  else:
51
- service.install_profile(profile.read())
55
+ service.install_profile(profile.read_bytes())
52
56
 
53
57
 
54
- @profile_group.command("cloud-configuration", cls=Command)
55
- @click.argument("config", type=click.File("rb"), required=False)
56
- def profile_cloud_configuration(service_provider: LockdownServiceProvider, config: Optional[IO]) -> None:
58
+ @cli.command("cloud-configuration")
59
+ def profile_cloud_configuration(service_provider: ServiceProviderDep, config: Optional[Path] = None) -> None:
57
60
  """Get/Set cloud configuration"""
58
61
  if not config:
59
62
  print_json(MobileConfigService(lockdown=service_provider).get_cloud_configuration())
60
63
  else:
61
- config_json = plistlib.load(config)
64
+ with config.open("rb") as config_file:
65
+ config_json = plistlib.load(config_file)
62
66
  logger.info(f"applying cloud configuration {config_json}")
63
67
  MobileConfigService(lockdown=service_provider).set_cloud_configuration(config_json)
64
68
  logger.info("applied cloud configuration")
65
69
 
66
70
 
67
- @profile_group.command("store", cls=Command)
68
- @click.argument("profiles", nargs=-1, type=click.File("rb"))
69
- def profile_store(service_provider: LockdownServiceProvider, profiles: list[IO]) -> None:
71
+ @cli.command("store")
72
+ def profile_store(service_provider: ServiceProviderDep, profiles: list[Path]) -> None:
70
73
  """Store a profile"""
71
74
  service = MobileConfigService(lockdown=service_provider)
72
75
  for profile in profiles:
73
76
  logger.info(f"storing {profile.name}")
74
- service.store_profile(profile.read())
77
+ service.store_profile(profile.read_bytes())
75
78
 
76
79
 
77
- @profile_group.command("remove", cls=Command)
78
- @click.argument("name")
79
- def profile_remove(service_provider: LockdownServiceProvider, name: str) -> None:
80
+ @cli.command("remove")
81
+ def profile_remove(service_provider: ServiceProviderDep, name: str) -> None:
80
82
  """Remove a profile by its name"""
81
83
  MobileConfigService(lockdown=service_provider).remove_profile(name)
82
84
 
83
85
 
84
- @profile_group.command("set-wifi-power", cls=Command)
85
- @click.argument("state", type=click.Choice(["on", "off"]), required=False)
86
- def profile_set_wifi_power(service_provider: LockdownServiceProvider, state: str) -> None:
86
+ @cli.command("set-wifi-power")
87
+ def profile_set_wifi_power(service_provider: ServiceProviderDep, state: Literal["on", "off"] = "off") -> None:
87
88
  """change Wi-Fi power state"""
88
89
  MobileConfigService(lockdown=service_provider).set_wifi_power_state(state == "on")
89
90
 
90
91
 
91
- @profile_group.command("erase-device", cls=Command)
92
- @click.option(
93
- "--preserve-data-plan/--no-preserve-data-plan", default=True, help="Preserves eSIM / data plan after erase"
94
- )
95
- @click.option(
96
- "--disallow-proximity-setup/--no-disallow-proximity-setup",
97
- default=False,
98
- help="Disallows to setup the erased device from nearby devices",
99
- )
92
+ @cli.command("erase-device")
100
93
  def profile_erase_device(
101
- service_provider: LockdownServiceProvider, preserve_data_plan: bool, disallow_proximity_setup: bool
94
+ service_provider: ServiceProviderDep,
95
+ preserve_data_plan: Annotated[
96
+ bool,
97
+ typer.Option(help="Preserves eSIM / data plan after erase"),
98
+ ] = True,
99
+ disallow_proximity_setup: Annotated[
100
+ bool,
101
+ typer.Option(help="Disallows setup of the erased device from nearby devices"),
102
+ ] = False,
102
103
  ) -> None:
103
104
  """Erase device"""
104
105
  logger.info(
@@ -109,18 +110,35 @@ def profile_erase_device(
109
110
  logger.info("Erased device")
110
111
 
111
112
 
112
- @profile_group.command("create-keybag")
113
- @click.argument("keybag", type=click.Path(file_okay=True, dir_okay=False, exists=False))
114
- @click.argument("organization")
115
- def profile_create_keybag(keybag: str, organization: str) -> None:
113
+ @cli.command("create-keybag")
114
+ def profile_create_keybag(
115
+ keybag: Annotated[
116
+ Path,
117
+ typer.Argument(
118
+ exists=False,
119
+ file_okay=True,
120
+ dir_okay=False,
121
+ ),
122
+ ],
123
+ organization: str,
124
+ ) -> None:
116
125
  """Create keybag storing certificate and private key"""
117
- create_keybag_file(Path(keybag), organization)
118
-
119
-
120
- @profile_group.command("supervise", cls=Command)
121
- @click.argument("organization")
122
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
123
- def profile_supervise(service_provider: LockdownServiceProvider, organization: str, keybag: Optional[str]) -> None:
126
+ create_keybag_file(keybag, organization)
127
+
128
+
129
+ @cli.command("supervise")
130
+ def profile_supervise(
131
+ service_provider: ServiceProviderDep,
132
+ organization: str,
133
+ keybag: Annotated[
134
+ Optional[Path],
135
+ typer.Option(
136
+ exists=True,
137
+ file_okay=True,
138
+ dir_okay=False,
139
+ ),
140
+ ] = None,
141
+ ) -> None:
124
142
  """Supervise device"""
125
143
  if MobileActivationService(service_provider).state == "Unactivated":
126
144
  logger.info("Activating device")
@@ -138,13 +156,20 @@ def profile_supervise(service_provider: LockdownServiceProvider, organization: s
138
156
  logger.info("Device has been successfully supervised")
139
157
 
140
158
 
141
- @profile_group.command("install-wifi-profile", cls=Command)
142
- @click.argument("encryption_type")
143
- @click.argument("ssid")
144
- @click.argument("password")
145
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
159
+ @cli.command("install-wifi-profile")
146
160
  def profile_install_wifi_profile(
147
- service_provider: LockdownServiceProvider, encryption_type: str, ssid: str, password: str, keybag: Optional[str]
161
+ service_provider: ServiceProviderDep,
162
+ encryption_type: str,
163
+ ssid: str,
164
+ password: str,
165
+ keybag: Annotated[
166
+ Optional[Path],
167
+ typer.Option(
168
+ exists=True,
169
+ file_okay=True,
170
+ dir_okay=False,
171
+ ),
172
+ ] = None,
148
173
  ) -> None:
149
174
  """
150
175
  Install Wi-Fi profile
@@ -158,12 +183,22 @@ def profile_install_wifi_profile(
158
183
  )
159
184
 
160
185
 
161
- @profile_group.command("install-http-proxy", cls=Command)
162
- @click.argument("server")
163
- @click.argument("port", type=click.IntRange(1, 65535))
164
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
186
+ @cli.command("install-http-proxy")
165
187
  def profile_install_http_proxy(
166
- service_provider: LockdownServiceProvider, server: str, port: int, keybag: Optional[str]
188
+ service_provider: ServiceProviderDep,
189
+ server: str,
190
+ port: Annotated[
191
+ int,
192
+ typer.Argument(min=1, max=0xFFFF),
193
+ ],
194
+ keybag: Annotated[
195
+ Optional[Path],
196
+ typer.Option(
197
+ exists=True,
198
+ file_okay=True,
199
+ dir_okay=False,
200
+ ),
201
+ ] = None,
167
202
  ) -> None:
168
203
  """Install HTTP Proxy profile"""
169
204
  if keybag is not None:
@@ -171,17 +206,27 @@ def profile_install_http_proxy(
171
206
  MobileConfigService(lockdown=service_provider).install_http_proxy(server, port, keybag_file=keybag)
172
207
 
173
208
 
174
- @profile_group.command("remove-http-proxy", cls=Command)
175
- def profile_remove_http_proxy(service_provider: LockdownServiceProvider) -> None:
209
+ @cli.command("remove-http-proxy")
210
+ def profile_remove_http_proxy(service_provider: ServiceProviderDep) -> None:
176
211
  """Remove HTTP Proxy profile that was previously installed using pymobiledevice3"""
177
212
  MobileConfigService(lockdown=service_provider).remove_http_proxy()
178
213
 
179
214
 
180
- @profile_group.command("install-restrictions-profile", cls=Command)
181
- @click.option("--keybag", type=click.Path(file_okay=True, dir_okay=False, exists=True))
182
- @click.option("--enforced-software-update-delay", type=click.IntRange(0, 90), default=0)
215
+ @cli.command("install-restrictions-profile")
183
216
  def profile_install_restrictions_profile(
184
- service_provider: LockdownServiceProvider, keybag: Optional[str], enforced_software_update_delay: int
217
+ service_provider: ServiceProviderDep,
218
+ keybag: Annotated[
219
+ Optional[Path],
220
+ typer.Option(
221
+ exists=True,
222
+ file_okay=True,
223
+ dir_okay=False,
224
+ ),
225
+ ] = None,
226
+ enforced_software_update_delay: Annotated[
227
+ int,
228
+ typer.Option(min=0, max=90),
229
+ ] = 0,
185
230
  ) -> None:
186
231
  """Install restrictions profile (can be used for delayed OTA)"""
187
232
  if keybag is not None:
@@ -1,56 +1,57 @@
1
1
  import logging
2
2
  from pathlib import Path
3
+ from typing import Annotated
3
4
 
4
- import click
5
+ import typer
6
+ from typer_injector import InjectingTyper
5
7
 
6
- from pymobiledevice3.cli.cli_common import Command, print_json
7
- from pymobiledevice3.lockdown import LockdownClient
8
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
8
9
  from pymobiledevice3.services.misagent import MisagentService
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
13
 
13
- @click.group()
14
- def cli() -> None:
15
- pass
14
+ cli = InjectingTyper(
15
+ name="provision",
16
+ help="Manage installed provision profiles",
17
+ no_args_is_help=True,
18
+ )
16
19
 
17
20
 
18
- @cli.group()
19
- def provision() -> None:
20
- """Manage installed provision profiles"""
21
- pass
22
-
23
-
24
- @provision.command("install", cls=Command)
25
- @click.argument("profile", type=click.File("rb"))
26
- def provision_install(service_provider: LockdownClient, profile):
21
+ @cli.command("install")
22
+ def provision_install(service_provider: ServiceProviderDep, profile: Path) -> None:
27
23
  """install a provision profile (.mobileprovision file)"""
28
- MisagentService(lockdown=service_provider).install(profile)
24
+ with profile.open("rb") as profile_file:
25
+ MisagentService(lockdown=service_provider).install(profile_file)
29
26
 
30
27
 
31
- @provision.command("remove", cls=Command)
32
- @click.argument("profile_id")
33
- def provision_remove(service_provider: LockdownClient, profile_id):
28
+ @cli.command("remove")
29
+ def provision_remove(service_provider: ServiceProviderDep, profile_id: str) -> None:
34
30
  """remove a provision profile"""
35
31
  MisagentService(lockdown=service_provider).remove(profile_id)
36
32
 
37
33
 
38
- @provision.command("clear", cls=Command)
39
- def provision_clear(service_provider: LockdownClient):
34
+ @cli.command("clear")
35
+ def provision_clear(service_provider: ServiceProviderDep) -> None:
40
36
  """remove all provision profiles"""
41
37
  for profile in MisagentService(lockdown=service_provider).copy_all():
42
38
  MisagentService(lockdown=service_provider).remove(profile.plist["UUID"])
43
39
 
44
40
 
45
- @provision.command("list", cls=Command)
46
- def provision_list(service_provider: LockdownClient):
41
+ @cli.command("list")
42
+ def provision_list(service_provider: ServiceProviderDep) -> None:
47
43
  """list installed provision profiles"""
48
44
  print_json([p.plist for p in MisagentService(lockdown=service_provider).copy_all()])
49
45
 
50
46
 
51
- @provision.command("dump", cls=Command)
52
- @click.argument("out", type=click.Path(file_okay=False, dir_okay=True, exists=True))
53
- def provision_dump(service_provider: LockdownClient, out):
47
+ @cli.command("dump")
48
+ def provision_dump(
49
+ service_provider: ServiceProviderDep,
50
+ out: Annotated[
51
+ Path,
52
+ typer.Argument(file_okay=False, dir_okay=True, exists=True),
53
+ ],
54
+ ) -> None:
54
55
  """dump installed provision profiles to specified location"""
55
56
  for profile in MisagentService(lockdown=service_provider).copy_all():
56
57
  filename = f"{profile.plist['UUID']}.mobileprovision"