router-cli 0.1.0__tar.gz → 0.2.0__tar.gz
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.
- router_cli-0.1.0/README.md → router_cli-0.2.0/PKG-INFO +37 -3
- router_cli-0.1.0/PKG-INFO → router_cli-0.2.0/README.md +12 -13
- router_cli-0.2.0/pyproject.toml +37 -0
- router_cli-0.2.0/src/router_cli/config.py +142 -0
- {router_cli-0.1.0 → router_cli-0.2.0}/src/router_cli/main.py +21 -12
- router_cli-0.1.0/pyproject.toml +0 -18
- router_cli-0.1.0/src/router_cli/config.py +0 -66
- {router_cli-0.1.0 → router_cli-0.2.0}/src/router_cli/__init__.py +0 -0
- {router_cli-0.1.0 → router_cli-0.2.0}/src/router_cli/client.py +0 -0
- {router_cli-0.1.0 → router_cli-0.2.0}/src/router_cli/py.typed +0 -0
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: router-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI tool to manage D-Link DSL-2750U router
|
|
5
|
+
Keywords: router,cli,dlink,dsl-2750u,network,admin
|
|
6
|
+
Author: d3vr
|
|
7
|
+
Author-email: d3vr <hi@f3.al>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: System :: Networking
|
|
19
|
+
Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Project-URL: Homepage, https://github.com/d3vr/router-cli
|
|
22
|
+
Project-URL: Repository, https://github.com/d3vr/router-cli
|
|
23
|
+
Project-URL: Issues, https://github.com/d3vr/router-cli/issues
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
1
26
|
# router-cli
|
|
2
27
|
|
|
3
28
|
A command-line tool to manage and monitor D-Link DSL-2750U routers.
|
|
@@ -48,10 +73,12 @@ username = "admin"
|
|
|
48
73
|
password = "your_password_here"
|
|
49
74
|
|
|
50
75
|
# Optional: Define known devices for colorized output
|
|
76
|
+
# Use MAC addresses for devices with stable MACs
|
|
77
|
+
# Use hostnames for devices with random MACs (like Android)
|
|
51
78
|
[known_devices]
|
|
52
79
|
"AA:BB:CC:DD:EE:FF" = "My Phone"
|
|
53
80
|
"11:22:33:44:55:66" = "Smart TV"
|
|
54
|
-
"
|
|
81
|
+
"android-abc123" = "John's Pixel"
|
|
55
82
|
```
|
|
56
83
|
|
|
57
84
|
The tool searches for configuration in the following locations (in order):
|
|
@@ -179,7 +206,7 @@ Sends a reboot command to the router.
|
|
|
179
206
|
|
|
180
207
|
## Known Devices
|
|
181
208
|
|
|
182
|
-
The `[known_devices]` section in your config file maps MAC addresses to friendly names. This enables:
|
|
209
|
+
The `[known_devices]` section in your config file maps MAC addresses or hostnames to friendly names. This enables:
|
|
183
210
|
|
|
184
211
|
- **Color-coded output**: Known devices appear in green, unknown in red
|
|
185
212
|
- **Friendly names**: See "My Phone" instead of a hostname or MAC address
|
|
@@ -187,11 +214,18 @@ The `[known_devices]` section in your config file maps MAC addresses to friendly
|
|
|
187
214
|
|
|
188
215
|
```toml
|
|
189
216
|
[known_devices]
|
|
217
|
+
# By MAC address (for devices with stable MACs)
|
|
190
218
|
"AA:BB:CC:DD:EE:FF" = "My Phone"
|
|
191
219
|
"11:22:33:44:55:66" = "Smart TV"
|
|
220
|
+
|
|
221
|
+
# By hostname (for devices with random MACs, like some Android phones)
|
|
222
|
+
"android-abc123def" = "John's Pixel"
|
|
223
|
+
"Galaxy-S24" = "Sarah's Phone"
|
|
192
224
|
```
|
|
193
225
|
|
|
194
|
-
MAC addresses are matched case-insensitively.
|
|
226
|
+
**MAC addresses** are identified by their format (`XX:XX:XX:XX:XX:XX`) and matched case-insensitively.
|
|
227
|
+
|
|
228
|
+
**Hostnames** are useful for Android and iOS devices that use MAC address randomization for privacy. These devices typically maintain a consistent hostname even when their MAC changes. Hostnames are also matched case-insensitively.
|
|
195
229
|
|
|
196
230
|
## Development
|
|
197
231
|
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: router-cli
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: CLI tool to manage D-Link DSL-2750U router
|
|
5
|
-
Author: d3vr
|
|
6
|
-
Author-email: d3vr <hi@f3.al>
|
|
7
|
-
Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
|
|
8
|
-
Requires-Python: >=3.10
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
|
|
11
1
|
# router-cli
|
|
12
2
|
|
|
13
3
|
A command-line tool to manage and monitor D-Link DSL-2750U routers.
|
|
@@ -58,10 +48,12 @@ username = "admin"
|
|
|
58
48
|
password = "your_password_here"
|
|
59
49
|
|
|
60
50
|
# Optional: Define known devices for colorized output
|
|
51
|
+
# Use MAC addresses for devices with stable MACs
|
|
52
|
+
# Use hostnames for devices with random MACs (like Android)
|
|
61
53
|
[known_devices]
|
|
62
54
|
"AA:BB:CC:DD:EE:FF" = "My Phone"
|
|
63
55
|
"11:22:33:44:55:66" = "Smart TV"
|
|
64
|
-
"
|
|
56
|
+
"android-abc123" = "John's Pixel"
|
|
65
57
|
```
|
|
66
58
|
|
|
67
59
|
The tool searches for configuration in the following locations (in order):
|
|
@@ -189,7 +181,7 @@ Sends a reboot command to the router.
|
|
|
189
181
|
|
|
190
182
|
## Known Devices
|
|
191
183
|
|
|
192
|
-
The `[known_devices]` section in your config file maps MAC addresses to friendly names. This enables:
|
|
184
|
+
The `[known_devices]` section in your config file maps MAC addresses or hostnames to friendly names. This enables:
|
|
193
185
|
|
|
194
186
|
- **Color-coded output**: Known devices appear in green, unknown in red
|
|
195
187
|
- **Friendly names**: See "My Phone" instead of a hostname or MAC address
|
|
@@ -197,11 +189,18 @@ The `[known_devices]` section in your config file maps MAC addresses to friendly
|
|
|
197
189
|
|
|
198
190
|
```toml
|
|
199
191
|
[known_devices]
|
|
192
|
+
# By MAC address (for devices with stable MACs)
|
|
200
193
|
"AA:BB:CC:DD:EE:FF" = "My Phone"
|
|
201
194
|
"11:22:33:44:55:66" = "Smart TV"
|
|
195
|
+
|
|
196
|
+
# By hostname (for devices with random MACs, like some Android phones)
|
|
197
|
+
"android-abc123def" = "John's Pixel"
|
|
198
|
+
"Galaxy-S24" = "Sarah's Phone"
|
|
202
199
|
```
|
|
203
200
|
|
|
204
|
-
MAC addresses are matched case-insensitively.
|
|
201
|
+
**MAC addresses** are identified by their format (`XX:XX:XX:XX:XX:XX`) and matched case-insensitively.
|
|
202
|
+
|
|
203
|
+
**Hostnames** are useful for Android and iOS devices that use MAC address randomization for privacy. These devices typically maintain a consistent hostname even when their MAC changes. Hostnames are also matched case-insensitively.
|
|
205
204
|
|
|
206
205
|
## Development
|
|
207
206
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "router-cli"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "CLI tool to manage D-Link DSL-2750U router"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "d3vr", email = "hi@f3.al" }]
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = ["tomli>=2.0.0; python_version < '3.11'"]
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
keywords = ["router", "cli", "dlink", "dsl-2750u", "network", "admin"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Environment :: Console",
|
|
14
|
+
"Intended Audience :: System Administrators",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: System :: Networking",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/d3vr/router-cli"
|
|
26
|
+
Repository = "https://github.com/d3vr/router-cli"
|
|
27
|
+
Issues = "https://github.com/d3vr/router-cli/issues"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
router = "router_cli.main:main"
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = ["pytest>=8.0.0"]
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["uv_build>=0.9.28,<0.10.0"]
|
|
37
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Configuration loading for router CLI."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 11):
|
|
7
|
+
import tomllib
|
|
8
|
+
else:
|
|
9
|
+
import tomli as tomllib
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_config_paths() -> list[Path]:
|
|
13
|
+
"""Return list of config file paths in order of priority."""
|
|
14
|
+
return [
|
|
15
|
+
Path.cwd() / "config.toml",
|
|
16
|
+
Path.home() / ".config" / "router" / "config.toml",
|
|
17
|
+
Path("/etc/router/config.toml"),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _load_config_file() -> dict | None:
|
|
22
|
+
"""Load the raw config file if it exists."""
|
|
23
|
+
for config_path in get_config_paths():
|
|
24
|
+
if config_path.exists():
|
|
25
|
+
with open(config_path, "rb") as f:
|
|
26
|
+
return tomllib.load(f)
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_config() -> dict:
|
|
31
|
+
"""Load configuration from TOML file.
|
|
32
|
+
|
|
33
|
+
Searches for config in:
|
|
34
|
+
1. ./config.toml (current directory)
|
|
35
|
+
2. ~/.config/router/config.toml
|
|
36
|
+
3. /etc/router/config.toml
|
|
37
|
+
"""
|
|
38
|
+
config = _load_config_file()
|
|
39
|
+
if config is not None:
|
|
40
|
+
return config.get("router", {})
|
|
41
|
+
|
|
42
|
+
raise FileNotFoundError(
|
|
43
|
+
"No config.toml found. Create one at ~/.config/router/config.toml with:\n"
|
|
44
|
+
"[router]\n"
|
|
45
|
+
'ip = "192.168.1.1"\n'
|
|
46
|
+
'username = "admin"\n'
|
|
47
|
+
'password = "your_password"\n'
|
|
48
|
+
"\n"
|
|
49
|
+
"[known_devices]\n"
|
|
50
|
+
'"AA:BB:CC:DD:EE:FF" = "My Phone" # by MAC address\n'
|
|
51
|
+
'"android-abc123" = "Pixel Phone" # by hostname (for random MACs)'
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_mac_address(identifier: str) -> bool:
|
|
56
|
+
"""Check if a string looks like a MAC address.
|
|
57
|
+
|
|
58
|
+
MAC addresses contain colons and are in format XX:XX:XX:XX:XX:XX
|
|
59
|
+
"""
|
|
60
|
+
import re
|
|
61
|
+
|
|
62
|
+
# Match typical MAC address formats (with colons)
|
|
63
|
+
return bool(re.match(r"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$", identifier))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class KnownDevices:
|
|
67
|
+
"""Container for known devices, supporting lookup by MAC or hostname.
|
|
68
|
+
|
|
69
|
+
This class supports devices that use random MAC addresses by allowing
|
|
70
|
+
hostname-based identification in addition to MAC-based.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
by_mac: dict[str, str] | None = None,
|
|
76
|
+
by_hostname: dict[str, str] | None = None,
|
|
77
|
+
):
|
|
78
|
+
self.by_mac: dict[str, str] = by_mac or {}
|
|
79
|
+
self.by_hostname: dict[str, str] = by_hostname or {}
|
|
80
|
+
|
|
81
|
+
def get_alias(self, mac: str, hostname: str = "") -> str | None:
|
|
82
|
+
"""Get alias for a device by MAC or hostname.
|
|
83
|
+
|
|
84
|
+
MAC lookup takes priority. Returns None if device is not known.
|
|
85
|
+
"""
|
|
86
|
+
# First try MAC lookup (normalized to uppercase)
|
|
87
|
+
alias = self.by_mac.get(mac.upper())
|
|
88
|
+
if alias:
|
|
89
|
+
return alias
|
|
90
|
+
|
|
91
|
+
# Then try hostname lookup (case-insensitive)
|
|
92
|
+
if hostname:
|
|
93
|
+
alias = self.by_hostname.get(hostname.lower())
|
|
94
|
+
if alias:
|
|
95
|
+
return alias
|
|
96
|
+
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
def is_known(self, mac: str, hostname: str = "") -> bool:
|
|
100
|
+
"""Check if a device is known by MAC or hostname."""
|
|
101
|
+
return self.get_alias(mac, hostname) is not None
|
|
102
|
+
|
|
103
|
+
def get(self, key: str, default: str | None = None) -> str | None:
|
|
104
|
+
"""Dict-like get for backward compatibility (MAC lookup only)."""
|
|
105
|
+
return self.by_mac.get(key, default)
|
|
106
|
+
|
|
107
|
+
def __contains__(self, key: str) -> bool:
|
|
108
|
+
"""Dict-like contains for backward compatibility (MAC lookup only)."""
|
|
109
|
+
return key in self.by_mac
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def load_known_devices() -> KnownDevices:
|
|
113
|
+
"""Load known devices from config file.
|
|
114
|
+
|
|
115
|
+
Returns a KnownDevices object supporting lookup by MAC address or hostname.
|
|
116
|
+
|
|
117
|
+
Config format:
|
|
118
|
+
[known_devices]
|
|
119
|
+
"AA:BB:CC:DD:EE:FF" = "My Phone" # MAC-based (for stable MACs)
|
|
120
|
+
"android-abc123" = "John's Pixel" # Hostname-based (for random MACs)
|
|
121
|
+
|
|
122
|
+
MAC addresses are identified by format (XX:XX:XX:XX:XX:XX with colons).
|
|
123
|
+
Any other identifier is treated as a hostname.
|
|
124
|
+
"""
|
|
125
|
+
config = _load_config_file()
|
|
126
|
+
if config is None:
|
|
127
|
+
return KnownDevices()
|
|
128
|
+
|
|
129
|
+
known = config.get("known_devices", {})
|
|
130
|
+
|
|
131
|
+
by_mac: dict[str, str] = {}
|
|
132
|
+
by_hostname: dict[str, str] = {}
|
|
133
|
+
|
|
134
|
+
for identifier, alias in known.items():
|
|
135
|
+
if _is_mac_address(identifier):
|
|
136
|
+
# MAC address - normalize to uppercase
|
|
137
|
+
by_mac[identifier.upper()] = alias
|
|
138
|
+
else:
|
|
139
|
+
# Hostname - normalize to lowercase for case-insensitive matching
|
|
140
|
+
by_hostname[identifier.lower()] = alias
|
|
141
|
+
|
|
142
|
+
return KnownDevices(by_mac=by_mac, by_hostname=by_hostname)
|
|
@@ -22,7 +22,7 @@ from .client import (
|
|
|
22
22
|
Statistics,
|
|
23
23
|
WirelessClient,
|
|
24
24
|
)
|
|
25
|
-
from .config import load_config, load_known_devices
|
|
25
|
+
from .config import KnownDevices, load_config, load_known_devices
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
# ANSI color codes
|
|
@@ -130,14 +130,20 @@ def spinner(message: str = "Loading..."):
|
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
def get_device_display(
|
|
133
|
-
mac: str, hostname: str, known_devices:
|
|
133
|
+
mac: str, hostname: str, known_devices: KnownDevices | None
|
|
134
134
|
) -> tuple[str, bool]:
|
|
135
135
|
"""Get display name for a device and whether it's known.
|
|
136
136
|
|
|
137
137
|
Returns (display_name, is_known) tuple.
|
|
138
138
|
If known, display_name is 'Alias (hostname)'.
|
|
139
|
+
|
|
140
|
+
Supports lookup by both MAC address and hostname (for devices with
|
|
141
|
+
random MAC addresses like some Android phones).
|
|
139
142
|
"""
|
|
140
|
-
|
|
143
|
+
if known_devices is None:
|
|
144
|
+
return hostname or mac, False
|
|
145
|
+
|
|
146
|
+
alias = known_devices.get_alias(mac, hostname)
|
|
141
147
|
if alias:
|
|
142
148
|
if hostname and hostname != alias:
|
|
143
149
|
return f"{alias} ({hostname})", True
|
|
@@ -220,21 +226,22 @@ def format_status(status: RouterStatus) -> str:
|
|
|
220
226
|
|
|
221
227
|
|
|
222
228
|
def format_clients(
|
|
223
|
-
clients: list[WirelessClient], known_devices:
|
|
229
|
+
clients: list[WirelessClient], known_devices: KnownDevices | None = None
|
|
224
230
|
) -> str:
|
|
225
231
|
"""Format wireless clients for display."""
|
|
226
232
|
if not clients:
|
|
227
233
|
return "No wireless clients connected."
|
|
228
234
|
|
|
229
|
-
known_devices = known_devices or
|
|
235
|
+
known_devices = known_devices or KnownDevices()
|
|
230
236
|
|
|
231
237
|
# Build display data and calculate column widths
|
|
232
238
|
rows = []
|
|
233
239
|
for c in clients:
|
|
234
240
|
assoc = "Yes" if c.associated else "No"
|
|
235
241
|
auth = "Yes" if c.authorized else "No"
|
|
236
|
-
|
|
237
|
-
alias = known_devices.
|
|
242
|
+
# WirelessClient doesn't have hostname, so we can only check by MAC
|
|
243
|
+
alias = known_devices.get_alias(c.mac, "")
|
|
244
|
+
is_known = alias is not None
|
|
238
245
|
mac_display = f"{c.mac} ({alias})" if alias else c.mac
|
|
239
246
|
rows.append((mac_display, assoc, auth, c.ssid, c.interface, is_known))
|
|
240
247
|
|
|
@@ -268,13 +275,13 @@ def format_clients(
|
|
|
268
275
|
|
|
269
276
|
|
|
270
277
|
def format_dhcp(
|
|
271
|
-
leases: list[DHCPLease], known_devices:
|
|
278
|
+
leases: list[DHCPLease], known_devices: KnownDevices | None = None
|
|
272
279
|
) -> str:
|
|
273
280
|
"""Format DHCP leases for display."""
|
|
274
281
|
if not leases:
|
|
275
282
|
return "No DHCP leases."
|
|
276
283
|
|
|
277
|
-
known_devices = known_devices or
|
|
284
|
+
known_devices = known_devices or KnownDevices()
|
|
278
285
|
|
|
279
286
|
# Build display data and calculate column widths
|
|
280
287
|
rows = []
|
|
@@ -421,10 +428,10 @@ def format_overview(
|
|
|
421
428
|
clients: list[WirelessClient],
|
|
422
429
|
leases: list[DHCPLease],
|
|
423
430
|
stats: Statistics,
|
|
424
|
-
known_devices:
|
|
431
|
+
known_devices: KnownDevices | None = None,
|
|
425
432
|
) -> str:
|
|
426
433
|
"""Format overview dashboard with highlights from multiple sources."""
|
|
427
|
-
known_devices = known_devices or
|
|
434
|
+
known_devices = known_devices or KnownDevices()
|
|
428
435
|
lines = [
|
|
429
436
|
"=" * 60,
|
|
430
437
|
f"{'ROUTER OVERVIEW':^60}",
|
|
@@ -512,7 +519,9 @@ def format_overview(
|
|
|
512
519
|
f" Low SNR margin: {stats.adsl.downstream_snr_margin:.1f} dB (may cause disconnects)"
|
|
513
520
|
)
|
|
514
521
|
# Warn about unknown devices
|
|
515
|
-
unknown_count = sum(
|
|
522
|
+
unknown_count = sum(
|
|
523
|
+
1 for lease in leases if not known_devices.is_known(lease.mac, lease.hostname)
|
|
524
|
+
)
|
|
516
525
|
if unknown_count > 0:
|
|
517
526
|
warnings.append(
|
|
518
527
|
colorize(f" Unknown devices on network: {unknown_count}", "red")
|
router_cli-0.1.0/pyproject.toml
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "router-cli"
|
|
3
|
-
version = "0.1.0"
|
|
4
|
-
description = "CLI tool to manage D-Link DSL-2750U router"
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
authors = [{ name = "d3vr", email = "hi@f3.al" }]
|
|
7
|
-
requires-python = ">=3.10"
|
|
8
|
-
dependencies = ["tomli>=2.0.0; python_version < '3.11'"]
|
|
9
|
-
|
|
10
|
-
[project.scripts]
|
|
11
|
-
router = "router_cli.main:main"
|
|
12
|
-
|
|
13
|
-
[dependency-groups]
|
|
14
|
-
dev = ["pytest>=8.0.0"]
|
|
15
|
-
|
|
16
|
-
[build-system]
|
|
17
|
-
requires = ["uv_build>=0.9.28,<0.10.0"]
|
|
18
|
-
build-backend = "uv_build"
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"""Configuration loading for router CLI."""
|
|
2
|
-
|
|
3
|
-
import sys
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
if sys.version_info >= (3, 11):
|
|
7
|
-
import tomllib
|
|
8
|
-
else:
|
|
9
|
-
import tomli as tomllib
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_config_paths() -> list[Path]:
|
|
13
|
-
"""Return list of config file paths in order of priority."""
|
|
14
|
-
return [
|
|
15
|
-
Path.cwd() / "config.toml",
|
|
16
|
-
Path.home() / ".config" / "router" / "config.toml",
|
|
17
|
-
Path("/etc/router/config.toml"),
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _load_config_file() -> dict | None:
|
|
22
|
-
"""Load the raw config file if it exists."""
|
|
23
|
-
for config_path in get_config_paths():
|
|
24
|
-
if config_path.exists():
|
|
25
|
-
with open(config_path, "rb") as f:
|
|
26
|
-
return tomllib.load(f)
|
|
27
|
-
return None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def load_config() -> dict:
|
|
31
|
-
"""Load configuration from TOML file.
|
|
32
|
-
|
|
33
|
-
Searches for config in:
|
|
34
|
-
1. ./config.toml (current directory)
|
|
35
|
-
2. ~/.config/router/config.toml
|
|
36
|
-
3. /etc/router/config.toml
|
|
37
|
-
"""
|
|
38
|
-
config = _load_config_file()
|
|
39
|
-
if config is not None:
|
|
40
|
-
return config.get("router", {})
|
|
41
|
-
|
|
42
|
-
raise FileNotFoundError(
|
|
43
|
-
"No config.toml found. Create one at ~/.config/router/config.toml with:\n"
|
|
44
|
-
"[router]\n"
|
|
45
|
-
'ip = "192.168.1.1"\n'
|
|
46
|
-
'username = "admin"\n'
|
|
47
|
-
'password = "your_password"\n'
|
|
48
|
-
"\n"
|
|
49
|
-
"[known_devices]\n"
|
|
50
|
-
'"AA:BB:CC:DD:EE:FF" = "My Phone"\n'
|
|
51
|
-
'"11:22:33:44:55:66" = "Smart TV"'
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def load_known_devices() -> dict[str, str]:
|
|
56
|
-
"""Load known devices from config file.
|
|
57
|
-
|
|
58
|
-
Returns a dict mapping MAC addresses (uppercase) to aliases.
|
|
59
|
-
"""
|
|
60
|
-
config = _load_config_file()
|
|
61
|
-
if config is None:
|
|
62
|
-
return {}
|
|
63
|
-
|
|
64
|
-
known = config.get("known_devices", {})
|
|
65
|
-
# Normalize MAC addresses to uppercase for case-insensitive matching
|
|
66
|
-
return {mac.upper(): alias for mac, alias in known.items()}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|