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
eeroctl/safety.py ADDED
@@ -0,0 +1,259 @@
1
+ """Safety middleware for the Eero CLI.
2
+
3
+ Handles confirmation prompts for destructive/disruptive operations.
4
+ Provides consistent safety behavior across all mutating commands.
5
+ """
6
+
7
+ import sys
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from typing import Callable, Optional
11
+
12
+ import click
13
+ from rich.console import Console
14
+ from rich.prompt import Confirm, Prompt
15
+
16
+ from .exit_codes import ExitCode
17
+
18
+
19
+ class OperationRisk(str, Enum):
20
+ """Risk level of an operation."""
21
+
22
+ LOW = "low"
23
+ """Low risk - no confirmation needed."""
24
+
25
+ MEDIUM = "medium"
26
+ """Medium risk - simple Y/N confirmation."""
27
+
28
+ HIGH = "high"
29
+ """High risk - requires typed confirmation phrase."""
30
+
31
+
32
+ @dataclass
33
+ class SafetyContext:
34
+ """Context for safety checks."""
35
+
36
+ force: bool = False
37
+ """Whether --force was specified."""
38
+
39
+ non_interactive: bool = False
40
+ """Whether --non-interactive was specified."""
41
+
42
+ dry_run: bool = False
43
+ """Whether --dry-run was specified."""
44
+
45
+
46
+ class SafetyError(Exception):
47
+ """Raised when a safety check fails."""
48
+
49
+ def __init__(self, message: str, exit_code: int = ExitCode.SAFETY_RAIL):
50
+ self.message = message
51
+ self.exit_code = exit_code
52
+ super().__init__(message)
53
+
54
+
55
+ def require_confirmation(
56
+ action: str,
57
+ target: str,
58
+ risk: OperationRisk = OperationRisk.MEDIUM,
59
+ confirmation_phrase: Optional[str] = None,
60
+ ctx: Optional[SafetyContext] = None,
61
+ console: Optional[Console] = None,
62
+ ) -> bool:
63
+ """Check if operation should proceed based on safety settings.
64
+
65
+ Args:
66
+ action: Description of the action (e.g., "reboot")
67
+ target: Target of the action (e.g., "Living Room eero")
68
+ risk: Risk level of the operation
69
+ confirmation_phrase: Required phrase for HIGH risk operations
70
+ ctx: Safety context with force/non_interactive flags
71
+ console: Rich console for prompts
72
+
73
+ Returns:
74
+ True if operation should proceed
75
+
76
+ Raises:
77
+ SafetyError: If safety check fails and operation should not proceed
78
+ """
79
+ if ctx is None:
80
+ ctx = SafetyContext()
81
+
82
+ if console is None:
83
+ console = Console(stderr=True)
84
+
85
+ # Dry run always prints and returns False
86
+ if ctx.dry_run:
87
+ console.print(f"[yellow]DRY RUN:[/yellow] Would {action} {target}")
88
+ return False
89
+
90
+ # Force bypasses all confirmations
91
+ if ctx.force:
92
+ return True
93
+
94
+ # Low risk operations proceed without confirmation
95
+ if risk == OperationRisk.LOW:
96
+ return True
97
+
98
+ # Non-interactive mode without force fails
99
+ if ctx.non_interactive:
100
+ raise SafetyError(
101
+ f"Operation '{action}' on '{target}' requires confirmation. "
102
+ "Use --force to proceed in non-interactive mode.",
103
+ exit_code=ExitCode.SAFETY_RAIL,
104
+ )
105
+
106
+ # Interactive confirmation
107
+ if risk == OperationRisk.HIGH:
108
+ # High risk requires typed phrase
109
+ if confirmation_phrase is None:
110
+ confirmation_phrase = action.upper().replace(" ", "")
111
+
112
+ console.print(
113
+ f"\n[bold yellow]⚠ Warning:[/bold yellow] You are about to {action} {target}."
114
+ )
115
+ console.print(
116
+ "[dim]This is a high-impact operation that may cause service disruption.[/dim]"
117
+ )
118
+ console.print(f"\nTo confirm, type [bold]{confirmation_phrase}[/bold] and press Enter:")
119
+
120
+ user_input = Prompt.ask("Confirmation", console=console)
121
+
122
+ if user_input != confirmation_phrase:
123
+ raise SafetyError(
124
+ f"Confirmation phrase mismatch. Expected '{confirmation_phrase}'.",
125
+ exit_code=ExitCode.SAFETY_RAIL,
126
+ )
127
+ return True
128
+
129
+ else:
130
+ # Medium risk - simple Y/N
131
+ console.print(f"\n[bold]Proceed with {action} on {target}?[/bold]")
132
+ confirmed = Confirm.ask("Continue", default=False, console=console)
133
+
134
+ if not confirmed:
135
+ raise SafetyError(
136
+ "Operation cancelled by user.",
137
+ exit_code=ExitCode.SAFETY_RAIL,
138
+ )
139
+ return True
140
+
141
+
142
+ def confirm_or_fail(
143
+ action: str,
144
+ target: str,
145
+ risk: OperationRisk = OperationRisk.MEDIUM,
146
+ confirmation_phrase: Optional[str] = None,
147
+ force: bool = False,
148
+ non_interactive: bool = False,
149
+ dry_run: bool = False,
150
+ console: Optional[Console] = None,
151
+ ) -> bool:
152
+ """Convenience function combining context creation and confirmation.
153
+
154
+ Args:
155
+ action: Description of the action
156
+ target: Target of the action
157
+ risk: Risk level
158
+ confirmation_phrase: Required phrase for HIGH risk
159
+ force: --force flag value
160
+ non_interactive: --non-interactive flag value
161
+ dry_run: --dry-run flag value
162
+ console: Rich console for prompts
163
+
164
+ Returns:
165
+ True if operation should proceed
166
+
167
+ Raises:
168
+ SafetyError: If safety check fails
169
+ """
170
+ ctx = SafetyContext(force=force, non_interactive=non_interactive, dry_run=dry_run)
171
+ return require_confirmation(
172
+ action=action,
173
+ target=target,
174
+ risk=risk,
175
+ confirmation_phrase=confirmation_phrase,
176
+ ctx=ctx,
177
+ console=console,
178
+ )
179
+
180
+
181
+ # Decorator for commands that require confirmation
182
+ def requires_confirmation(
183
+ action: str,
184
+ target_param: str = "target",
185
+ risk: OperationRisk = OperationRisk.MEDIUM,
186
+ confirmation_phrase: Optional[str] = None,
187
+ ):
188
+ """Decorator to add confirmation requirement to a command.
189
+
190
+ Args:
191
+ action: Description of the action
192
+ target_param: Name of the parameter containing the target
193
+ risk: Risk level of the operation
194
+ confirmation_phrase: Required phrase for HIGH risk
195
+
196
+ Example:
197
+ @requires_confirmation("reboot", target_param="eero_id", risk=OperationRisk.MEDIUM)
198
+ def reboot(ctx, eero_id: str, force: bool, non_interactive: bool, dry_run: bool):
199
+ ...
200
+ """
201
+
202
+ def decorator(func: Callable) -> Callable:
203
+ def wrapper(*args, **kwargs):
204
+ # Extract safety-related kwargs
205
+ force = kwargs.get("force", False)
206
+ non_interactive = kwargs.get("non_interactive", False)
207
+ dry_run = kwargs.get("dry_run", False)
208
+ target = kwargs.get(target_param, "unknown")
209
+
210
+ try:
211
+ if confirm_or_fail(
212
+ action=action,
213
+ target=str(target),
214
+ risk=risk,
215
+ confirmation_phrase=confirmation_phrase,
216
+ force=force,
217
+ non_interactive=non_interactive,
218
+ dry_run=dry_run,
219
+ ):
220
+ return func(*args, **kwargs)
221
+ except SafetyError as e:
222
+ click.echo(f"Error: {e.message}", err=True)
223
+ sys.exit(e.exit_code)
224
+
225
+ # Preserve function metadata
226
+ wrapper.__name__ = func.__name__
227
+ wrapper.__doc__ = func.__doc__
228
+ return wrapper
229
+
230
+ return decorator
231
+
232
+
233
+ # List of operations and their risk levels for reference
234
+ OPERATION_RISKS = {
235
+ # HIGH risk - requires typed confirmation
236
+ "reboot_network": OperationRisk.HIGH,
237
+ "change_wifi_password": OperationRisk.HIGH,
238
+ "change_wifi_ssid": OperationRisk.HIGH,
239
+ "factory_reset": OperationRisk.HIGH,
240
+ # MEDIUM risk - Y/N confirmation
241
+ "reboot_eero": OperationRisk.MEDIUM,
242
+ "guest_enable": OperationRisk.MEDIUM,
243
+ "guest_disable": OperationRisk.MEDIUM,
244
+ "dns_change": OperationRisk.MEDIUM,
245
+ "security_change": OperationRisk.MEDIUM,
246
+ "sqm_change": OperationRisk.MEDIUM,
247
+ "block_device": OperationRisk.MEDIUM,
248
+ "unblock_device": OperationRisk.MEDIUM,
249
+ "pause_profile": OperationRisk.MEDIUM,
250
+ "unpause_profile": OperationRisk.MEDIUM,
251
+ "schedule_change": OperationRisk.MEDIUM,
252
+ "delete_forward": OperationRisk.MEDIUM,
253
+ "delete_reservation": OperationRisk.MEDIUM,
254
+ "export_support_bundle": OperationRisk.MEDIUM,
255
+ # LOW risk - no confirmation
256
+ "rename_device": OperationRisk.LOW,
257
+ "rename_network": OperationRisk.LOW, # Actually shown as MEDIUM in spec
258
+ "view_any": OperationRisk.LOW,
259
+ }
eeroctl/utils.py ADDED
@@ -0,0 +1,181 @@
1
+ """Utility functions for the Eero CLI."""
2
+
3
+ import asyncio
4
+ import functools
5
+ import json
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Awaitable, Callable, Optional, TypeVar
10
+
11
+ import click
12
+ from eero import EeroClient
13
+ from eero.exceptions import EeroAuthenticationException
14
+ from rich.console import Console
15
+
16
+ # Create console for rich output
17
+ console = Console()
18
+
19
+ T = TypeVar("T")
20
+
21
+
22
+ def with_client(func: Callable[..., Awaitable[T]]) -> Callable[..., T]:
23
+ """Decorator that provides an EeroClient to async Click commands.
24
+
25
+ This decorator eliminates the repetitive boilerplate pattern of:
26
+ async def run_cmd():
27
+ async def inner(client):
28
+ ...
29
+ await run_with_client(inner)
30
+ asyncio.run(run_cmd())
31
+
32
+ Instead, you can write:
33
+ @command.command()
34
+ @click.pass_context
35
+ @with_client
36
+ async def my_command(ctx, client, ...):
37
+ # Just do the work
38
+
39
+ The decorator:
40
+ - Creates an EeroClient context
41
+ - Handles authentication errors
42
+ - Runs the async function synchronously via asyncio.run()
43
+
44
+ Args:
45
+ func: Async function that receives (ctx, client, *args, **kwargs)
46
+
47
+ Returns:
48
+ Synchronous wrapper function
49
+ """
50
+
51
+ @functools.wraps(func)
52
+ def wrapper(*args, **kwargs):
53
+ async def run():
54
+ cookie_file = get_cookie_file()
55
+ try:
56
+ async with EeroClient(cookie_file=str(cookie_file)) as client:
57
+ return await func(*args, client=client, **kwargs)
58
+ except EeroAuthenticationException:
59
+ console.print("[bold red]Not authenticated[/bold red]")
60
+ console.print("Please login first: [bold]eero auth login[/bold]")
61
+ sys.exit(3) # ExitCode.AUTH_REQUIRED
62
+
63
+ return asyncio.run(run())
64
+
65
+ return wrapper
66
+
67
+
68
+ def output_option(func):
69
+ """Decorator to add --output option to commands."""
70
+ return click.option(
71
+ "--output",
72
+ type=click.Choice(["brief", "extensive", "json"]),
73
+ default="brief",
74
+ help="Output format (brief, extensive, or json)",
75
+ )(func)
76
+
77
+
78
+ def get_config_dir() -> Path:
79
+ """Get the configuration directory.
80
+
81
+ Returns:
82
+ Path to the configuration directory
83
+ """
84
+ if os.name == "nt": # Windows
85
+ config_dir = Path(os.environ["APPDATA"]) / "eero-api"
86
+ else:
87
+ config_dir = Path.home() / ".config" / "eero-api"
88
+
89
+ config_dir.mkdir(parents=True, exist_ok=True)
90
+ return config_dir
91
+
92
+
93
+ def get_cookie_file() -> Path:
94
+ """Get the cookie file path.
95
+
96
+ Returns:
97
+ Path to the cookie file
98
+ """
99
+ return get_config_dir() / "cookies.json"
100
+
101
+
102
+ def get_config_file() -> Path:
103
+ """Get the config file path.
104
+
105
+ Returns:
106
+ Path to the config file
107
+ """
108
+ return get_config_dir() / "config.json"
109
+
110
+
111
+ def set_preferred_network(network_id: str) -> None:
112
+ """Set the preferred network ID in the configuration.
113
+
114
+ Args:
115
+ network_id: The network ID to set as preferred
116
+ """
117
+ config_file = get_config_file()
118
+ config = {}
119
+
120
+ if config_file.exists():
121
+ try:
122
+ with open(config_file, "r") as f:
123
+ config = json.load(f)
124
+ except (json.JSONDecodeError, IOError):
125
+ pass
126
+
127
+ config["preferred_network_id"] = network_id
128
+
129
+ try:
130
+ with open(config_file, "w") as f:
131
+ json.dump(config, f, indent=2)
132
+ except IOError as e:
133
+ console.print(f"[bold red]Error saving config: {e}[/bold red]")
134
+
135
+
136
+ def get_preferred_network() -> Optional[str]:
137
+ """Get the preferred network ID from the configuration.
138
+
139
+ Returns:
140
+ The preferred network ID or None if not set
141
+ """
142
+ config_file = get_config_file()
143
+
144
+ if not config_file.exists():
145
+ return None
146
+
147
+ try:
148
+ with open(config_file, "r") as f:
149
+ config = json.load(f)
150
+ return config.get("preferred_network_id")
151
+ except (json.JSONDecodeError, IOError):
152
+ return None
153
+
154
+
155
+ async def run_with_client(func):
156
+ """Run a function with an EeroClient instance.
157
+
158
+ Args:
159
+ func: Async function that takes an EeroClient as argument
160
+ """
161
+ cookie_file = get_cookie_file()
162
+
163
+ try:
164
+ async with EeroClient(cookie_file=str(cookie_file)) as client:
165
+ await func(client)
166
+ except EeroAuthenticationException:
167
+ console.print("[bold red]Not authenticated[/bold red]")
168
+ console.print("Please login first: [bold]eero auth login[/bold]")
169
+ raise SystemExit(1)
170
+
171
+
172
+ def confirm_action(message: str) -> bool:
173
+ """Ask user to confirm an action.
174
+
175
+ Args:
176
+ message: The message to display
177
+
178
+ Returns:
179
+ True if user confirms, False otherwise
180
+ """
181
+ return click.confirm(message)
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.4
2
+ Name: eeroctl
3
+ Version: 1.7.1
4
+ Summary: Command-line interface for Eero network management
5
+ Author: Eero CLI Contributors
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/fulviofreitas/eeroctl
8
+ Project-URL: Issues, https://github.com/fulviofreitas/eeroctl/issues
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: End Users/Desktop
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Home Automation
18
+ Classifier: Topic :: System :: Networking
19
+ Classifier: Environment :: Console
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: eero-api==1.4.3
25
+ Requires-Dist: rich>=13.0.0
26
+ Requires-Dist: click>=8.0.0
27
+ Requires-Dist: pyyaml>=6.0.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Requires-Dist: black>=23.0.0; extra == "dev"
33
+ Requires-Dist: isort>=5.12.0; extra == "dev"
34
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
35
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # 🌐 eeroctl
39
+
40
+ [![CI](https://github.com/fulviofreitas/eeroctl/actions/workflows/ci.yml/badge.svg)](https://github.com/fulviofreitas/eeroctl/actions/workflows/ci.yml)
41
+ [![PyPI](https://img.shields.io/pypi/v/eeroctl.svg)](https://pypi.org/project/eeroctl/)
42
+ [![Homebrew](https://img.shields.io/badge/homebrew-eeroctl-orange)](https://github.com/fulviofreitas/homebrew-eeroctl)
43
+
44
+ > Manage your Eero mesh Wi-Fi from the terminal ✨
45
+
46
+ ## ⚡ Features
47
+
48
+ - 🧭 **Intuitive commands** — noun-first structure (`eero network list`)
49
+ - 📊 **Multiple formats** — table, JSON, YAML, text
50
+ - 🛡️ **Safety rails** — confirmation for destructive actions
51
+ - 🔧 **Script-friendly** — non-interactive mode + machine-readable output
52
+ - 🐚 **Shell completion** — bash, zsh, fish
53
+
54
+ ## 📦 Install
55
+
56
+ ### Homebrew
57
+
58
+ ```bash
59
+ brew install fulviofreitas/eeroctl/eeroctl
60
+ ```
61
+
62
+ ### PyPI
63
+
64
+ ```bash
65
+ pip install eeroctl
66
+ ```
67
+
68
+ <details>
69
+ <summary>From source</summary>
70
+
71
+ ```bash
72
+ git clone https://github.com/fulviofreitas/eeroctl.git
73
+ cd eeroctl
74
+ uv sync && source .venv/bin/activate
75
+ ```
76
+
77
+ Or with pip:
78
+
79
+ ```bash
80
+ python3 -m venv .venv && source .venv/bin/activate
81
+ pip install -e .
82
+ ```
83
+
84
+ </details>
85
+
86
+ ## 🚀 Quick Start
87
+
88
+ ```bash
89
+ eero auth login # Authenticate
90
+ eero network list # List networks
91
+ eero device list # Connected devices
92
+ eero eero list # Mesh nodes
93
+ eero troubleshoot speedtest --force
94
+ ```
95
+
96
+ > **Tip:** Both `eero` and `eeroctl` commands are available and work identically.
97
+
98
+ ## 📖 Documentation
99
+
100
+ Full documentation lives in the **[Wiki](https://github.com/fulviofreitas/eeroctl/wiki)**:
101
+
102
+ | 📚 Guide | Description |
103
+ |----------|-------------|
104
+ | [CLI Reference](https://github.com/fulviofreitas/eeroctl/wiki/CLI-Reference) | Commands, flags & exit codes |
105
+ | [Usage Examples](https://github.com/fulviofreitas/eeroctl/wiki/Usage-Examples) | Practical examples |
106
+ | [Configuration](https://github.com/fulviofreitas/eeroctl/wiki/Configuration) | Auth storage & env vars |
107
+ | [Troubleshooting](https://github.com/fulviofreitas/eeroctl/wiki/Troubleshooting) | Common issues |
108
+
109
+ ## 🔗 Dependencies
110
+
111
+ Built on [eero-api](https://github.com/fulviofreitas/eero-api) for API communication.
112
+
113
+ ## 📄 License
114
+
115
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,45 @@
1
+ eeroctl/__init__.py,sha256=Bck2y5Vmenb-009bMukNb_kT7efmk8c6j0adGiZQRNg,436
2
+ eeroctl/context.py,sha256=fIsjyXbRwe6C-9grZG_lnd5fcj-bqyLb4BhOq6BN5Sk,8077
3
+ eeroctl/errors.py,sha256=UbE4jkMIu3oTtn7-4r6wMdzFI_YaBmboXzXSNE3Yaro,5238
4
+ eeroctl/exit_codes.py,sha256=JK-8OA6jZDWk1cPDVQ94Bz80P5MRnkczFwoLOSailmQ,2008
5
+ eeroctl/main.py,sha256=ER1Ji_wWvfLJV6erduF7sG7pm5NqFuRD7YbwxVSIX3M,3894
6
+ eeroctl/options.py,sha256=YbUeo3OwkCJw2A-HRpGKdpQooP6DsyaI0_rfB-I2sXw,13029
7
+ eeroctl/output.py,sha256=07cxnI_eks3QXDB4Cy5RwiKwrpcunCN8Ibu2cvLqI00,25620
8
+ eeroctl/safety.py,sha256=Qkct4MxE_gUekd36ibqPGvJiyflwH-vl28wsfgj-48g,8107
9
+ eeroctl/utils.py,sha256=n6boUe0VQ1C0z-mLaa1ZRXDvGkKL9wehMOsw1KyO12A,4747
10
+ eeroctl/commands/__init__.py,sha256=yTmjcnPJpGmczatEJrhQvfworH0OBpH2fmg1HVAcYfQ,893
11
+ eeroctl/commands/activity.py,sha256=GvrP2DQBOrJLE2DJP1FOseQJVDwTXyk-G5Nm80LjKzU,7926
12
+ eeroctl/commands/auth.py,sha256=r9hgixgIeyw0464f5WybvFX6zgPbHJHAoD5nAHsEUBc,18139
13
+ eeroctl/commands/completion.py,sha256=EjpmmN7QY8z1eitoc212mw8jEIA3or7ZlWkbHjqEYiI,3522
14
+ eeroctl/commands/device.py,sha256=0uAfU7mV15yfnMOd4emohx1P4VP7T_hxlve772yoqFQ,16760
15
+ eeroctl/commands/profile.py,sha256=mUOvEwT02TgL9RLM4-fRtY3cSZpk1F6mQARzqWl4LRE,23180
16
+ eeroctl/commands/troubleshoot.py,sha256=OZFMt_ZQrgtwT5YuYRht-EWV1ROEMtiE889hw-SAXWY,11043
17
+ eeroctl/commands/eero/__init__.py,sha256=l7K7-Bvw_-GMAepCohmjpy038GC451yM-rphjXbgEyQ,354
18
+ eeroctl/commands/eero/base.py,sha256=xT6Jt8zpRNhI4_Bf7AyKgg0a23EGbOA0Zr2Ktfp4miQ,7883
19
+ eeroctl/commands/eero/led.py,sha256=DnkGp6eXMUm8nKMBk5RgtjjdYXtH-uh2di4lN2HNbPI,5209
20
+ eeroctl/commands/eero/nightlight.py,sha256=BUa1jwQERri4m4lqVtDMHKInI6Rv49yWk7Ka1QMyuOo,9095
21
+ eeroctl/commands/eero/updates.py,sha256=9NWb5jYfmA8NTRjvTtdURb-TLucMAlsp9CxHja6nkwk,2460
22
+ eeroctl/commands/network/__init__.py,sha256=0mlN6w5Hl5npgZWbBPCQLwUU6sQNVoMGHmPRwn9tfL4,574
23
+ eeroctl/commands/network/advanced.py,sha256=LDpbeVfBkVQsmghpxEaMwnvPpKFT2wo0aQ9enUmHNXU,5647
24
+ eeroctl/commands/network/backup.py,sha256=McvLCX2rlEav3V1V7vl_8sQm8EPoS6pGDErijLiARWE,5756
25
+ eeroctl/commands/network/base.py,sha256=MW8EW9GfcaUtCp5eGlhPm9ov2ZawePopFY1e13Ee2D4,11424
26
+ eeroctl/commands/network/dhcp.py,sha256=A-yYPkdvjCYBMi5kijETgpijwt5PlXT3cVMKLTtGaEY,3755
27
+ eeroctl/commands/network/dns.py,sha256=E2J0CVLI3Dhephlnr9Y_Bp6uEp9R0H_r-PtdEfZmGRw,6247
28
+ eeroctl/commands/network/forwards.py,sha256=SnzxuL5nlraJmofWMhQIGI7VzBSj3aRLrZ_eGrkPuMo,3680
29
+ eeroctl/commands/network/guest.py,sha256=eYhqWEBGXiyG8HnRGTSCZujwzMU61vRVVcD6c5XxC8U,5168
30
+ eeroctl/commands/network/security.py,sha256=BZQEfyUpJv6GhrW6j6ZWCt94tmH5Z4Lpu-Pvib9tCqA,5506
31
+ eeroctl/commands/network/speedtest.py,sha256=9lxpE6LT0hKDGdAfCs9LB05GNEKEyF_HPhfwl1FqiDA,3309
32
+ eeroctl/commands/network/sqm.py,sha256=l0DyZMsFKi-Jd_qqx1dxkE23pwJR-KJyoXZePNzuZhk,6223
33
+ eeroctl/formatting/__init__.py,sha256=7SxZZyCzIjrfJr-XRasnTmqSKuyHTbmCbRE3XVJVHUk,1830
34
+ eeroctl/formatting/base.py,sha256=__VMPz__etiHYKCdbegEyQyQu2FomsAF6DVEWIZI-hk,5011
35
+ eeroctl/formatting/device.py,sha256=mD8hePr8je69xt4S_AphrKLLEZMvAQGr4QX8g0yqnZY,14322
36
+ eeroctl/formatting/eero.py,sha256=4FShOgQhHNSIZ_OpjjA8WCnmXeIAvUOuhuaHCcz-iOk,19294
37
+ eeroctl/formatting/misc.py,sha256=gG7t9PXkMqtDLJvw4QvQull7iS3XeI5MYPKYJHr0FjY,2688
38
+ eeroctl/formatting/network.py,sha256=iyqIkLgmWTL_boj4yg0H10WrqZSGtKmUAU0yfRILNBE,22663
39
+ eeroctl/formatting/profile.py,sha256=FogqLzNkZP4SHqSyVrG7Q9B0UPeKVl2pvGJWpmgsM7U,15012
40
+ eeroctl-1.7.1.dist-info/licenses/LICENSE,sha256=VL7wy_qH5snbBmKamgHHb6PYrAmk4tcj0R_MVBA3FlU,1078
41
+ eeroctl-1.7.1.dist-info/METADATA,sha256=UdlU9FoN03uDPH-DTN9cZqOPNkjckcJhVYgl8Y9rz5Q,3644
42
+ eeroctl-1.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
43
+ eeroctl-1.7.1.dist-info/entry_points.txt,sha256=1GTBJ02E21uwOP1frYBhMjx8zmIk1Sf8OFuI3YFK4Lk,69
44
+ eeroctl-1.7.1.dist-info/top_level.txt,sha256=9H9_4a997qOKPPivIK7pHbo7Gk9vZ3WU4VrKPZCBdHs,8
45
+ eeroctl-1.7.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ eero = eeroctl.main:cli
3
+ eeroctl = eeroctl.main:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Eero CLI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ eeroctl