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,118 @@
|
|
|
1
|
+
"""DHCP commands for the Eero CLI.
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
- eero network dhcp reservations: List DHCP reservations
|
|
5
|
+
- eero network dhcp leases: List current DHCP leases
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from eero import EeroClient
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from ...context import get_cli_context
|
|
15
|
+
from ...utils import run_with_client
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group(name="dhcp")
|
|
19
|
+
@click.pass_context
|
|
20
|
+
def dhcp_group(ctx: click.Context) -> None:
|
|
21
|
+
"""Manage DHCP settings.
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
Commands:
|
|
25
|
+
reservations - List DHCP reservations
|
|
26
|
+
leases - List current DHCP leases
|
|
27
|
+
reserve - Create a reservation (stub)
|
|
28
|
+
unreserve - Remove a reservation (stub)
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dhcp_group.command(name="reservations")
|
|
34
|
+
@click.pass_context
|
|
35
|
+
def dhcp_reservations(ctx: click.Context) -> None:
|
|
36
|
+
"""List DHCP reservations."""
|
|
37
|
+
cli_ctx = get_cli_context(ctx)
|
|
38
|
+
console = cli_ctx.console
|
|
39
|
+
renderer = cli_ctx.renderer
|
|
40
|
+
|
|
41
|
+
async def run_cmd() -> None:
|
|
42
|
+
async def get_reservations(client: EeroClient) -> None:
|
|
43
|
+
with cli_ctx.status("Getting DHCP reservations..."):
|
|
44
|
+
reservations = await client.get_reservations(cli_ctx.network_id)
|
|
45
|
+
|
|
46
|
+
if cli_ctx.is_json_output():
|
|
47
|
+
renderer.render_json(reservations, "eero.network.dhcp.reservations/v1")
|
|
48
|
+
else:
|
|
49
|
+
if not reservations:
|
|
50
|
+
console.print("[yellow]No DHCP reservations configured[/yellow]")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
table = Table(title="DHCP Reservations")
|
|
54
|
+
table.add_column("MAC", style="yellow")
|
|
55
|
+
table.add_column("IP", style="green")
|
|
56
|
+
table.add_column("Hostname", style="cyan")
|
|
57
|
+
|
|
58
|
+
for res in reservations:
|
|
59
|
+
table.add_row(
|
|
60
|
+
res.get("mac", ""),
|
|
61
|
+
res.get("ip", ""),
|
|
62
|
+
res.get("hostname", ""),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
console.print(table)
|
|
66
|
+
|
|
67
|
+
await run_with_client(get_reservations)
|
|
68
|
+
|
|
69
|
+
asyncio.run(run_cmd())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dhcp_group.command(name="leases")
|
|
73
|
+
@click.pass_context
|
|
74
|
+
def dhcp_leases(ctx: click.Context) -> None:
|
|
75
|
+
"""List current DHCP leases."""
|
|
76
|
+
cli_ctx = get_cli_context(ctx)
|
|
77
|
+
console = cli_ctx.console
|
|
78
|
+
renderer = cli_ctx.renderer
|
|
79
|
+
|
|
80
|
+
async def run_cmd() -> None:
|
|
81
|
+
async def get_leases(client: EeroClient) -> None:
|
|
82
|
+
with cli_ctx.status("Getting DHCP leases..."):
|
|
83
|
+
# Leases come from devices list
|
|
84
|
+
devices = await client.get_devices(cli_ctx.network_id)
|
|
85
|
+
|
|
86
|
+
if cli_ctx.is_json_output():
|
|
87
|
+
data = [
|
|
88
|
+
{
|
|
89
|
+
"ip": d.ip or d.ipv4,
|
|
90
|
+
"mac": d.mac,
|
|
91
|
+
"hostname": d.hostname,
|
|
92
|
+
"name": d.display_name or d.nickname,
|
|
93
|
+
}
|
|
94
|
+
for d in devices
|
|
95
|
+
if d.ip or d.ipv4
|
|
96
|
+
]
|
|
97
|
+
renderer.render_json(data, "eero.network.dhcp.leases/v1")
|
|
98
|
+
else:
|
|
99
|
+
table = Table(title="DHCP Leases")
|
|
100
|
+
table.add_column("IP", style="green")
|
|
101
|
+
table.add_column("MAC", style="yellow")
|
|
102
|
+
table.add_column("Hostname", style="cyan")
|
|
103
|
+
table.add_column("Name", style="blue")
|
|
104
|
+
|
|
105
|
+
for d in devices:
|
|
106
|
+
if d.ip or d.ipv4:
|
|
107
|
+
table.add_row(
|
|
108
|
+
d.ip or d.ipv4 or "",
|
|
109
|
+
d.mac or "",
|
|
110
|
+
d.hostname or "",
|
|
111
|
+
d.display_name or d.nickname or "",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
console.print(table)
|
|
115
|
+
|
|
116
|
+
await run_with_client(get_leases)
|
|
117
|
+
|
|
118
|
+
asyncio.run(run_cmd())
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""DNS commands for the Eero CLI.
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
- eero network dns show: Show DNS settings
|
|
5
|
+
- eero network dns mode set: Set DNS mode
|
|
6
|
+
- eero network dns caching: Enable/disable DNS caching
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from eero import EeroClient
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
|
|
16
|
+
from ...context import EeroCliContext, get_cli_context
|
|
17
|
+
from ...exit_codes import ExitCode
|
|
18
|
+
from ...safety import OperationRisk, SafetyError, confirm_or_fail
|
|
19
|
+
from ...utils import run_with_client
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group(name="dns")
|
|
23
|
+
@click.pass_context
|
|
24
|
+
def dns_group(ctx: click.Context) -> None:
|
|
25
|
+
"""Manage DNS settings.
|
|
26
|
+
|
|
27
|
+
\b
|
|
28
|
+
Commands:
|
|
29
|
+
show - Show current DNS settings
|
|
30
|
+
mode - Set DNS mode
|
|
31
|
+
caching - Enable/disable DNS caching
|
|
32
|
+
|
|
33
|
+
\b
|
|
34
|
+
Examples:
|
|
35
|
+
eero network dns show
|
|
36
|
+
eero network dns mode set google
|
|
37
|
+
eero network dns caching enable
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dns_group.command(name="show")
|
|
43
|
+
@click.pass_context
|
|
44
|
+
def dns_show(ctx: click.Context) -> None:
|
|
45
|
+
"""Show current DNS settings."""
|
|
46
|
+
cli_ctx = get_cli_context(ctx)
|
|
47
|
+
console = cli_ctx.console
|
|
48
|
+
renderer = cli_ctx.renderer
|
|
49
|
+
|
|
50
|
+
async def run_cmd() -> None:
|
|
51
|
+
async def get_dns(client: EeroClient) -> None:
|
|
52
|
+
with cli_ctx.status("Getting DNS settings..."):
|
|
53
|
+
dns_data = await client.get_dns_settings(cli_ctx.network_id)
|
|
54
|
+
|
|
55
|
+
if cli_ctx.is_json_output():
|
|
56
|
+
renderer.render_json(dns_data, "eero.network.dns.show/v1")
|
|
57
|
+
else:
|
|
58
|
+
caching = dns_data.get("dns_caching", False)
|
|
59
|
+
mode = dns_data.get("dns_mode", "auto")
|
|
60
|
+
custom_dns = dns_data.get("custom_dns", [])
|
|
61
|
+
|
|
62
|
+
content = (
|
|
63
|
+
f"[bold]DNS Mode:[/bold] {mode}\n"
|
|
64
|
+
f"[bold]DNS Caching:[/bold] {'[green]Enabled[/green]' if caching else '[dim]Disabled[/dim]'}"
|
|
65
|
+
)
|
|
66
|
+
if custom_dns:
|
|
67
|
+
content += f"\n[bold]Custom DNS:[/bold] {', '.join(custom_dns)}"
|
|
68
|
+
|
|
69
|
+
console.print(Panel(content, title="DNS Settings", border_style="blue"))
|
|
70
|
+
|
|
71
|
+
await run_with_client(get_dns)
|
|
72
|
+
|
|
73
|
+
asyncio.run(run_cmd())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dns_group.group(name="mode")
|
|
77
|
+
@click.pass_context
|
|
78
|
+
def dns_mode_group(ctx: click.Context) -> None:
|
|
79
|
+
"""Set DNS mode."""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dns_mode_group.command(name="set")
|
|
84
|
+
@click.argument("mode", type=click.Choice(["auto", "cloudflare", "google", "opendns", "custom"]))
|
|
85
|
+
@click.option("--servers", "-s", multiple=True, help="Custom DNS servers (for 'custom' mode)")
|
|
86
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
87
|
+
@click.pass_context
|
|
88
|
+
def dns_mode_set(ctx: click.Context, mode: str, servers: tuple, force: bool) -> None:
|
|
89
|
+
"""Set DNS mode.
|
|
90
|
+
|
|
91
|
+
\b
|
|
92
|
+
Modes:
|
|
93
|
+
auto - Use ISP DNS
|
|
94
|
+
cloudflare - Use Cloudflare (1.1.1.1)
|
|
95
|
+
google - Use Google (8.8.8.8)
|
|
96
|
+
opendns - Use OpenDNS
|
|
97
|
+
custom - Use custom servers (requires --servers)
|
|
98
|
+
|
|
99
|
+
\b
|
|
100
|
+
Examples:
|
|
101
|
+
eero network dns mode set google
|
|
102
|
+
eero network dns mode set custom --servers 8.8.8.8 --servers 8.8.4.4
|
|
103
|
+
"""
|
|
104
|
+
cli_ctx = get_cli_context(ctx)
|
|
105
|
+
console = cli_ctx.console
|
|
106
|
+
|
|
107
|
+
if mode == "custom" and not servers:
|
|
108
|
+
console.print("[red]Error: --servers required for custom mode[/red]")
|
|
109
|
+
sys.exit(ExitCode.USAGE_ERROR)
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
confirm_or_fail(
|
|
113
|
+
action="change DNS mode",
|
|
114
|
+
target=f"to {mode}",
|
|
115
|
+
risk=OperationRisk.MEDIUM,
|
|
116
|
+
force=force or cli_ctx.force,
|
|
117
|
+
non_interactive=cli_ctx.non_interactive,
|
|
118
|
+
dry_run=cli_ctx.dry_run,
|
|
119
|
+
)
|
|
120
|
+
except SafetyError as e:
|
|
121
|
+
cli_ctx.renderer.render_error(e.message)
|
|
122
|
+
sys.exit(e.exit_code)
|
|
123
|
+
|
|
124
|
+
async def run_cmd() -> None:
|
|
125
|
+
async def set_mode(client: EeroClient) -> None:
|
|
126
|
+
custom_servers = list(servers) if servers else None
|
|
127
|
+
with cli_ctx.status(f"Setting DNS mode to {mode}..."):
|
|
128
|
+
result = await client.set_dns_mode(mode, custom_servers, cli_ctx.network_id)
|
|
129
|
+
|
|
130
|
+
if result:
|
|
131
|
+
console.print(f"[bold green]DNS mode set to {mode}[/bold green]")
|
|
132
|
+
else:
|
|
133
|
+
console.print("[red]Failed to set DNS mode[/red]")
|
|
134
|
+
sys.exit(ExitCode.GENERIC_ERROR)
|
|
135
|
+
|
|
136
|
+
await run_with_client(set_mode)
|
|
137
|
+
|
|
138
|
+
asyncio.run(run_cmd())
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dns_group.group(name="caching")
|
|
142
|
+
@click.pass_context
|
|
143
|
+
def dns_caching_group(ctx: click.Context) -> None:
|
|
144
|
+
"""Manage DNS caching."""
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dns_caching_group.command(name="enable")
|
|
149
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
150
|
+
@click.pass_context
|
|
151
|
+
def dns_caching_enable(ctx: click.Context, force: bool) -> None:
|
|
152
|
+
"""Enable DNS caching."""
|
|
153
|
+
cli_ctx = get_cli_context(ctx)
|
|
154
|
+
_set_dns_caching(cli_ctx, True, force)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dns_caching_group.command(name="disable")
|
|
158
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
159
|
+
@click.pass_context
|
|
160
|
+
def dns_caching_disable(ctx: click.Context, force: bool) -> None:
|
|
161
|
+
"""Disable DNS caching."""
|
|
162
|
+
cli_ctx = get_cli_context(ctx)
|
|
163
|
+
_set_dns_caching(cli_ctx, False, force)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _set_dns_caching(cli_ctx: EeroCliContext, enable: bool, force: bool) -> None:
|
|
167
|
+
"""Set DNS caching state."""
|
|
168
|
+
console = cli_ctx.console
|
|
169
|
+
action = "enable" if enable else "disable"
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
confirm_or_fail(
|
|
173
|
+
action=f"{action} DNS caching",
|
|
174
|
+
target="network",
|
|
175
|
+
risk=OperationRisk.MEDIUM,
|
|
176
|
+
force=force or cli_ctx.force,
|
|
177
|
+
non_interactive=cli_ctx.non_interactive,
|
|
178
|
+
dry_run=cli_ctx.dry_run,
|
|
179
|
+
)
|
|
180
|
+
except SafetyError as e:
|
|
181
|
+
cli_ctx.renderer.render_error(e.message)
|
|
182
|
+
sys.exit(e.exit_code)
|
|
183
|
+
|
|
184
|
+
async def run_cmd() -> None:
|
|
185
|
+
async def set_caching(client: EeroClient) -> None:
|
|
186
|
+
with cli_ctx.status(f"{action.capitalize()}ing DNS caching..."):
|
|
187
|
+
result = await client.set_dns_caching(enable, cli_ctx.network_id)
|
|
188
|
+
|
|
189
|
+
if result:
|
|
190
|
+
console.print(f"[bold green]DNS caching {action}d[/bold green]")
|
|
191
|
+
else:
|
|
192
|
+
console.print(f"[red]Failed to {action} DNS caching[/red]")
|
|
193
|
+
sys.exit(ExitCode.GENERIC_ERROR)
|
|
194
|
+
|
|
195
|
+
await run_with_client(set_caching)
|
|
196
|
+
|
|
197
|
+
asyncio.run(run_cmd())
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Port forwarding commands for the Eero CLI.
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
- eero network forwards list: List all port forwards
|
|
5
|
+
- eero network forwards show: Show details of a port forward
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from eero import EeroClient
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from ...context import get_cli_context
|
|
17
|
+
from ...exit_codes import ExitCode
|
|
18
|
+
from ...utils import run_with_client
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group(name="forwards")
|
|
22
|
+
@click.pass_context
|
|
23
|
+
def forwards_group(ctx: click.Context) -> None:
|
|
24
|
+
"""Manage port forwarding rules.
|
|
25
|
+
|
|
26
|
+
\b
|
|
27
|
+
Commands:
|
|
28
|
+
list - List all port forwards
|
|
29
|
+
show - Show details of a port forward
|
|
30
|
+
add - Add a new port forward (stub)
|
|
31
|
+
delete - Delete a port forward (stub)
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@forwards_group.command(name="list")
|
|
37
|
+
@click.pass_context
|
|
38
|
+
def forwards_list(ctx: click.Context) -> None:
|
|
39
|
+
"""List all port forwarding rules."""
|
|
40
|
+
cli_ctx = get_cli_context(ctx)
|
|
41
|
+
console = cli_ctx.console
|
|
42
|
+
renderer = cli_ctx.renderer
|
|
43
|
+
|
|
44
|
+
async def run_cmd() -> None:
|
|
45
|
+
async def get_forwards(client: EeroClient) -> None:
|
|
46
|
+
with cli_ctx.status("Getting port forwards..."):
|
|
47
|
+
forwards = await client.get_forwards(cli_ctx.network_id)
|
|
48
|
+
|
|
49
|
+
if cli_ctx.is_json_output():
|
|
50
|
+
renderer.render_json(forwards, "eero.network.forwards.list/v1")
|
|
51
|
+
else:
|
|
52
|
+
if not forwards:
|
|
53
|
+
console.print("[yellow]No port forwards configured[/yellow]")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
table = Table(title="Port Forwards")
|
|
57
|
+
table.add_column("ID", style="dim")
|
|
58
|
+
table.add_column("Name", style="cyan")
|
|
59
|
+
table.add_column("External Port")
|
|
60
|
+
table.add_column("Internal IP")
|
|
61
|
+
table.add_column("Internal Port")
|
|
62
|
+
table.add_column("Protocol")
|
|
63
|
+
|
|
64
|
+
for fwd in forwards:
|
|
65
|
+
table.add_row(
|
|
66
|
+
str(fwd.get("id", "")),
|
|
67
|
+
fwd.get("name", ""),
|
|
68
|
+
str(fwd.get("external_port", "")),
|
|
69
|
+
fwd.get("internal_ip", ""),
|
|
70
|
+
str(fwd.get("internal_port", "")),
|
|
71
|
+
fwd.get("protocol", "tcp"),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
console.print(table)
|
|
75
|
+
|
|
76
|
+
await run_with_client(get_forwards)
|
|
77
|
+
|
|
78
|
+
asyncio.run(run_cmd())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@forwards_group.command(name="show")
|
|
82
|
+
@click.argument("forward_id")
|
|
83
|
+
@click.pass_context
|
|
84
|
+
def forwards_show(ctx: click.Context, forward_id: str) -> None:
|
|
85
|
+
"""Show details of a port forward."""
|
|
86
|
+
cli_ctx = get_cli_context(ctx)
|
|
87
|
+
console = cli_ctx.console
|
|
88
|
+
renderer = cli_ctx.renderer
|
|
89
|
+
|
|
90
|
+
async def run_cmd() -> None:
|
|
91
|
+
async def get_forward(client: EeroClient) -> None:
|
|
92
|
+
with cli_ctx.status("Getting port forward..."):
|
|
93
|
+
forwards = await client.get_forwards(cli_ctx.network_id)
|
|
94
|
+
|
|
95
|
+
target = None
|
|
96
|
+
for fwd in forwards:
|
|
97
|
+
if str(fwd.get("id")) == forward_id:
|
|
98
|
+
target = fwd
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
if not target:
|
|
102
|
+
console.print(f"[red]Port forward '{forward_id}' not found[/red]")
|
|
103
|
+
sys.exit(ExitCode.NOT_FOUND)
|
|
104
|
+
|
|
105
|
+
if cli_ctx.is_json_output():
|
|
106
|
+
renderer.render_json(target, "eero.network.forwards.show/v1")
|
|
107
|
+
else:
|
|
108
|
+
content = "\n".join(f"[bold]{k}:[/bold] {v}" for k, v in target.items())
|
|
109
|
+
console.print(
|
|
110
|
+
Panel(content, title=f"Port Forward: {forward_id}", border_style="blue")
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
await run_with_client(get_forward)
|
|
114
|
+
|
|
115
|
+
asyncio.run(run_cmd())
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Guest network commands for the Eero CLI.
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
- eero network guest show: Show guest network settings
|
|
5
|
+
- eero network guest enable: Enable guest network
|
|
6
|
+
- eero network guest disable: Disable guest network
|
|
7
|
+
- eero network guest set: Configure guest network
|
|
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="guest")
|
|
25
|
+
@click.pass_context
|
|
26
|
+
def guest_group(ctx: click.Context) -> None:
|
|
27
|
+
"""Manage guest network.
|
|
28
|
+
|
|
29
|
+
\b
|
|
30
|
+
Commands:
|
|
31
|
+
show - Show guest network settings
|
|
32
|
+
enable - Enable guest network
|
|
33
|
+
disable - Disable guest network
|
|
34
|
+
set - Configure guest network
|
|
35
|
+
|
|
36
|
+
\b
|
|
37
|
+
Examples:
|
|
38
|
+
eero network guest show
|
|
39
|
+
eero network guest enable
|
|
40
|
+
eero network guest set --name "Guest WiFi" --password "secret123"
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@guest_group.command(name="show")
|
|
46
|
+
@click.pass_context
|
|
47
|
+
def guest_show(ctx: click.Context) -> None:
|
|
48
|
+
"""Show guest network 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_guest(client: EeroClient) -> None:
|
|
55
|
+
with cli_ctx.status("Getting guest network settings..."):
|
|
56
|
+
network = await client.get_network(cli_ctx.network_id)
|
|
57
|
+
|
|
58
|
+
if cli_ctx.is_json_output():
|
|
59
|
+
data = {
|
|
60
|
+
"enabled": network.guest_network_enabled,
|
|
61
|
+
"name": network.guest_network_name,
|
|
62
|
+
"password": "********" if network.guest_network_password else None,
|
|
63
|
+
}
|
|
64
|
+
renderer.render_json(data, "eero.network.guest.show/v1")
|
|
65
|
+
else:
|
|
66
|
+
enabled = network.guest_network_enabled
|
|
67
|
+
content = (
|
|
68
|
+
f"[bold]Enabled:[/bold] {'[green]Yes[/green]' if enabled else '[dim]No[/dim]'}\n"
|
|
69
|
+
f"[bold]Name:[/bold] {network.guest_network_name or 'N/A'}\n"
|
|
70
|
+
f"[bold]Password:[/bold] {'********' if network.guest_network_password else 'N/A'}"
|
|
71
|
+
)
|
|
72
|
+
console.print(Panel(content, title="Guest Network", border_style="blue"))
|
|
73
|
+
|
|
74
|
+
await run_with_client(get_guest)
|
|
75
|
+
|
|
76
|
+
asyncio.run(run_cmd())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@guest_group.command(name="enable")
|
|
80
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
81
|
+
@click.pass_context
|
|
82
|
+
def guest_enable(ctx: click.Context, force: bool) -> None:
|
|
83
|
+
"""Enable guest network."""
|
|
84
|
+
cli_ctx = get_cli_context(ctx)
|
|
85
|
+
_set_guest_network(cli_ctx, True, None, None, force)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@guest_group.command(name="disable")
|
|
89
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
90
|
+
@click.pass_context
|
|
91
|
+
def guest_disable(ctx: click.Context, force: bool) -> None:
|
|
92
|
+
"""Disable guest network."""
|
|
93
|
+
cli_ctx = get_cli_context(ctx)
|
|
94
|
+
_set_guest_network(cli_ctx, False, None, None, force)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@guest_group.command(name="set")
|
|
98
|
+
@click.option("--name", help="Guest network name")
|
|
99
|
+
@click.option("--password", help="Guest network password")
|
|
100
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
101
|
+
@click.pass_context
|
|
102
|
+
def guest_set(
|
|
103
|
+
ctx: click.Context, name: Optional[str], password: Optional[str], force: bool
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Configure guest network settings.
|
|
106
|
+
|
|
107
|
+
\b
|
|
108
|
+
Options:
|
|
109
|
+
--name Guest network SSID
|
|
110
|
+
--password Guest network password
|
|
111
|
+
|
|
112
|
+
\b
|
|
113
|
+
Examples:
|
|
114
|
+
eero network guest set --name "Guest WiFi" --password "welcome123"
|
|
115
|
+
"""
|
|
116
|
+
cli_ctx = get_cli_context(ctx)
|
|
117
|
+
_set_guest_network(cli_ctx, True, name, password, force)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _set_guest_network(
|
|
121
|
+
cli_ctx: EeroCliContext,
|
|
122
|
+
enable: bool,
|
|
123
|
+
name: Optional[str],
|
|
124
|
+
password: Optional[str],
|
|
125
|
+
force: bool,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Set guest network settings."""
|
|
128
|
+
console = cli_ctx.console
|
|
129
|
+
action = "enable" if enable else "disable"
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
confirm_or_fail(
|
|
133
|
+
action=f"{action} guest network",
|
|
134
|
+
target="network",
|
|
135
|
+
risk=OperationRisk.MEDIUM,
|
|
136
|
+
force=force or cli_ctx.force,
|
|
137
|
+
non_interactive=cli_ctx.non_interactive,
|
|
138
|
+
dry_run=cli_ctx.dry_run,
|
|
139
|
+
)
|
|
140
|
+
except SafetyError as e:
|
|
141
|
+
cli_ctx.renderer.render_error(e.message)
|
|
142
|
+
sys.exit(e.exit_code)
|
|
143
|
+
|
|
144
|
+
async def run_cmd() -> None:
|
|
145
|
+
async def set_guest(client: EeroClient) -> None:
|
|
146
|
+
with cli_ctx.status(f"{action.capitalize()}ing guest network..."):
|
|
147
|
+
result = await client.set_guest_network(
|
|
148
|
+
enabled=enable,
|
|
149
|
+
name=name,
|
|
150
|
+
password=password,
|
|
151
|
+
network_id=cli_ctx.network_id,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if result:
|
|
155
|
+
console.print(f"[bold green]Guest network {action}d[/bold green]")
|
|
156
|
+
else:
|
|
157
|
+
console.print(f"[red]Failed to {action} guest network[/red]")
|
|
158
|
+
sys.exit(ExitCode.GENERIC_ERROR)
|
|
159
|
+
|
|
160
|
+
await run_with_client(set_guest)
|
|
161
|
+
|
|
162
|
+
asyncio.run(run_cmd())
|