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,18 @@
1
+ """Network commands package for the Eero CLI.
2
+
3
+ This package contains all network-related commands, split into logical submodules:
4
+ - base: Core commands (list, show, use, rename, premium)
5
+ - dns: DNS management commands
6
+ - security: Security settings commands
7
+ - sqm: SQM/QoS commands
8
+ - guest: Guest network commands
9
+ - backup: Backup network commands (Eero Plus)
10
+ - speedtest: Speed test commands
11
+ - forwards: Port forwarding commands
12
+ - dhcp: DHCP management commands
13
+ - advanced: Routing, Thread, Support commands
14
+ """
15
+
16
+ from .base import network_group
17
+
18
+ __all__ = ["network_group"]
@@ -0,0 +1,191 @@
1
+ """Advanced network commands for the Eero CLI.
2
+
3
+ Commands:
4
+ - eero network routing: Show routing information
5
+ - eero network thread: Thread protocol settings
6
+ - eero network support: Support and diagnostics
7
+ """
8
+
9
+ import asyncio
10
+ import json
11
+ import sys
12
+
13
+ import click
14
+ from eero import EeroClient
15
+ from rich.panel import Panel
16
+
17
+ from ...context import get_cli_context
18
+ from ...safety import OperationRisk, SafetyError, confirm_or_fail
19
+ from ...utils import run_with_client
20
+
21
+ # ==================== Routing Subcommand ====================
22
+
23
+
24
+ @click.command(name="routing")
25
+ @click.pass_context
26
+ def routing_show(ctx: click.Context) -> None:
27
+ """Show routing information."""
28
+ cli_ctx = get_cli_context(ctx)
29
+ console = cli_ctx.console
30
+ renderer = cli_ctx.renderer
31
+
32
+ async def run_cmd() -> None:
33
+ async def get_routing(client: EeroClient) -> None:
34
+ with cli_ctx.status("Getting routing information..."):
35
+ routing = await client.get_routing(cli_ctx.network_id)
36
+
37
+ if cli_ctx.is_json_output():
38
+ renderer.render_json(routing, "eero.network.routing.show/v1")
39
+ else:
40
+ console.print(
41
+ Panel(
42
+ json.dumps(routing, indent=2),
43
+ title="Routing Information",
44
+ border_style="blue",
45
+ )
46
+ )
47
+
48
+ await run_with_client(get_routing)
49
+
50
+ asyncio.run(run_cmd())
51
+
52
+
53
+ # ==================== Thread Subcommand Group ====================
54
+
55
+
56
+ @click.group(name="thread")
57
+ @click.pass_context
58
+ def thread_cmd_group(ctx: click.Context) -> None:
59
+ """Manage Thread protocol settings.
60
+
61
+ Thread is used for smart home devices. Enable/disable
62
+ is under security settings.
63
+ """
64
+ pass
65
+
66
+
67
+ @thread_cmd_group.command(name="show")
68
+ @click.pass_context
69
+ def thread_show(ctx: click.Context) -> None:
70
+ """Show Thread protocol information."""
71
+ cli_ctx = get_cli_context(ctx)
72
+ console = cli_ctx.console
73
+ renderer = cli_ctx.renderer
74
+
75
+ async def run_cmd() -> None:
76
+ async def get_thread(client: EeroClient) -> None:
77
+ with cli_ctx.status("Getting Thread information..."):
78
+ thread_data = await client.get_thread(cli_ctx.network_id)
79
+
80
+ if cli_ctx.is_json_output():
81
+ renderer.render_json(thread_data, "eero.network.thread.show/v1")
82
+ else:
83
+ console.print(
84
+ Panel(
85
+ json.dumps(thread_data, indent=2),
86
+ title="Thread Protocol",
87
+ border_style="blue",
88
+ )
89
+ )
90
+
91
+ await run_with_client(get_thread)
92
+
93
+ asyncio.run(run_cmd())
94
+
95
+
96
+ # ==================== Support Subcommand Group ====================
97
+
98
+
99
+ @click.group(name="support")
100
+ @click.pass_context
101
+ def support_group(ctx: click.Context) -> None:
102
+ """Support and diagnostics.
103
+
104
+ \b
105
+ Commands:
106
+ show - Show support information
107
+ bundle - Export support bundle
108
+ """
109
+ pass
110
+
111
+
112
+ @support_group.command(name="show")
113
+ @click.pass_context
114
+ def support_show(ctx: click.Context) -> None:
115
+ """Show support information."""
116
+ cli_ctx = get_cli_context(ctx)
117
+ console = cli_ctx.console
118
+ renderer = cli_ctx.renderer
119
+
120
+ async def run_cmd() -> None:
121
+ async def get_support(client: EeroClient) -> None:
122
+ with cli_ctx.status("Getting support information..."):
123
+ support_data = await client.get_support(cli_ctx.network_id)
124
+
125
+ if cli_ctx.is_json_output():
126
+ renderer.render_json(support_data, "eero.network.support.show/v1")
127
+ else:
128
+ console.print(
129
+ Panel(
130
+ json.dumps(support_data, indent=2),
131
+ title="Support Information",
132
+ border_style="blue",
133
+ )
134
+ )
135
+
136
+ await run_with_client(get_support)
137
+
138
+ asyncio.run(run_cmd())
139
+
140
+
141
+ @support_group.group(name="bundle")
142
+ @click.pass_context
143
+ def bundle_group(ctx: click.Context) -> None:
144
+ """Manage support bundles."""
145
+ pass
146
+
147
+
148
+ @bundle_group.command(name="export")
149
+ @click.option("--out", "-o", required=True, help="Output file path")
150
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
151
+ @click.pass_context
152
+ def bundle_export(ctx: click.Context, out: str, force: bool) -> None:
153
+ """Export support bundle to file.
154
+
155
+ Creates a diagnostic bundle for Eero support.
156
+ """
157
+ cli_ctx = get_cli_context(ctx)
158
+ console = cli_ctx.console
159
+
160
+ try:
161
+ confirm_or_fail(
162
+ action="export support bundle",
163
+ target=f"to {out}",
164
+ risk=OperationRisk.MEDIUM,
165
+ force=force or cli_ctx.force,
166
+ non_interactive=cli_ctx.non_interactive,
167
+ dry_run=cli_ctx.dry_run,
168
+ )
169
+ except SafetyError as e:
170
+ cli_ctx.renderer.render_error(e.message)
171
+ sys.exit(e.exit_code)
172
+
173
+ async def run_cmd() -> None:
174
+ async def export_bundle(client: EeroClient) -> None:
175
+ with cli_ctx.status("Generating support bundle..."):
176
+ support_data = await client.get_support(cli_ctx.network_id)
177
+ diagnostics = await client.get_diagnostics(cli_ctx.network_id)
178
+
179
+ bundle = {
180
+ "support": support_data,
181
+ "diagnostics": diagnostics,
182
+ }
183
+
184
+ with open(out, "w") as f:
185
+ json.dump(bundle, f, indent=2, default=str)
186
+
187
+ console.print(f"[bold green]Support bundle exported to {out}[/bold green]")
188
+
189
+ await run_with_client(export_bundle)
190
+
191
+ asyncio.run(run_cmd())
@@ -0,0 +1,162 @@
1
+ """Backup network commands for the Eero CLI (Eero Plus feature).
2
+
3
+ Commands:
4
+ - eero network backup show: Show backup network settings
5
+ - eero network backup enable: Enable backup network
6
+ - eero network backup disable: Disable backup network
7
+ - eero network backup status: Show current backup status
8
+ """
9
+
10
+ import asyncio
11
+ import sys
12
+
13
+ import click
14
+ from eero import EeroClient
15
+ from rich.panel import Panel
16
+
17
+ from ...context import EeroCliContext, get_cli_context
18
+ from ...errors import is_premium_error
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="backup")
25
+ @click.pass_context
26
+ def backup_group(ctx: click.Context) -> None:
27
+ """Manage backup network (Eero Plus feature).
28
+
29
+ \b
30
+ Commands:
31
+ show - Show backup network settings
32
+ enable - Enable backup network
33
+ disable - Disable backup network
34
+ status - Show current backup status
35
+ """
36
+ pass
37
+
38
+
39
+ @backup_group.command(name="show")
40
+ @click.pass_context
41
+ def backup_show(ctx: click.Context) -> None:
42
+ """Show backup network configuration."""
43
+ cli_ctx = get_cli_context(ctx)
44
+ console = cli_ctx.console
45
+ renderer = cli_ctx.renderer
46
+
47
+ async def run_cmd() -> None:
48
+ async def get_backup(client: EeroClient) -> None:
49
+ with cli_ctx.status("Getting backup network settings..."):
50
+ try:
51
+ backup_data = await client.get_backup_network(cli_ctx.network_id)
52
+ except Exception as e:
53
+ if is_premium_error(e):
54
+ console.print("[yellow]Backup network requires Eero Plus[/yellow]")
55
+ sys.exit(ExitCode.PREMIUM_REQUIRED)
56
+ raise
57
+
58
+ if cli_ctx.is_json_output():
59
+ renderer.render_json(backup_data, "eero.network.backup.show/v1")
60
+ else:
61
+ enabled = backup_data.get("enabled", False)
62
+ content = (
63
+ f"[bold]Enabled:[/bold] {'[green]Yes[/green]' if enabled else '[dim]No[/dim]'}"
64
+ )
65
+ console.print(Panel(content, title="Backup Network", border_style="blue"))
66
+
67
+ await run_with_client(get_backup)
68
+
69
+ asyncio.run(run_cmd())
70
+
71
+
72
+ @backup_group.command(name="enable")
73
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
74
+ @click.pass_context
75
+ def backup_enable(ctx: click.Context, force: bool) -> None:
76
+ """Enable backup network."""
77
+ cli_ctx = get_cli_context(ctx)
78
+ _set_backup(cli_ctx, True, force)
79
+
80
+
81
+ @backup_group.command(name="disable")
82
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
83
+ @click.pass_context
84
+ def backup_disable(ctx: click.Context, force: bool) -> None:
85
+ """Disable backup network."""
86
+ cli_ctx = get_cli_context(ctx)
87
+ _set_backup(cli_ctx, False, force)
88
+
89
+
90
+ def _set_backup(cli_ctx: EeroCliContext, enable: bool, force: bool) -> None:
91
+ """Set backup network state."""
92
+ console = cli_ctx.console
93
+ action = "enable" if enable else "disable"
94
+
95
+ try:
96
+ confirm_or_fail(
97
+ action=f"{action} backup network",
98
+ target="network",
99
+ risk=OperationRisk.MEDIUM,
100
+ force=force or cli_ctx.force,
101
+ non_interactive=cli_ctx.non_interactive,
102
+ dry_run=cli_ctx.dry_run,
103
+ )
104
+ except SafetyError as e:
105
+ cli_ctx.renderer.render_error(e.message)
106
+ sys.exit(e.exit_code)
107
+
108
+ async def run_cmd() -> None:
109
+ async def set_backup(client: EeroClient) -> None:
110
+ with cli_ctx.status(f"{action.capitalize()}ing backup network..."):
111
+ try:
112
+ result = await client.set_backup_network(enable, cli_ctx.network_id)
113
+ except Exception as e:
114
+ if is_premium_error(e):
115
+ console.print("[yellow]Backup network requires Eero Plus[/yellow]")
116
+ sys.exit(ExitCode.PREMIUM_REQUIRED)
117
+ raise
118
+
119
+ if result:
120
+ console.print(f"[bold green]Backup network {action}d[/bold green]")
121
+ else:
122
+ console.print(f"[red]Failed to {action} backup network[/red]")
123
+ sys.exit(ExitCode.GENERIC_ERROR)
124
+
125
+ await run_with_client(set_backup)
126
+
127
+ asyncio.run(run_cmd())
128
+
129
+
130
+ @backup_group.command(name="status")
131
+ @click.pass_context
132
+ def backup_status(ctx: click.Context) -> None:
133
+ """Show current backup network status."""
134
+ cli_ctx = get_cli_context(ctx)
135
+ console = cli_ctx.console
136
+ renderer = cli_ctx.renderer
137
+
138
+ async def run_cmd() -> None:
139
+ async def get_status(client: EeroClient) -> None:
140
+ with cli_ctx.status("Getting backup status..."):
141
+ try:
142
+ status_data = await client.get_backup_status(cli_ctx.network_id)
143
+ is_using = await client.is_using_backup(cli_ctx.network_id)
144
+ except Exception as e:
145
+ if is_premium_error(e):
146
+ console.print("[yellow]Backup network requires Eero Plus[/yellow]")
147
+ sys.exit(ExitCode.PREMIUM_REQUIRED)
148
+ raise
149
+
150
+ if cli_ctx.is_json_output():
151
+ renderer.render_json(
152
+ {**status_data, "using_backup": is_using}, "eero.network.backup.status/v1"
153
+ )
154
+ else:
155
+ style = "yellow" if is_using else "green"
156
+ status = "Using Backup" if is_using else "Primary Connection"
157
+ content = f"[bold]Status:[/bold] [{style}]{status}[/{style}]"
158
+ console.print(Panel(content, title="Backup Status", border_style=style))
159
+
160
+ await run_with_client(get_status)
161
+
162
+ asyncio.run(run_cmd())
@@ -0,0 +1,331 @@
1
+ """Base network commands for the Eero CLI.
2
+
3
+ Commands:
4
+ - eero network list: List all networks
5
+ - eero network use: Set preferred network
6
+ - eero network show: Show network details
7
+ - eero network rename: Rename network
8
+ - eero network premium: Check Eero Plus status
9
+ """
10
+
11
+ import asyncio
12
+ import sys
13
+ from typing import Literal, Optional
14
+
15
+ import click
16
+ from eero import EeroClient
17
+ from rich.panel import Panel
18
+ from rich.table import Table
19
+
20
+ from ...context import ensure_cli_context, get_cli_context
21
+ from ...exit_codes import ExitCode
22
+ from ...formatting import get_network_status_value
23
+ from ...options import apply_options, force_option, network_option, output_option
24
+ from ...output import OutputFormat
25
+ from ...safety import OperationRisk, SafetyError, confirm_or_fail
26
+ from ...utils import run_with_client, set_preferred_network
27
+
28
+
29
+ @click.group(name="network")
30
+ @click.pass_context
31
+ def network_group(ctx: click.Context) -> None:
32
+ """Manage network settings.
33
+
34
+ \b
35
+ Commands:
36
+ list - List all networks
37
+ use - Set preferred network
38
+ show - Show network details
39
+ rename - Rename network (SSID)
40
+ premium - Check Eero Plus status
41
+ dns - DNS settings
42
+ security - Security settings
43
+ sqm - SQM/QoS settings
44
+ guest - Guest network
45
+ backup - Backup network (Eero Plus)
46
+ speedtest - Speed tests
47
+ forwards - Port forwarding
48
+ dhcp - DHCP settings
49
+ routing - Routing information
50
+ thread - Thread protocol
51
+ support - Support bundle
52
+
53
+ \b
54
+ Examples:
55
+ eero network list # List all networks
56
+ eero network show # Show current network
57
+ eero network dns show # Show DNS settings
58
+ eero network guest enable # Enable guest network
59
+ """
60
+ ensure_cli_context(ctx)
61
+
62
+
63
+ @network_group.command(name="list")
64
+ @output_option
65
+ @click.pass_context
66
+ def network_list(ctx: click.Context, output: Optional[str]) -> None:
67
+ """List all networks.
68
+
69
+ Shows all networks associated with your account.
70
+ """
71
+ cli_ctx = apply_options(ctx, output=output)
72
+ console = cli_ctx.console
73
+
74
+ async def run_cmd() -> None:
75
+ async def get_networks(client: EeroClient) -> None:
76
+ with cli_ctx.status("Getting networks..."):
77
+ networks = await client.get_networks()
78
+
79
+ if not networks:
80
+ console.print("[yellow]No networks found[/yellow]")
81
+ return
82
+
83
+ if cli_ctx.is_structured_output():
84
+ data = [
85
+ {
86
+ "id": n.id,
87
+ "name": n.name,
88
+ "status": get_network_status_value(n),
89
+ "public_ip": n.public_ip,
90
+ "isp_name": n.isp_name,
91
+ }
92
+ for n in networks
93
+ ]
94
+ cli_ctx.render_structured(data, "eero.network.list/v1")
95
+ elif cli_ctx.output_format == OutputFormat.LIST:
96
+ for n in networks:
97
+ status = get_network_status_value(n)
98
+ # Use print() with fixed-width columns for alignment
99
+ print(
100
+ f"{n.id or '':<12} {n.name or '':<25} {status:<15} "
101
+ f"{n.public_ip or 'N/A':<15} {n.isp_name or 'N/A'}"
102
+ )
103
+ else:
104
+ table = Table(title="Eero Networks")
105
+ table.add_column("ID", style="dim")
106
+ table.add_column("Name", style="cyan")
107
+ table.add_column("Status", style="green")
108
+ table.add_column("Public IP", style="blue")
109
+ table.add_column("ISP", style="magenta")
110
+
111
+ for n in networks:
112
+ status = get_network_status_value(n)
113
+ if "online" in status.lower() or "connected" in status.lower():
114
+ status_display = f"[green]{status}[/green]"
115
+ elif "offline" in status.lower():
116
+ status_display = f"[red]{status}[/red]"
117
+ else:
118
+ status_display = f"[yellow]{status}[/yellow]"
119
+
120
+ table.add_row(
121
+ n.id or "",
122
+ n.name or "",
123
+ status_display,
124
+ n.public_ip or "N/A",
125
+ n.isp_name or "N/A",
126
+ )
127
+
128
+ console.print(table)
129
+
130
+ await run_with_client(get_networks)
131
+
132
+ asyncio.run(run_cmd())
133
+
134
+
135
+ @network_group.command(name="use")
136
+ @click.argument("network_id")
137
+ @click.pass_context
138
+ def network_use(ctx: click.Context, network_id: str) -> None:
139
+ """Set preferred network for future commands.
140
+
141
+ \b
142
+ Arguments:
143
+ NETWORK_ID The network ID to use
144
+
145
+ \b
146
+ Examples:
147
+ eero network use abc123
148
+ """
149
+ cli_ctx = get_cli_context(ctx)
150
+ console = cli_ctx.console
151
+
152
+ async def run_cmd() -> None:
153
+ async def set_preferred(client: EeroClient) -> None:
154
+ client.set_preferred_network(network_id)
155
+ set_preferred_network(network_id)
156
+
157
+ try:
158
+ with cli_ctx.status(f"Verifying network {network_id}..."):
159
+ net = await client.get_network(network_id)
160
+ console.print(
161
+ f"[bold green]Preferred network set to '{net.name}' ({network_id})[/bold green]"
162
+ )
163
+ except Exception as e:
164
+ console.print(
165
+ f"[yellow]Network ID set to {network_id}, but could not verify: {e}[/yellow]"
166
+ )
167
+
168
+ await run_with_client(set_preferred)
169
+
170
+ asyncio.run(run_cmd())
171
+
172
+
173
+ @network_group.command(name="show")
174
+ @output_option
175
+ @network_option
176
+ @click.pass_context
177
+ def network_show(ctx: click.Context, output: Optional[str], network_id: Optional[str]) -> None:
178
+ """Show current network details.
179
+
180
+ Displays comprehensive information about the current network
181
+ including settings, DHCP, and recent speed test results.
182
+ """
183
+ cli_ctx = apply_options(ctx, output=output, network_id=network_id)
184
+
185
+ async def run_cmd() -> None:
186
+ async def get_network(client: EeroClient) -> None:
187
+ with cli_ctx.status("Getting network details..."):
188
+ network = await client.get_network(cli_ctx.network_id)
189
+
190
+ if cli_ctx.is_structured_output():
191
+ cli_ctx.render_structured(
192
+ network.model_dump(mode="json"),
193
+ "eero.network.show/v1",
194
+ )
195
+ else:
196
+ from ...formatting import print_network_details
197
+
198
+ detail: Literal["brief", "full"] = (
199
+ "full" if cli_ctx.detail_level == "full" else "brief"
200
+ )
201
+ print_network_details(network, detail_level=detail)
202
+
203
+ await run_with_client(get_network)
204
+
205
+ asyncio.run(run_cmd())
206
+
207
+
208
+ @network_group.command(name="rename")
209
+ @click.option("--name", required=True, help="New network name (SSID)")
210
+ @force_option
211
+ @network_option
212
+ @click.pass_context
213
+ def network_rename(
214
+ ctx: click.Context, name: str, force: Optional[bool], network_id: Optional[str]
215
+ ) -> None:
216
+ """Rename the network (change SSID).
217
+
218
+ Note: May require network restart to take effect.
219
+
220
+ \b
221
+ Options:
222
+ --name TEXT New network name (required)
223
+
224
+ \b
225
+ Examples:
226
+ eero network rename --name "My Home WiFi"
227
+ """
228
+ cli_ctx = apply_options(ctx, network_id=network_id, force=force)
229
+ console = cli_ctx.console
230
+
231
+ try:
232
+ confirm_or_fail(
233
+ action="rename network",
234
+ target=f"to '{name}'",
235
+ risk=OperationRisk.MEDIUM,
236
+ force=cli_ctx.force,
237
+ non_interactive=cli_ctx.non_interactive,
238
+ dry_run=cli_ctx.dry_run,
239
+ )
240
+ except SafetyError as e:
241
+ cli_ctx.renderer.render_error(e.message)
242
+ sys.exit(e.exit_code)
243
+
244
+ async def run_cmd() -> None:
245
+ async def set_name(client: EeroClient) -> None:
246
+ with cli_ctx.status(f"Renaming network to '{name}'..."):
247
+ result = await client.set_network_name(name, cli_ctx.network_id)
248
+
249
+ if result:
250
+ console.print(f"[bold green]Network renamed to '{name}'[/bold green]")
251
+ console.print("[dim]Note: Network restart may be required[/dim]")
252
+ else:
253
+ console.print("[bold red]Failed to rename network[/bold red]")
254
+ sys.exit(ExitCode.GENERIC_ERROR)
255
+
256
+ await run_with_client(set_name)
257
+
258
+ asyncio.run(run_cmd())
259
+
260
+
261
+ @network_group.command(name="premium")
262
+ @output_option
263
+ @network_option
264
+ @click.pass_context
265
+ def network_premium(ctx: click.Context, output: Optional[str], network_id: Optional[str]) -> None:
266
+ """Check Eero Plus subscription status.
267
+
268
+ Shows whether Eero Plus/Secure is active and which
269
+ features are available.
270
+ """
271
+ cli_ctx = apply_options(ctx, output=output, network_id=network_id)
272
+ console = cli_ctx.console
273
+ renderer = cli_ctx.renderer
274
+
275
+ async def run_cmd() -> None:
276
+ async def check_premium(client: EeroClient) -> None:
277
+ with cli_ctx.status("Checking premium status..."):
278
+ premium_data = await client.get_premium_status(cli_ctx.network_id)
279
+ is_premium = await client.is_premium(cli_ctx.network_id)
280
+
281
+ if cli_ctx.is_json_output():
282
+ renderer.render_json(
283
+ {**premium_data, "is_active": is_premium},
284
+ "eero.network.premium/v1",
285
+ )
286
+ else:
287
+ status_style = "green" if is_premium else "yellow"
288
+ status_text = "Active" if is_premium else "Not Active"
289
+
290
+ content = f"[bold]Eero Plus:[/bold] [{status_style}]{status_text}[/{status_style}]"
291
+ if "plan" in premium_data:
292
+ content += f"\n[bold]Plan:[/bold] {premium_data['plan']}"
293
+ if "expires_at" in premium_data:
294
+ content += f"\n[bold]Expires:[/bold] {premium_data['expires_at']}"
295
+
296
+ console.print(
297
+ Panel(
298
+ content,
299
+ title="Premium Status",
300
+ border_style="blue" if is_premium else "yellow",
301
+ )
302
+ )
303
+
304
+ await run_with_client(check_premium)
305
+
306
+ asyncio.run(run_cmd())
307
+
308
+
309
+ # Import and register subcommand groups after network_group is defined
310
+ from .advanced import routing_show, support_group, thread_cmd_group # noqa: E402
311
+ from .backup import backup_group # noqa: E402
312
+ from .dhcp import dhcp_group # noqa: E402
313
+ from .dns import dns_group # noqa: E402
314
+ from .forwards import forwards_group # noqa: E402
315
+ from .guest import guest_group # noqa: E402
316
+ from .security import security_group # noqa: E402
317
+ from .speedtest import speedtest_group # noqa: E402
318
+ from .sqm import sqm_group # noqa: E402
319
+
320
+ # Register all subcommand groups
321
+ network_group.add_command(dns_group)
322
+ network_group.add_command(security_group)
323
+ network_group.add_command(sqm_group)
324
+ network_group.add_command(guest_group)
325
+ network_group.add_command(backup_group)
326
+ network_group.add_command(speedtest_group)
327
+ network_group.add_command(forwards_group)
328
+ network_group.add_command(dhcp_group)
329
+ network_group.add_command(routing_show)
330
+ network_group.add_command(thread_cmd_group)
331
+ network_group.add_command(support_group)