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.
- eeroctl/__init__.py +19 -0
- eeroctl/commands/__init__.py +32 -0
- eeroctl/commands/activity.py +237 -0
- eeroctl/commands/auth.py +471 -0
- eeroctl/commands/completion.py +142 -0
- eeroctl/commands/device.py +492 -0
- eeroctl/commands/eero/__init__.py +12 -0
- eeroctl/commands/eero/base.py +224 -0
- eeroctl/commands/eero/led.py +154 -0
- eeroctl/commands/eero/nightlight.py +235 -0
- eeroctl/commands/eero/updates.py +82 -0
- eeroctl/commands/network/__init__.py +18 -0
- eeroctl/commands/network/advanced.py +191 -0
- eeroctl/commands/network/backup.py +162 -0
- eeroctl/commands/network/base.py +331 -0
- eeroctl/commands/network/dhcp.py +118 -0
- eeroctl/commands/network/dns.py +197 -0
- eeroctl/commands/network/forwards.py +115 -0
- eeroctl/commands/network/guest.py +162 -0
- eeroctl/commands/network/security.py +162 -0
- eeroctl/commands/network/speedtest.py +99 -0
- eeroctl/commands/network/sqm.py +194 -0
- eeroctl/commands/profile.py +671 -0
- eeroctl/commands/troubleshoot.py +317 -0
- eeroctl/context.py +254 -0
- eeroctl/errors.py +156 -0
- eeroctl/exit_codes.py +68 -0
- eeroctl/formatting/__init__.py +90 -0
- eeroctl/formatting/base.py +181 -0
- eeroctl/formatting/device.py +430 -0
- eeroctl/formatting/eero.py +591 -0
- eeroctl/formatting/misc.py +87 -0
- eeroctl/formatting/network.py +659 -0
- eeroctl/formatting/profile.py +443 -0
- eeroctl/main.py +161 -0
- eeroctl/options.py +429 -0
- eeroctl/output.py +739 -0
- eeroctl/safety.py +259 -0
- eeroctl/utils.py +181 -0
- eeroctl-1.7.1.dist-info/METADATA +115 -0
- eeroctl-1.7.1.dist-info/RECORD +45 -0
- eeroctl-1.7.1.dist-info/WHEEL +5 -0
- eeroctl-1.7.1.dist-info/entry_points.txt +3 -0
- eeroctl-1.7.1.dist-info/licenses/LICENSE +21 -0
- 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)
|