eeroctl 1.7.1__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 (45) hide show
  1. eeroctl/__init__.py +19 -0
  2. eeroctl/commands/__init__.py +32 -0
  3. eeroctl/commands/activity.py +237 -0
  4. eeroctl/commands/auth.py +471 -0
  5. eeroctl/commands/completion.py +142 -0
  6. eeroctl/commands/device.py +492 -0
  7. eeroctl/commands/eero/__init__.py +12 -0
  8. eeroctl/commands/eero/base.py +224 -0
  9. eeroctl/commands/eero/led.py +154 -0
  10. eeroctl/commands/eero/nightlight.py +235 -0
  11. eeroctl/commands/eero/updates.py +82 -0
  12. eeroctl/commands/network/__init__.py +18 -0
  13. eeroctl/commands/network/advanced.py +191 -0
  14. eeroctl/commands/network/backup.py +162 -0
  15. eeroctl/commands/network/base.py +331 -0
  16. eeroctl/commands/network/dhcp.py +118 -0
  17. eeroctl/commands/network/dns.py +197 -0
  18. eeroctl/commands/network/forwards.py +115 -0
  19. eeroctl/commands/network/guest.py +162 -0
  20. eeroctl/commands/network/security.py +162 -0
  21. eeroctl/commands/network/speedtest.py +99 -0
  22. eeroctl/commands/network/sqm.py +194 -0
  23. eeroctl/commands/profile.py +671 -0
  24. eeroctl/commands/troubleshoot.py +317 -0
  25. eeroctl/context.py +254 -0
  26. eeroctl/errors.py +156 -0
  27. eeroctl/exit_codes.py +68 -0
  28. eeroctl/formatting/__init__.py +90 -0
  29. eeroctl/formatting/base.py +181 -0
  30. eeroctl/formatting/device.py +430 -0
  31. eeroctl/formatting/eero.py +591 -0
  32. eeroctl/formatting/misc.py +87 -0
  33. eeroctl/formatting/network.py +659 -0
  34. eeroctl/formatting/profile.py +443 -0
  35. eeroctl/main.py +161 -0
  36. eeroctl/options.py +429 -0
  37. eeroctl/output.py +739 -0
  38. eeroctl/safety.py +259 -0
  39. eeroctl/utils.py +181 -0
  40. eeroctl-1.7.1.dist-info/METADATA +115 -0
  41. eeroctl-1.7.1.dist-info/RECORD +45 -0
  42. eeroctl-1.7.1.dist-info/WHEEL +5 -0
  43. eeroctl-1.7.1.dist-info/entry_points.txt +3 -0
  44. eeroctl-1.7.1.dist-info/licenses/LICENSE +21 -0
  45. eeroctl-1.7.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,162 @@
1
+ """Security commands for the Eero CLI.
2
+
3
+ Commands:
4
+ - eero network security show: Show security settings
5
+ - eero network security wpa3: WPA3 encryption
6
+ - eero network security band-steering: Band steering
7
+ - eero network security upnp: UPnP
8
+ - eero network security ipv6: IPv6
9
+ - eero network security thread: Thread protocol
10
+ """
11
+
12
+ import asyncio
13
+ import sys
14
+
15
+ import click
16
+ from eero import EeroClient
17
+ from rich.table import Table
18
+
19
+ from ...context import get_cli_context
20
+ from ...exit_codes import ExitCode
21
+ from ...safety import OperationRisk, SafetyError, confirm_or_fail
22
+ from ...utils import run_with_client
23
+
24
+
25
+ @click.group(name="security")
26
+ @click.pass_context
27
+ def security_group(ctx: click.Context) -> None:
28
+ """Manage security settings.
29
+
30
+ \b
31
+ Commands:
32
+ show - Show security settings
33
+ wpa3 - WPA3 encryption
34
+ band-steering - Band steering
35
+ upnp - UPnP
36
+ ipv6 - IPv6
37
+ thread - Thread protocol
38
+
39
+ \b
40
+ Examples:
41
+ eero network security show
42
+ eero network security wpa3 enable
43
+ eero network security upnp disable
44
+ """
45
+ pass
46
+
47
+
48
+ @security_group.command(name="show")
49
+ @click.pass_context
50
+ def security_show(ctx: click.Context) -> None:
51
+ """Show security settings."""
52
+ cli_ctx = get_cli_context(ctx)
53
+ console = cli_ctx.console
54
+ renderer = cli_ctx.renderer
55
+
56
+ async def run_cmd() -> None:
57
+ async def get_security(client: EeroClient) -> None:
58
+ with cli_ctx.status("Getting security settings..."):
59
+ sec_data = await client.get_security_settings(cli_ctx.network_id)
60
+
61
+ if cli_ctx.is_json_output():
62
+ renderer.render_json(sec_data, "eero.network.security.show/v1")
63
+ else:
64
+ table = Table(title="Security Settings")
65
+ table.add_column("Setting", style="cyan")
66
+ table.add_column("Status", justify="center")
67
+
68
+ settings = [
69
+ ("WPA3", sec_data.get("wpa3", False)),
70
+ ("Band Steering", sec_data.get("band_steering", True)),
71
+ ("UPnP", sec_data.get("upnp", True)),
72
+ ("IPv6", sec_data.get("ipv6_upstream", False)),
73
+ ("Thread", sec_data.get("thread", False)),
74
+ ]
75
+
76
+ for name, enabled in settings:
77
+ status = "[green]Enabled[/green]" if enabled else "[dim]Disabled[/dim]"
78
+ table.add_row(name, status)
79
+
80
+ console.print(table)
81
+
82
+ await run_with_client(get_security)
83
+
84
+ asyncio.run(run_cmd())
85
+
86
+
87
+ # Security toggle commands factory
88
+ def _make_security_toggle(setting_name: str, api_method: str, display_name: str):
89
+ """Factory for security toggle command groups."""
90
+
91
+ @click.group(name=setting_name)
92
+ @click.pass_context
93
+ def toggle_group(ctx: click.Context) -> None:
94
+ pass
95
+
96
+ @toggle_group.command(name="enable")
97
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
98
+ @click.pass_context
99
+ def enable_cmd(ctx: click.Context, force: bool) -> None:
100
+ _set_security_setting(ctx, api_method, display_name, True, force)
101
+
102
+ @toggle_group.command(name="disable")
103
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
104
+ @click.pass_context
105
+ def disable_cmd(ctx: click.Context, force: bool) -> None:
106
+ _set_security_setting(ctx, api_method, display_name, False, force)
107
+
108
+ enable_cmd.__doc__ = f"Enable {display_name}."
109
+ disable_cmd.__doc__ = f"Disable {display_name}."
110
+ toggle_group.__doc__ = f"Manage {display_name}."
111
+
112
+ return toggle_group
113
+
114
+
115
+ def _set_security_setting(ctx, api_method: str, display_name: str, enable: bool, force: bool):
116
+ """Set a security setting."""
117
+ cli_ctx = get_cli_context(ctx)
118
+ console = cli_ctx.console
119
+ action = "enable" if enable else "disable"
120
+
121
+ try:
122
+ confirm_or_fail(
123
+ action=f"{action} {display_name}",
124
+ target="network",
125
+ risk=OperationRisk.MEDIUM,
126
+ force=force or cli_ctx.force,
127
+ non_interactive=cli_ctx.non_interactive,
128
+ dry_run=cli_ctx.dry_run,
129
+ )
130
+ except SafetyError as e:
131
+ cli_ctx.renderer.render_error(e.message)
132
+ sys.exit(e.exit_code)
133
+
134
+ async def run_cmd() -> None:
135
+ async def set_setting(client: EeroClient) -> None:
136
+ method = getattr(client, api_method)
137
+ with cli_ctx.status(f"{action.capitalize()}ing {display_name}..."):
138
+ result = await method(enable, cli_ctx.network_id)
139
+
140
+ if result:
141
+ console.print(f"[bold green]{display_name} {action}d[/bold green]")
142
+ else:
143
+ console.print(f"[red]Failed to {action} {display_name}[/red]")
144
+ sys.exit(ExitCode.GENERIC_ERROR)
145
+
146
+ await run_with_client(set_setting)
147
+
148
+ asyncio.run(run_cmd())
149
+
150
+
151
+ # Create and register security toggle commands
152
+ wpa3_group = _make_security_toggle("wpa3", "set_wpa3", "WPA3")
153
+ band_steering_group = _make_security_toggle("band-steering", "set_band_steering", "band steering")
154
+ upnp_group = _make_security_toggle("upnp", "set_upnp", "UPnP")
155
+ ipv6_group = _make_security_toggle("ipv6", "set_ipv6", "IPv6")
156
+ thread_group = _make_security_toggle("thread", "set_thread_enabled", "Thread")
157
+
158
+ security_group.add_command(wpa3_group)
159
+ security_group.add_command(band_steering_group)
160
+ security_group.add_command(upnp_group)
161
+ security_group.add_command(ipv6_group)
162
+ security_group.add_command(thread_group)
@@ -0,0 +1,99 @@
1
+ """Speed test commands for the Eero CLI.
2
+
3
+ Commands:
4
+ - eero network speedtest run: Run a new speed test
5
+ - eero network speedtest show: Show last speed test results
6
+ """
7
+
8
+ import asyncio
9
+
10
+ import click
11
+ from eero import EeroClient
12
+ from rich.panel import Panel
13
+
14
+ from ...context import get_cli_context
15
+ from ...utils import run_with_client
16
+
17
+
18
+ @click.group(name="speedtest")
19
+ @click.pass_context
20
+ def speedtest_group(ctx: click.Context) -> None:
21
+ """Run and view speed tests.
22
+
23
+ \b
24
+ Commands:
25
+ run - Run a new speed test
26
+ show - Show last speed test results
27
+ """
28
+ pass
29
+
30
+
31
+ @speedtest_group.command(name="run")
32
+ @click.pass_context
33
+ def speedtest_run(ctx: click.Context) -> None:
34
+ """Run a new speed test."""
35
+ cli_ctx = get_cli_context(ctx)
36
+ console = cli_ctx.console
37
+ renderer = cli_ctx.renderer
38
+
39
+ async def run_cmd() -> None:
40
+ async def run_test(client: EeroClient) -> None:
41
+ with cli_ctx.status("Running speed test (this may take a minute)..."):
42
+ result = await client.run_speed_test(cli_ctx.network_id)
43
+
44
+ if cli_ctx.is_json_output():
45
+ renderer.render_json(result, "eero.network.speedtest.run/v1")
46
+ else:
47
+ download = result.get("down", {}).get("value", 0)
48
+ upload = result.get("up", {}).get("value", 0)
49
+ latency = result.get("latency", {}).get("value", 0)
50
+
51
+ content = (
52
+ f"[bold]Download:[/bold] {download} Mbps\n"
53
+ f"[bold]Upload:[/bold] {upload} Mbps\n"
54
+ f"[bold]Latency:[/bold] {latency} ms"
55
+ )
56
+ console.print(Panel(content, title="Speed Test Results", border_style="green"))
57
+
58
+ await run_with_client(run_test)
59
+
60
+ asyncio.run(run_cmd())
61
+
62
+
63
+ @speedtest_group.command(name="show")
64
+ @click.pass_context
65
+ def speedtest_show(ctx: click.Context) -> None:
66
+ """Show last speed test results."""
67
+ cli_ctx = get_cli_context(ctx)
68
+ console = cli_ctx.console
69
+ renderer = cli_ctx.renderer
70
+
71
+ async def run_cmd() -> None:
72
+ async def get_results(client: EeroClient) -> None:
73
+ with cli_ctx.status("Getting speed test results..."):
74
+ network = await client.get_network(cli_ctx.network_id)
75
+
76
+ speed_test = network.speed_test
77
+ if not speed_test:
78
+ console.print("[yellow]No speed test results available[/yellow]")
79
+ return
80
+
81
+ if cli_ctx.is_json_output():
82
+ renderer.render_json(speed_test, "eero.network.speedtest.show/v1")
83
+ else:
84
+ download = speed_test.get("down", {}).get("value", 0)
85
+ upload = speed_test.get("up", {}).get("value", 0)
86
+ latency = speed_test.get("latency", {}).get("value", 0)
87
+ tested = speed_test.get("date", "Unknown")
88
+
89
+ content = (
90
+ f"[bold]Download:[/bold] {download} Mbps\n"
91
+ f"[bold]Upload:[/bold] {upload} Mbps\n"
92
+ f"[bold]Latency:[/bold] {latency} ms\n"
93
+ f"[bold]Tested:[/bold] {tested}"
94
+ )
95
+ console.print(Panel(content, title="Speed Test Results", border_style="blue"))
96
+
97
+ await run_with_client(get_results)
98
+
99
+ asyncio.run(run_cmd())
@@ -0,0 +1,194 @@
1
+ """SQM (Smart Queue Management) commands for the Eero CLI.
2
+
3
+ Commands:
4
+ - eero network sqm show: Show SQM settings
5
+ - eero network sqm enable: Enable SQM
6
+ - eero network sqm disable: Disable SQM
7
+ - eero network sqm set: Configure bandwidth limits
8
+ """
9
+
10
+ import asyncio
11
+ import sys
12
+ from typing import Optional
13
+
14
+ import click
15
+ from eero import EeroClient
16
+ from rich.panel import Panel
17
+
18
+ from ...context import EeroCliContext, get_cli_context
19
+ from ...exit_codes import ExitCode
20
+ from ...safety import OperationRisk, SafetyError, confirm_or_fail
21
+ from ...utils import run_with_client
22
+
23
+
24
+ @click.group(name="sqm")
25
+ @click.pass_context
26
+ def sqm_group(ctx: click.Context) -> None:
27
+ """Manage Smart Queue Management (SQM) / QoS settings.
28
+
29
+ \b
30
+ Commands:
31
+ show - Show SQM settings
32
+ enable - Enable SQM
33
+ disable - Disable SQM
34
+ set - Configure bandwidth limits
35
+
36
+ \b
37
+ Examples:
38
+ eero network sqm show
39
+ eero network sqm enable
40
+ eero network sqm set --upload 50 --download 200
41
+ """
42
+ pass
43
+
44
+
45
+ @sqm_group.command(name="show")
46
+ @click.pass_context
47
+ def sqm_show(ctx: click.Context) -> None:
48
+ """Show SQM settings."""
49
+ cli_ctx = get_cli_context(ctx)
50
+ console = cli_ctx.console
51
+ renderer = cli_ctx.renderer
52
+
53
+ async def run_cmd() -> None:
54
+ async def get_sqm(client: EeroClient) -> None:
55
+ with cli_ctx.status("Getting SQM settings..."):
56
+ sqm_data = await client.get_sqm_settings(cli_ctx.network_id)
57
+
58
+ if cli_ctx.is_json_output():
59
+ renderer.render_json(sqm_data, "eero.network.sqm.show/v1")
60
+ else:
61
+ enabled = sqm_data.get("enabled", False)
62
+ upload_bw = sqm_data.get("upload_bandwidth")
63
+ download_bw = sqm_data.get("download_bandwidth")
64
+
65
+ content = (
66
+ f"[bold]Enabled:[/bold] {'[green]Yes[/green]' if enabled else '[dim]No[/dim]'}"
67
+ )
68
+ if upload_bw:
69
+ content += f"\n[bold]Upload:[/bold] {upload_bw} Mbps"
70
+ if download_bw:
71
+ content += f"\n[bold]Download:[/bold] {download_bw} Mbps"
72
+
73
+ console.print(Panel(content, title="SQM Settings", border_style="blue"))
74
+
75
+ await run_with_client(get_sqm)
76
+
77
+ asyncio.run(run_cmd())
78
+
79
+
80
+ @sqm_group.command(name="enable")
81
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
82
+ @click.pass_context
83
+ def sqm_enable(ctx: click.Context, force: bool) -> None:
84
+ """Enable SQM."""
85
+ cli_ctx = get_cli_context(ctx)
86
+ _set_sqm_enabled(cli_ctx, True, force)
87
+
88
+
89
+ @sqm_group.command(name="disable")
90
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
91
+ @click.pass_context
92
+ def sqm_disable(ctx: click.Context, force: bool) -> None:
93
+ """Disable SQM."""
94
+ cli_ctx = get_cli_context(ctx)
95
+ _set_sqm_enabled(cli_ctx, False, force)
96
+
97
+
98
+ def _set_sqm_enabled(cli_ctx: EeroCliContext, enable: bool, force: bool) -> None:
99
+ """Enable or disable SQM."""
100
+ console = cli_ctx.console
101
+ action = "enable" if enable else "disable"
102
+
103
+ try:
104
+ confirm_or_fail(
105
+ action=f"{action} SQM",
106
+ target="network",
107
+ risk=OperationRisk.MEDIUM,
108
+ force=force or cli_ctx.force,
109
+ non_interactive=cli_ctx.non_interactive,
110
+ dry_run=cli_ctx.dry_run,
111
+ )
112
+ except SafetyError as e:
113
+ cli_ctx.renderer.render_error(e.message)
114
+ sys.exit(e.exit_code)
115
+
116
+ async def run_cmd() -> None:
117
+ async def set_sqm(client: EeroClient) -> None:
118
+ with cli_ctx.status(f"{action.capitalize()}ing SQM..."):
119
+ result = await client.set_sqm_enabled(enable, cli_ctx.network_id)
120
+
121
+ if result:
122
+ console.print(f"[bold green]SQM {action}d[/bold green]")
123
+ else:
124
+ console.print(f"[red]Failed to {action} SQM[/red]")
125
+ sys.exit(ExitCode.GENERIC_ERROR)
126
+
127
+ await run_with_client(set_sqm)
128
+
129
+ asyncio.run(run_cmd())
130
+
131
+
132
+ @sqm_group.command(name="set")
133
+ @click.option("--upload", "-u", type=int, help="Upload bandwidth limit (Mbps)")
134
+ @click.option("--download", "-d", type=int, help="Download bandwidth limit (Mbps)")
135
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
136
+ @click.pass_context
137
+ def sqm_set(
138
+ ctx: click.Context, upload: Optional[int], download: Optional[int], force: bool
139
+ ) -> None:
140
+ """Configure SQM bandwidth limits.
141
+
142
+ \b
143
+ Options:
144
+ --upload, -u Upload bandwidth in Mbps
145
+ --download, -d Download bandwidth in Mbps
146
+
147
+ \b
148
+ Examples:
149
+ eero network sqm set --upload 50 --download 200
150
+ """
151
+ cli_ctx = get_cli_context(ctx)
152
+ console = cli_ctx.console
153
+
154
+ if upload is None and download is None:
155
+ console.print("[yellow]Specify --upload and/or --download[/yellow]")
156
+ sys.exit(ExitCode.USAGE_ERROR)
157
+
158
+ try:
159
+ confirm_or_fail(
160
+ action="configure SQM",
161
+ target=f"upload={upload}Mbps download={download}Mbps",
162
+ risk=OperationRisk.MEDIUM,
163
+ force=force or cli_ctx.force,
164
+ non_interactive=cli_ctx.non_interactive,
165
+ dry_run=cli_ctx.dry_run,
166
+ )
167
+ except SafetyError as e:
168
+ cli_ctx.renderer.render_error(e.message)
169
+ sys.exit(e.exit_code)
170
+
171
+ async def run_cmd() -> None:
172
+ async def configure_sqm(client: EeroClient) -> None:
173
+ with cli_ctx.status("Configuring SQM..."):
174
+ result = await client.configure_sqm(
175
+ enabled=True,
176
+ upload_mbps=upload,
177
+ download_mbps=download,
178
+ network_id=cli_ctx.network_id,
179
+ )
180
+
181
+ if result:
182
+ msg_parts = []
183
+ if upload:
184
+ msg_parts.append(f"Upload={upload}Mbps")
185
+ if download:
186
+ msg_parts.append(f"Download={download}Mbps")
187
+ console.print(f"[bold green]SQM configured: {' '.join(msg_parts)}[/bold green]")
188
+ else:
189
+ console.print("[red]Failed to configure SQM[/red]")
190
+ sys.exit(ExitCode.GENERIC_ERROR)
191
+
192
+ await run_with_client(configure_sqm)
193
+
194
+ asyncio.run(run_cmd())