namedaemon 0.1.0__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.
- namedaemon/__init__.py +12 -0
- namedaemon/__main__.py +3 -0
- namedaemon/cli.py +79 -0
- namedaemon/client.py +194 -0
- namedaemon/py.typed +0 -0
- namedaemon-0.1.0.dist-info/METADATA +106 -0
- namedaemon-0.1.0.dist-info/RECORD +10 -0
- namedaemon-0.1.0.dist-info/WHEEL +4 -0
- namedaemon-0.1.0.dist-info/entry_points.txt +2 -0
- namedaemon-0.1.0.dist-info/licenses/LICENSE +21 -0
namedaemon/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Client and CLI for NameDaemon (https://namedaemon.com)."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from .client import ( # noqa: E402
|
|
6
|
+
DEFAULT_BASE_URL,
|
|
7
|
+
NameDaemon,
|
|
8
|
+
NameDaemonError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = ["NameDaemon", "NameDaemonError", "RateLimitError", "DEFAULT_BASE_URL", "__version__"]
|
namedaemon/__main__.py
ADDED
namedaemon/cli.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Command line: ``namedaemon rate example`` or ``python -m namedaemon rate example``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, List, Optional
|
|
9
|
+
|
|
10
|
+
from . import __version__
|
|
11
|
+
from .client import NameDaemon, NameDaemonError, RateLimitError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _pretty(cmd: str, r: Any) -> str:
|
|
15
|
+
if cmd == "rate":
|
|
16
|
+
line = f"{r['name']}: {r['score'] if r.get('score') is not None else 'no score'} {r.get('verdict') or ''}".strip()
|
|
17
|
+
tips = r.get("tips") or []
|
|
18
|
+
return line + ("\n- " + "\n- ".join(tips) if tips else "")
|
|
19
|
+
if cmd == "ideas":
|
|
20
|
+
return "\n".join(f"{i['domain']}.com {i['score']}" for i in r["ideas"])
|
|
21
|
+
if cmd in ("check", "verify", "tld"):
|
|
22
|
+
return "\n".join(
|
|
23
|
+
f"{x['domain']} {x['status']}" + (f" (drops {x['est_drop']})" if x.get("est_drop") else "")
|
|
24
|
+
for x in r["results"]
|
|
25
|
+
)
|
|
26
|
+
return json.dumps(r, indent=2)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
30
|
+
p = argparse.ArgumentParser(prog="namedaemon", description="NameDaemon .com domain tools (https://namedaemon.com)")
|
|
31
|
+
p.add_argument("--version", action="version", version=f"namedaemon {__version__}")
|
|
32
|
+
p.add_argument("--json", action="store_true", help="print the raw JSON response")
|
|
33
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
34
|
+
sub.add_parser("rate", help="brandability score for one name").add_argument("name")
|
|
35
|
+
ideas = sub.add_parser("ideas", help="two-word .com ideas around a keyword")
|
|
36
|
+
ideas.add_argument("keyword")
|
|
37
|
+
ideas.add_argument("limit", nargs="?", type=int)
|
|
38
|
+
sub.add_parser("check", help="status of up to 50 names").add_argument("names", nargs="+")
|
|
39
|
+
sub.add_parser("verify", help="live availability of up to 10 names").add_argument("names", nargs="+")
|
|
40
|
+
tld = sub.add_parser("tld", help="one label across several extensions")
|
|
41
|
+
tld.add_argument("label")
|
|
42
|
+
tld.add_argument("tlds", nargs="+")
|
|
43
|
+
sub.add_parser("tco", help="cost of ownership for an extension").add_argument("tld")
|
|
44
|
+
sub.add_parser("health", help="DNS, email and registration health").add_argument("name")
|
|
45
|
+
sub.add_parser("appraise", help="strength report (no price)").add_argument("name")
|
|
46
|
+
sub.add_parser("handles", help="username availability on social platforms").add_argument("name")
|
|
47
|
+
return p
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
51
|
+
args = build_parser().parse_args(argv)
|
|
52
|
+
nd = NameDaemon()
|
|
53
|
+
try:
|
|
54
|
+
if args.cmd == "rate":
|
|
55
|
+
res = nd.rate(args.name)
|
|
56
|
+
elif args.cmd == "ideas":
|
|
57
|
+
res = nd.ideas(args.keyword, args.limit)
|
|
58
|
+
elif args.cmd == "check":
|
|
59
|
+
res = nd.check(args.names)
|
|
60
|
+
elif args.cmd == "verify":
|
|
61
|
+
res = nd.verify(args.names)
|
|
62
|
+
elif args.cmd == "tld":
|
|
63
|
+
res = nd.tld_check(args.label, args.tlds)
|
|
64
|
+
elif args.cmd == "tco":
|
|
65
|
+
res = nd.tco(args.tld)
|
|
66
|
+
elif args.cmd == "health":
|
|
67
|
+
res = nd.domain_health(args.name)
|
|
68
|
+
elif args.cmd == "appraise":
|
|
69
|
+
res = nd.appraise(args.name)
|
|
70
|
+
else:
|
|
71
|
+
res = nd.handles(args.name)
|
|
72
|
+
except RateLimitError as e:
|
|
73
|
+
print(f"Rate limit reached. Try again in about {e.retry_after} seconds.", file=sys.stderr)
|
|
74
|
+
return 1
|
|
75
|
+
except NameDaemonError as e:
|
|
76
|
+
print(f"Error{' ' + str(e.status) if e.status else ''}: {e.message}", file=sys.stderr)
|
|
77
|
+
return 1
|
|
78
|
+
print(json.dumps(res, indent=2) if args.json else _pretty(args.cmd, res))
|
|
79
|
+
return 0
|
namedaemon/client.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""NameDaemon client for the public REST layer (https://namedaemon.com/wp-json/nd/v1).
|
|
2
|
+
|
|
3
|
+
Standard library only (urllib), so it installs with no dependencies.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
from typing import Any, Dict, Iterable, Optional, Sequence, Union
|
|
14
|
+
|
|
15
|
+
from . import __version__
|
|
16
|
+
|
|
17
|
+
DEFAULT_BASE_URL = "https://namedaemon.com/wp-json/nd/v1"
|
|
18
|
+
|
|
19
|
+
Json = Dict[str, Any]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NameDaemonError(Exception):
|
|
23
|
+
"""Any failed request. `status` is 0 for network errors."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, message: str, status: int = 0, code: str = "", body: Any = None):
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
self.message = message
|
|
28
|
+
self.status = status
|
|
29
|
+
self.code = code
|
|
30
|
+
self.body = body
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RateLimitError(NameDaemonError):
|
|
34
|
+
"""HTTP 429: the shared limit was hit. `retry_after` is in seconds."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, message: str, retry_after: int = 60, **kw: Any):
|
|
37
|
+
super().__init__(message, status=429, **kw)
|
|
38
|
+
self.retry_after = retry_after
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _clean(params: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
|
42
|
+
out: Dict[str, str] = {}
|
|
43
|
+
for k, v in (params or {}).items():
|
|
44
|
+
if v is None or v == "":
|
|
45
|
+
continue
|
|
46
|
+
if isinstance(v, bool):
|
|
47
|
+
v = "true" if v else "false"
|
|
48
|
+
elif isinstance(v, (list, tuple)):
|
|
49
|
+
v = ",".join(str(x) for x in v)
|
|
50
|
+
out[k] = str(v)
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class NameDaemon:
|
|
55
|
+
"""Client for the public NameDaemon API.
|
|
56
|
+
|
|
57
|
+
>>> nd = NameDaemon()
|
|
58
|
+
>>> nd.rate("example")["score"]
|
|
59
|
+
95.5
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
65
|
+
timeout: float = 20.0,
|
|
66
|
+
retries: int = 0,
|
|
67
|
+
user_agent: Optional[str] = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
self.base_url = base_url.rstrip("/")
|
|
70
|
+
self.timeout = timeout
|
|
71
|
+
self.retries = retries
|
|
72
|
+
self.user_agent = user_agent or f"namedaemon-py/{__version__}"
|
|
73
|
+
|
|
74
|
+
# -- transport ---------------------------------------------------------
|
|
75
|
+
def _request(self, method: str, path: str, params: Optional[Dict[str, Any]] = None, body: Any = None) -> Json:
|
|
76
|
+
url = f"{self.base_url}/{path.lstrip('/')}"
|
|
77
|
+
query = urllib.parse.urlencode(_clean(params))
|
|
78
|
+
if query:
|
|
79
|
+
url += "?" + query
|
|
80
|
+
headers = {"Accept": "application/json", "User-Agent": self.user_agent}
|
|
81
|
+
data = None
|
|
82
|
+
if body is not None:
|
|
83
|
+
data = json.dumps(body).encode("utf-8")
|
|
84
|
+
headers["Content-Type"] = "application/json"
|
|
85
|
+
attempt = 0
|
|
86
|
+
while True:
|
|
87
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
88
|
+
try:
|
|
89
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
90
|
+
raw = resp.read().decode("utf-8", "replace")
|
|
91
|
+
status = resp.status
|
|
92
|
+
hdrs = resp.headers
|
|
93
|
+
except urllib.error.HTTPError as e:
|
|
94
|
+
raw = e.read().decode("utf-8", "replace")
|
|
95
|
+
status = e.code
|
|
96
|
+
hdrs = e.headers
|
|
97
|
+
except (urllib.error.URLError, TimeoutError, OSError) as e:
|
|
98
|
+
raise NameDaemonError(f"Network error: {e}") from e
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
parsed = json.loads(raw) if raw else None
|
|
102
|
+
except ValueError:
|
|
103
|
+
parsed = None
|
|
104
|
+
|
|
105
|
+
if 200 <= status < 300:
|
|
106
|
+
if parsed is None:
|
|
107
|
+
raise NameDaemonError("Response was not JSON", status=status)
|
|
108
|
+
return parsed
|
|
109
|
+
|
|
110
|
+
message = (parsed or {}).get("message") if isinstance(parsed, dict) else None
|
|
111
|
+
code = (parsed or {}).get("code", "") if isinstance(parsed, dict) else ""
|
|
112
|
+
message = message or f"HTTP {status}"
|
|
113
|
+
if status == 429:
|
|
114
|
+
try:
|
|
115
|
+
retry_after = int(hdrs.get("Retry-After", "")) if hdrs else 60
|
|
116
|
+
except (TypeError, ValueError):
|
|
117
|
+
retry_after = 60
|
|
118
|
+
if retry_after <= 0:
|
|
119
|
+
retry_after = 60
|
|
120
|
+
if attempt < self.retries:
|
|
121
|
+
attempt += 1
|
|
122
|
+
time.sleep(min(retry_after, 60))
|
|
123
|
+
continue
|
|
124
|
+
raise RateLimitError(message, retry_after=retry_after, code=code, body=parsed)
|
|
125
|
+
raise NameDaemonError(message, status=status, code=code, body=parsed)
|
|
126
|
+
|
|
127
|
+
def get(self, path: str, **params: Any) -> Json:
|
|
128
|
+
"""Any public GET route, e.g. ``nd.get("trend", keyword="ai")``."""
|
|
129
|
+
return self._request("GET", path, params)
|
|
130
|
+
|
|
131
|
+
def post(self, path: str, body: Any) -> Json:
|
|
132
|
+
"""Any public POST route with a JSON body."""
|
|
133
|
+
return self._request("POST", path, body=body)
|
|
134
|
+
|
|
135
|
+
# -- tools -------------------------------------------------------------
|
|
136
|
+
def rate(self, name: str) -> Json:
|
|
137
|
+
"""Brandability score and breakdown for one name (4-24 letters)."""
|
|
138
|
+
return self.get("rate", name=name)
|
|
139
|
+
|
|
140
|
+
def ideas(self, q: str, limit: Optional[int] = None) -> Json:
|
|
141
|
+
"""Two-word .com ideas built around a keyword (3-14 letters)."""
|
|
142
|
+
return self.get("ideas", q=q, limit=limit)
|
|
143
|
+
|
|
144
|
+
def verify(self, domains: Sequence[str]) -> Json:
|
|
145
|
+
"""Live availability of up to 10 names."""
|
|
146
|
+
return self.post("verify", {"domains": list(domains)})
|
|
147
|
+
|
|
148
|
+
def check(self, domains: Sequence[str], verify: int = 0) -> Json:
|
|
149
|
+
"""Zone status of up to 50 names, with optional live checks (0-10)."""
|
|
150
|
+
return self.post("check", {"domains": list(domains), "verify": verify})
|
|
151
|
+
|
|
152
|
+
def tld_check(self, label: str, tlds: Union[str, Iterable[str]]) -> Json:
|
|
153
|
+
"""One label across up to 12 extensions."""
|
|
154
|
+
return self.get("tld-check", label=label, tlds=tlds if isinstance(tlds, str) else list(tlds))
|
|
155
|
+
|
|
156
|
+
def tco(self, tld: Optional[str] = None, **options: Any) -> Json:
|
|
157
|
+
"""Cost of ownership for an extension, or a ranking with q/scope/sort/limit."""
|
|
158
|
+
return self.get("tco", tld=tld, **options)
|
|
159
|
+
|
|
160
|
+
def hacks(self, **options: Any) -> Json:
|
|
161
|
+
"""Domain hacks (e.g. hack.er). Pass word=... or keyword=... and mode=..."""
|
|
162
|
+
return self.get("hacks", **options)
|
|
163
|
+
|
|
164
|
+
def names(self, seeds: Union[str, Iterable[str]], **options: Any) -> Json:
|
|
165
|
+
"""Brand-name generator: seeds plus vibe, min_len, max_len, count, variation, hide_taken."""
|
|
166
|
+
return self.get("names", seeds=seeds if isinstance(seeds, str) else list(seeds), **options)
|
|
167
|
+
|
|
168
|
+
def domain_health(self, name: str) -> Json:
|
|
169
|
+
"""Registration, DNS, email and blocklist checks for a .com label."""
|
|
170
|
+
return self.get("domain-health", name=name)
|
|
171
|
+
|
|
172
|
+
def appraise(self, name: str, deep: bool = False) -> Json:
|
|
173
|
+
"""Strength report for a name (no price)."""
|
|
174
|
+
return self.get("appraise", name=name, deep=True if deep else None)
|
|
175
|
+
|
|
176
|
+
def handles(self, name: str) -> Json:
|
|
177
|
+
"""Username availability on social platforms."""
|
|
178
|
+
return self.get("handles", name=name)
|
|
179
|
+
|
|
180
|
+
def web3(self, name: str) -> Json:
|
|
181
|
+
"""ENS and Solana name availability."""
|
|
182
|
+
return self.get("web3", name=name)
|
|
183
|
+
|
|
184
|
+
def screen(self, name: str) -> Json:
|
|
185
|
+
"""Name screening: unwanted meanings in other languages."""
|
|
186
|
+
return self.get("screen", name=name)
|
|
187
|
+
|
|
188
|
+
def hosting(self, mode: Optional[str] = None, q: Optional[str] = None) -> Json:
|
|
189
|
+
"""Hosting provider ranking (mode: top, provider or domain)."""
|
|
190
|
+
return self.get("hosting", mode=mode, q=q)
|
|
191
|
+
|
|
192
|
+
def trend(self, keyword: str) -> Json:
|
|
193
|
+
"""Daily size history for a keyword in the .com zone."""
|
|
194
|
+
return self.get("trend", keyword=keyword)
|
namedaemon/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: namedaemon
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client and CLI for NameDaemon: .com domain availability, brandability scores, name ideas, TLD costs and domain health.
|
|
5
|
+
Project-URL: Homepage, https://namedaemon.com
|
|
6
|
+
Project-URL: Repository, https://github.com/krasenslavov/namedaemon-pypi
|
|
7
|
+
Project-URL: Issues, https://github.com/krasenslavov/namedaemon-pypi/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/krasenslavov/namedaemon-pypi/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Krasen Slavov
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: brand-name,dns,domain,domain-availability,domain-name,name-generator,namedaemon,rdap,tld,whois
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Internet :: Name Service (DNS)
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# namedaemon
|
|
24
|
+
|
|
25
|
+
Python client and command-line tool for [NameDaemon](https://namedaemon.com), a free set of .com domain tools: availability,
|
|
26
|
+
brandability scores, name ideas, extension costs, domain health and more. No API key, no dependencies.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pip install namedaemon
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Needs Python 3.9 or newer.
|
|
33
|
+
|
|
34
|
+
## Command line
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
namedaemon rate example
|
|
38
|
+
namedaemon ideas nova 10
|
|
39
|
+
namedaemon check nova novax mybrand
|
|
40
|
+
namedaemon tld nova com io ai
|
|
41
|
+
namedaemon tco io
|
|
42
|
+
namedaemon health example
|
|
43
|
+
namedaemon --json appraise example
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`python -m namedaemon ...` works too. `--json` (before the command) prints the raw response.
|
|
47
|
+
|
|
48
|
+
## Library
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from namedaemon import NameDaemon
|
|
52
|
+
|
|
53
|
+
nd = NameDaemon()
|
|
54
|
+
|
|
55
|
+
r = nd.rate("example")
|
|
56
|
+
print(r["score"], r["verdict"]) # 95.5 Excellent
|
|
57
|
+
|
|
58
|
+
ideas = nd.ideas("nova", limit=10)["ideas"]
|
|
59
|
+
results = nd.check(["nova", "novax"])["results"]
|
|
60
|
+
cost = nd.tco("io", years=5)
|
|
61
|
+
health = nd.domain_health("example")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Method | What it returns |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `rate(name)` | brandability score, verdict, breakdown, tips |
|
|
67
|
+
| `ideas(keyword, limit=None)` | two-word .com ideas built around a keyword |
|
|
68
|
+
| `check(names, verify=0)` | zone status of up to 50 names, optional live checks |
|
|
69
|
+
| `verify(names)` | live availability of up to 10 names |
|
|
70
|
+
| `tld_check(label, tlds)` | one label across up to 12 extensions |
|
|
71
|
+
| `tco(tld, **options)` | registration, renewal and total cost by registrar |
|
|
72
|
+
| `hacks(word=...)` | domain hacks such as `hack.er` |
|
|
73
|
+
| `names(seeds, vibe=..., count=...)` | brand-name generator |
|
|
74
|
+
| `domain_health(name)` | registration, DNS, email and blocklist checks |
|
|
75
|
+
| `appraise(name, deep=False)` | strength report (no price) |
|
|
76
|
+
| `handles(name)` | username availability on social platforms |
|
|
77
|
+
| `web3(name)`, `screen(name)`, `hosting()`, `trend(keyword)` | ENS/Solana names, name screening, hosting share, keyword trend |
|
|
78
|
+
| `get(path, **params)`, `post(path, body)` | any other public route under `/wp-json/nd/v1/` |
|
|
79
|
+
|
|
80
|
+
Every method returns the parsed JSON of the matching REST route. The package is typed (`py.typed`).
|
|
81
|
+
|
|
82
|
+
### Errors and rate limits
|
|
83
|
+
|
|
84
|
+
The public API is free and shared, so it limits each visitor to roughly 20 requests a minute. Heavier routes count for more.
|
|
85
|
+
When the limit is hit the client raises `RateLimitError` (with `retry_after` in seconds, 60 by default); pass `retries=1` to wait
|
|
86
|
+
and retry automatically. Other failures raise `NameDaemonError` with `status`, `code` and the server's `message`.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from namedaemon import NameDaemon, RateLimitError
|
|
90
|
+
|
|
91
|
+
nd = NameDaemon(timeout=20, retries=1)
|
|
92
|
+
try:
|
|
93
|
+
nd.rate("example")
|
|
94
|
+
except RateLimitError as e:
|
|
95
|
+
print(e.retry_after)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Please keep usage reasonable. For bulk work, see the paid plan at https://namedaemon.com.
|
|
99
|
+
|
|
100
|
+
## Links
|
|
101
|
+
|
|
102
|
+
- Website and free tools: https://namedaemon.com
|
|
103
|
+
- Issues: https://github.com/krasenslavov/namedaemon-pypi/issues
|
|
104
|
+
- JavaScript version: `npm install namedaemon`
|
|
105
|
+
|
|
106
|
+
MIT license.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
namedaemon/__init__.py,sha256=ym1XFq0EDBQWisq_73XNtxuxNelZ2wWWROCeBzqkuMY,301
|
|
2
|
+
namedaemon/__main__.py,sha256=k1ocEWawweo1qCJWNFAAvyxz3tcY13dzvCenHszij30,48
|
|
3
|
+
namedaemon/cli.py,sha256=HAoireMU0cuOTlduvz6DDMVSzznls23G_6DpQehrLEI,3500
|
|
4
|
+
namedaemon/client.py,sha256=P1SfzxHvKZeTveJECw5p06pFJEkttdbdz_50U61ahEc,7654
|
|
5
|
+
namedaemon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
namedaemon-0.1.0.dist-info/METADATA,sha256=TfMiQONbzyZaPLET9bGJGvnzIl46pO9NxtIu18OkhJM,3930
|
|
7
|
+
namedaemon-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
8
|
+
namedaemon-0.1.0.dist-info/entry_points.txt,sha256=jyG_WeD02SO_j8cC1VFA5aFp6ffB7nJmZHDMcdMTxBs,51
|
|
9
|
+
namedaemon-0.1.0.dist-info/licenses/LICENSE,sha256=JIVhApflbNrYfPfaWhUkCv5JDFadsz1Fbkv9O9i6-n0,1070
|
|
10
|
+
namedaemon-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Krasen Slavov
|
|
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.
|