namecheap-python 1.0.0__py3-none-any.whl → 1.0.2__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.
- namecheap/_api/base.py +5 -2
- namecheap/_api/dns.py +10 -2
- namecheap/_api/domains.py +24 -8
- namecheap/logging.py +12 -4
- namecheap/models.py +5 -3
- namecheap_cli/__main__.py +128 -45
- namecheap_dns_tui/__main__.py +27 -9
- {namecheap_python-1.0.0.dist-info → namecheap_python-1.0.2.dist-info}/METADATA +73 -68
- {namecheap_python-1.0.0.dist-info → namecheap_python-1.0.2.dist-info}/RECORD +12 -12
- {namecheap_python-1.0.0.dist-info → namecheap_python-1.0.2.dist-info}/WHEEL +0 -0
- {namecheap_python-1.0.0.dist-info → namecheap_python-1.0.2.dist-info}/entry_points.txt +0 -0
- {namecheap_python-1.0.0.dist-info → namecheap_python-1.0.2.dist-info}/licenses/LICENSE +0 -0
namecheap/_api/base.py
CHANGED
|
@@ -56,7 +56,9 @@ def normalize_xml_response(data: dict[str, Any]) -> dict[str, Any]:
|
|
|
56
56
|
# Fix known typos from Namecheap
|
|
57
57
|
if key == "@YourAdditonalCost": # Their typo (missing 'i' in Additional)
|
|
58
58
|
normalized["@YourAdditionalCost"] = value # Correct spelling
|
|
59
|
-
logger.debug(
|
|
59
|
+
logger.debug(
|
|
60
|
+
"Fixed Namecheap typo: @YourAdditonalCost -> @YourAdditionalCost"
|
|
61
|
+
)
|
|
60
62
|
|
|
61
63
|
# Debug canary: Alert if they have both versions (means they're fixing it)
|
|
62
64
|
if "@YourAdditionalCost" in data and "@YourAdditonalCost" in data:
|
|
@@ -70,7 +72,8 @@ def normalize_xml_response(data: dict[str, Any]) -> dict[str, Any]:
|
|
|
70
72
|
normalized[key] = normalize_xml_response(value)
|
|
71
73
|
elif isinstance(value, list):
|
|
72
74
|
normalized[key] = [
|
|
73
|
-
normalize_xml_response(item) if isinstance(item, dict) else item
|
|
75
|
+
normalize_xml_response(item) if isinstance(item, dict) else item
|
|
76
|
+
for item in value
|
|
74
77
|
]
|
|
75
78
|
|
|
76
79
|
return normalized
|
namecheap/_api/dns.py
CHANGED
|
@@ -35,7 +35,9 @@ class DNSRecordBuilder:
|
|
|
35
35
|
Self for chaining
|
|
36
36
|
"""
|
|
37
37
|
self._records.append(
|
|
38
|
-
DNSRecord.model_validate(
|
|
38
|
+
DNSRecord.model_validate(
|
|
39
|
+
{"@Name": name, "@Type": "A", "@Address": ip, "@TTL": ttl}
|
|
40
|
+
)
|
|
39
41
|
)
|
|
40
42
|
return self
|
|
41
43
|
|
|
@@ -94,7 +96,13 @@ class DNSRecordBuilder:
|
|
|
94
96
|
"""
|
|
95
97
|
self._records.append(
|
|
96
98
|
DNSRecord.model_validate(
|
|
97
|
-
{
|
|
99
|
+
{
|
|
100
|
+
"@Name": name,
|
|
101
|
+
"@Type": "MX",
|
|
102
|
+
"@Address": server,
|
|
103
|
+
"@TTL": ttl,
|
|
104
|
+
"@MXPref": priority,
|
|
105
|
+
}
|
|
98
106
|
)
|
|
99
107
|
)
|
|
100
108
|
return self
|
namecheap/_api/domains.py
CHANGED
|
@@ -17,7 +17,9 @@ from .base import BaseAPI
|
|
|
17
17
|
class DomainsAPI(BaseAPI):
|
|
18
18
|
"""Domain management operations."""
|
|
19
19
|
|
|
20
|
-
def check(
|
|
20
|
+
def check(
|
|
21
|
+
self, *domains: str, include_pricing: bool = False
|
|
22
|
+
) -> builtins.list[DomainCheck]:
|
|
21
23
|
"""
|
|
22
24
|
Check domain availability.
|
|
23
25
|
|
|
@@ -158,7 +160,9 @@ class DomainsAPI(BaseAPI):
|
|
|
158
160
|
|
|
159
161
|
# Add contact info if provided
|
|
160
162
|
if contact:
|
|
161
|
-
contact_data =
|
|
163
|
+
contact_data = (
|
|
164
|
+
contact.model_dump() if isinstance(contact, Contact) else contact
|
|
165
|
+
)
|
|
162
166
|
# Add contact fields for all types (Registrant, Tech, Admin, AuxBilling)
|
|
163
167
|
for contact_type in ["Registrant", "Tech", "Admin", "AuxBilling"]:
|
|
164
168
|
for field, value in contact_data.items():
|
|
@@ -284,7 +288,9 @@ class DomainsAPI(BaseAPI):
|
|
|
284
288
|
assert isinstance(result, dict)
|
|
285
289
|
return bool(result)
|
|
286
290
|
|
|
287
|
-
def _get_pricing(
|
|
291
|
+
def _get_pricing(
|
|
292
|
+
self, domains: builtins.list[str]
|
|
293
|
+
) -> dict[str, dict[str, Decimal | None]]:
|
|
288
294
|
"""
|
|
289
295
|
Get pricing information for domains.
|
|
290
296
|
|
|
@@ -362,7 +368,9 @@ class DomainsAPI(BaseAPI):
|
|
|
362
368
|
if not isinstance(products, list):
|
|
363
369
|
products = [products] if products else []
|
|
364
370
|
|
|
365
|
-
logger.debug(
|
|
371
|
+
logger.debug(
|
|
372
|
+
f"Found {len(products)} products in REGISTER category"
|
|
373
|
+
)
|
|
366
374
|
|
|
367
375
|
# Find the product matching our TLD
|
|
368
376
|
for product in products:
|
|
@@ -370,7 +378,9 @@ class DomainsAPI(BaseAPI):
|
|
|
370
378
|
continue
|
|
371
379
|
|
|
372
380
|
product_name = product.get("@Name", "")
|
|
373
|
-
logger.debug(
|
|
381
|
+
logger.debug(
|
|
382
|
+
f"Checking product: {product_name} vs {tld}"
|
|
383
|
+
)
|
|
374
384
|
|
|
375
385
|
if product_name.lower() == tld.lower():
|
|
376
386
|
# Get price list
|
|
@@ -378,7 +388,9 @@ class DomainsAPI(BaseAPI):
|
|
|
378
388
|
if not isinstance(price_info, list):
|
|
379
389
|
price_info = [price_info] if price_info else []
|
|
380
390
|
|
|
381
|
-
logger.debug(
|
|
391
|
+
logger.debug(
|
|
392
|
+
f"Found {len(price_info)} price entries for {tld}"
|
|
393
|
+
)
|
|
382
394
|
|
|
383
395
|
# Find 1 year price
|
|
384
396
|
for price in price_info:
|
|
@@ -405,13 +417,17 @@ class DomainsAPI(BaseAPI):
|
|
|
405
417
|
# Apply to all domains with this TLD
|
|
406
418
|
for domain in domain_list:
|
|
407
419
|
pricing[domain] = {
|
|
408
|
-
"regular_price": Decimal(
|
|
420
|
+
"regular_price": Decimal(
|
|
421
|
+
regular_price
|
|
422
|
+
)
|
|
409
423
|
if regular_price
|
|
410
424
|
else None,
|
|
411
425
|
"your_price": Decimal(your_price)
|
|
412
426
|
if your_price
|
|
413
427
|
else None,
|
|
414
|
-
"retail_price": Decimal(
|
|
428
|
+
"retail_price": Decimal(
|
|
429
|
+
retail_price
|
|
430
|
+
)
|
|
415
431
|
if retail_price
|
|
416
432
|
else None,
|
|
417
433
|
}
|
namecheap/logging.py
CHANGED
|
@@ -74,13 +74,21 @@ class ErrorDisplay:
|
|
|
74
74
|
|
|
75
75
|
if hasattr(error, "_ip_help") and error._ip_help is not None:
|
|
76
76
|
console.print("\n[yellow]🔍 IP Configuration Issue[/yellow]")
|
|
77
|
-
console.print(
|
|
78
|
-
|
|
77
|
+
console.print(
|
|
78
|
+
f" Your current IP: [cyan]{error._ip_help['actual_ip']}[/cyan]"
|
|
79
|
+
)
|
|
80
|
+
console.print(
|
|
81
|
+
f" Configured IP: [cyan]{error._ip_help['configured_ip']}[/cyan]"
|
|
82
|
+
)
|
|
79
83
|
console.print("\n[yellow]💡 To fix this:[/yellow]")
|
|
80
|
-
console.print(
|
|
84
|
+
console.print(
|
|
85
|
+
" 1. Log in to [link=https://www.namecheap.com]Namecheap[/link]"
|
|
86
|
+
)
|
|
81
87
|
console.print(" 2. Go to Profile → Tools → API Access")
|
|
82
88
|
actual_ip = error._ip_help["actual_ip"]
|
|
83
|
-
console.print(
|
|
89
|
+
console.print(
|
|
90
|
+
f" 3. Add this IP to whitelist: [cyan]{actual_ip}[/cyan]"
|
|
91
|
+
)
|
|
84
92
|
console.print(
|
|
85
93
|
f" 4. Update your .env file: [cyan]NAMECHEAP_CLIENT_IP={actual_ip}[/cyan]"
|
|
86
94
|
)
|
namecheap/models.py
CHANGED
|
@@ -89,7 +89,9 @@ class DomainCheck(XMLModel):
|
|
|
89
89
|
"""
|
|
90
90
|
|
|
91
91
|
domain: str = Field(alias="@Domain", description="Domain name checked")
|
|
92
|
-
available: bool = Field(
|
|
92
|
+
available: bool = Field(
|
|
93
|
+
alias="@Available", description="Whether domain is available"
|
|
94
|
+
)
|
|
93
95
|
premium: bool = Field(
|
|
94
96
|
default=False,
|
|
95
97
|
alias="@IsPremiumName",
|
|
@@ -182,8 +184,8 @@ class DNSRecord(XMLModel):
|
|
|
182
184
|
"""A DNS record."""
|
|
183
185
|
|
|
184
186
|
name: str = Field(alias="@Name", default="@")
|
|
185
|
-
type: Literal["A", "AAAA", "CNAME", "MX", "NS", "TXT", "URL", "URL301", "FRAME"] =
|
|
186
|
-
alias="@Type"
|
|
187
|
+
type: Literal["A", "AAAA", "CNAME", "MX", "NS", "TXT", "URL", "URL301", "FRAME"] = (
|
|
188
|
+
Field(alias="@Type")
|
|
187
189
|
)
|
|
188
190
|
value: str = Field(alias="@Address")
|
|
189
191
|
ttl: int = Field(alias="@TTL", default=1800)
|
namecheap_cli/__main__.py
CHANGED
|
@@ -23,8 +23,26 @@ from .completion import get_completion_script
|
|
|
23
23
|
|
|
24
24
|
console = Console()
|
|
25
25
|
|
|
26
|
+
|
|
26
27
|
# Configuration
|
|
27
|
-
|
|
28
|
+
def get_config_dir() -> Path:
|
|
29
|
+
"""Get config directory, using XDG on Unix-like systems."""
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
|
|
33
|
+
if sys.platform == "win32":
|
|
34
|
+
# Windows: use platformdirs for proper Windows paths
|
|
35
|
+
from platformdirs import user_config_dir
|
|
36
|
+
|
|
37
|
+
return Path(user_config_dir("namecheap"))
|
|
38
|
+
# Linux/macOS: use XDG
|
|
39
|
+
xdg_config = os.environ.get("XDG_CONFIG_HOME")
|
|
40
|
+
if xdg_config:
|
|
41
|
+
return Path(xdg_config) / "namecheap"
|
|
42
|
+
return Path.home() / ".config" / "namecheap"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
CONFIG_DIR = get_config_dir()
|
|
28
46
|
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
29
47
|
|
|
30
48
|
|
|
@@ -54,9 +72,33 @@ class Config:
|
|
|
54
72
|
if self.client:
|
|
55
73
|
return self.client
|
|
56
74
|
|
|
75
|
+
# Check if config file exists
|
|
76
|
+
if not CONFIG_FILE.exists():
|
|
77
|
+
console.print("[red]❌ Configuration not found![/red]")
|
|
78
|
+
console.print(
|
|
79
|
+
"\nPlease run [bold cyan]namecheap-cli config init[/bold cyan] to set up your configuration."
|
|
80
|
+
)
|
|
81
|
+
console.print(
|
|
82
|
+
f"\nThis will create a config file at: [dim]{CONFIG_FILE}[/dim]"
|
|
83
|
+
)
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
|
|
57
86
|
config = self.load_config()
|
|
58
87
|
profile_config = config.get("profiles", {}).get(self.profile, {})
|
|
59
88
|
|
|
89
|
+
# Check if profile exists
|
|
90
|
+
if not profile_config:
|
|
91
|
+
console.print(
|
|
92
|
+
f"[red]❌ Profile '{self.profile}' not found in configuration![/red]"
|
|
93
|
+
)
|
|
94
|
+
console.print(
|
|
95
|
+
f"\nAvailable profiles: {', '.join(config.get('profiles', {}).keys()) or 'none'}"
|
|
96
|
+
)
|
|
97
|
+
console.print(
|
|
98
|
+
"\nRun [bold cyan]namecheap-cli config init[/bold cyan] to create a new profile."
|
|
99
|
+
)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
60
102
|
# Override sandbox if specified
|
|
61
103
|
if self.sandbox is not None:
|
|
62
104
|
profile_config["sandbox"] = self.sandbox
|
|
@@ -65,7 +107,21 @@ class Config:
|
|
|
65
107
|
self.client = Namecheap(**profile_config)
|
|
66
108
|
return self.client
|
|
67
109
|
except Exception as e:
|
|
68
|
-
|
|
110
|
+
# Check for common configuration errors
|
|
111
|
+
error_msg = str(e)
|
|
112
|
+
if (
|
|
113
|
+
"Parameter APIUser is missing" in error_msg
|
|
114
|
+
or "Parameter APIKey is missing" in error_msg
|
|
115
|
+
):
|
|
116
|
+
console.print("[red]❌ Invalid or incomplete configuration![/red]")
|
|
117
|
+
console.print(
|
|
118
|
+
"\nYour configuration appears to be missing required fields."
|
|
119
|
+
)
|
|
120
|
+
console.print(
|
|
121
|
+
"Please run [bold cyan]namecheap-cli config init[/bold cyan] to reconfigure."
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
console.print(f"[red]❌ Error initializing client: {e}[/red]")
|
|
69
125
|
sys.exit(1)
|
|
70
126
|
|
|
71
127
|
|
|
@@ -93,7 +149,9 @@ def output_formatter(data: Any, format: str, headers: list[str] | None = None) -
|
|
|
93
149
|
|
|
94
150
|
|
|
95
151
|
@click.group()
|
|
96
|
-
@click.option(
|
|
152
|
+
@click.option(
|
|
153
|
+
"--config", "config_path", type=click.Path(exists=True), help="Config file path"
|
|
154
|
+
)
|
|
97
155
|
@click.option("--profile", default="default", help="Config profile to use")
|
|
98
156
|
@click.option("--sandbox", is_flag=True, help="Use sandbox API")
|
|
99
157
|
@click.option(
|
|
@@ -108,7 +166,9 @@ def output_formatter(data: Any, format: str, headers: list[str] | None = None) -
|
|
|
108
166
|
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
109
167
|
@click.version_option()
|
|
110
168
|
@pass_config
|
|
111
|
-
def cli(
|
|
169
|
+
def cli(
|
|
170
|
+
config: Config, config_path, profile, sandbox, output, no_color, quiet, verbose
|
|
171
|
+
) -> None:
|
|
112
172
|
"""Namecheap CLI - Manage domains and DNS records."""
|
|
113
173
|
config.output_format = output
|
|
114
174
|
config.no_color = no_color
|
|
@@ -129,10 +189,14 @@ def domain_group() -> None:
|
|
|
129
189
|
|
|
130
190
|
@domain_group.command("list")
|
|
131
191
|
@click.option("--status", type=click.Choice(["active", "expired", "locked"]))
|
|
132
|
-
@click.option(
|
|
192
|
+
@click.option(
|
|
193
|
+
"--sort", type=click.Choice(["name", "expires", "created"]), default="name"
|
|
194
|
+
)
|
|
133
195
|
@click.option("--expiring-in", type=int, help="Show domains expiring within N days")
|
|
134
196
|
@pass_config
|
|
135
|
-
def domain_list(
|
|
197
|
+
def domain_list(
|
|
198
|
+
config: Config, status: str | None, sort: str, expiring_in: int | None
|
|
199
|
+
) -> None:
|
|
136
200
|
"""List all domains."""
|
|
137
201
|
nc = config.init_client()
|
|
138
202
|
|
|
@@ -213,9 +277,8 @@ def domain_list(config: Config, status: str | None, sort: str, expiring_in: int
|
|
|
213
277
|
@domain_group.command("check")
|
|
214
278
|
@click.argument("domains", nargs=-1, required=False)
|
|
215
279
|
@click.option("--file", "-f", type=click.File("r"), help="File with domains to check")
|
|
216
|
-
@click.option("--pricing", "-p", is_flag=True, help="Include pricing information")
|
|
217
280
|
@pass_config
|
|
218
|
-
def domain_check(config: Config, domains: tuple[str, ...], file
|
|
281
|
+
def domain_check(config: Config, domains: tuple[str, ...], file) -> None:
|
|
219
282
|
"""Check domain availability."""
|
|
220
283
|
nc = config.init_client()
|
|
221
284
|
|
|
@@ -235,39 +298,30 @@ def domain_check(config: Config, domains: tuple[str, ...], file, pricing: bool)
|
|
|
235
298
|
transient=True,
|
|
236
299
|
) as progress:
|
|
237
300
|
progress.add_task(f"Checking {len(domain_list)} domains...", total=None)
|
|
238
|
-
results = nc.domains.check(*domain_list, include_pricing=
|
|
301
|
+
results = nc.domains.check(*domain_list, include_pricing=True)
|
|
239
302
|
|
|
240
303
|
# Output
|
|
241
304
|
if config.output_format == "table":
|
|
242
305
|
table = Table(title="Domain Availability")
|
|
243
306
|
table.add_column("Domain", style="cyan")
|
|
244
307
|
table.add_column("Available", style="green")
|
|
245
|
-
|
|
246
|
-
table.add_column("Price", style="yellow")
|
|
247
|
-
table.add_column("Total", style="yellow")
|
|
308
|
+
table.add_column("Price (USD/year)", style="yellow")
|
|
248
309
|
|
|
249
310
|
for result in results:
|
|
250
311
|
available_text = "✅ Available" if result.available else "❌ Taken"
|
|
251
312
|
available_style = "green" if result.available else "red"
|
|
252
313
|
|
|
314
|
+
if result.available and result.price:
|
|
315
|
+
price_text = f"${result.price:.2f}"
|
|
316
|
+
else:
|
|
317
|
+
price_text = "-"
|
|
318
|
+
|
|
253
319
|
row = [
|
|
254
320
|
result.domain,
|
|
255
321
|
f"[{available_style}]{available_text}[/{available_style}]",
|
|
322
|
+
price_text,
|
|
256
323
|
]
|
|
257
324
|
|
|
258
|
-
if pricing and result.available:
|
|
259
|
-
if result.price:
|
|
260
|
-
price_text = f"${result.price:.2f}"
|
|
261
|
-
total_text = (
|
|
262
|
-
f"${result.total_price:.2f}" if result.total_price else price_text
|
|
263
|
-
)
|
|
264
|
-
else:
|
|
265
|
-
price_text = "N/A"
|
|
266
|
-
total_text = "N/A"
|
|
267
|
-
row.extend([price_text, total_text])
|
|
268
|
-
elif pricing:
|
|
269
|
-
row.extend(["-", "-"])
|
|
270
|
-
|
|
271
325
|
table.add_row(*row)
|
|
272
326
|
|
|
273
327
|
console.print(table)
|
|
@@ -293,16 +347,11 @@ def domain_check(config: Config, domains: tuple[str, ...], file, pricing: bool)
|
|
|
293
347
|
"domain": result.domain,
|
|
294
348
|
"available": result.available,
|
|
295
349
|
}
|
|
296
|
-
if
|
|
350
|
+
if result.available and result.price:
|
|
297
351
|
item["price"] = float(result.price)
|
|
298
|
-
item["total_price"] = (
|
|
299
|
-
float(result.total_price) if result.total_price else float(result.price)
|
|
300
|
-
)
|
|
301
352
|
data.append(item)
|
|
302
353
|
|
|
303
|
-
headers = ["domain", "available"]
|
|
304
|
-
if pricing:
|
|
305
|
-
headers.extend(["price", "total_price"])
|
|
354
|
+
headers = ["domain", "available", "price"]
|
|
306
355
|
output_formatter(data, config.output_format, headers)
|
|
307
356
|
|
|
308
357
|
except NamecheapError as e:
|
|
@@ -331,12 +380,18 @@ def domain_info(config: Config, domain: str) -> None:
|
|
|
331
380
|
f"[bold]Status:[/bold] "
|
|
332
381
|
f"{'Active' if not domain_obj.is_expired else '[red]Expired[/red]'}"
|
|
333
382
|
)
|
|
334
|
-
console.print(
|
|
335
|
-
|
|
383
|
+
console.print(
|
|
384
|
+
f"[bold]Created:[/bold] {domain_obj.created.strftime('%Y-%m-%d')}"
|
|
385
|
+
)
|
|
386
|
+
console.print(
|
|
387
|
+
f"[bold]Expires:[/bold] {domain_obj.expires.strftime('%Y-%m-%d')}"
|
|
388
|
+
)
|
|
336
389
|
console.print(
|
|
337
390
|
f"[bold]Auto-Renew:[/bold] {'✓ Enabled' if domain_obj.auto_renew else '✗ Disabled'}"
|
|
338
391
|
)
|
|
339
|
-
console.print(
|
|
392
|
+
console.print(
|
|
393
|
+
f"[bold]Locked:[/bold] {'🔒 Yes' if domain_obj.is_locked else '🔓 No'}"
|
|
394
|
+
)
|
|
340
395
|
console.print(
|
|
341
396
|
f"[bold]WHOIS Guard:[/bold] "
|
|
342
397
|
f"{'✓ Enabled' if domain_obj.whois_guard else '✗ Disabled'}"
|
|
@@ -345,7 +400,9 @@ def domain_info(config: Config, domain: str) -> None:
|
|
|
345
400
|
# Calculate days until expiration
|
|
346
401
|
days_left = (domain_obj.expires - datetime.now()).days
|
|
347
402
|
if days_left < 30:
|
|
348
|
-
console.print(
|
|
403
|
+
console.print(
|
|
404
|
+
f"\n⚠️ [yellow]Domain expires in {days_left} days![/yellow]"
|
|
405
|
+
)
|
|
349
406
|
elif days_left < 60:
|
|
350
407
|
console.print(f"\n📅 Domain expires in {days_left} days")
|
|
351
408
|
|
|
@@ -478,7 +535,9 @@ def dns_add(
|
|
|
478
535
|
|
|
479
536
|
try:
|
|
480
537
|
# Create the new record
|
|
481
|
-
new_record = DNSRecord(
|
|
538
|
+
new_record = DNSRecord(
|
|
539
|
+
name=name, type=record_type, value=value, ttl=ttl, priority=priority
|
|
540
|
+
)
|
|
482
541
|
|
|
483
542
|
# Get existing records
|
|
484
543
|
if not config.quiet:
|
|
@@ -549,7 +608,9 @@ def dns_delete(
|
|
|
549
608
|
nc = config.init_client()
|
|
550
609
|
|
|
551
610
|
if all and not (type or name or value) and not yes:
|
|
552
|
-
console.print(
|
|
611
|
+
console.print(
|
|
612
|
+
f"[red]⚠️ Warning: This will delete ALL DNS records for {domain}[/red]"
|
|
613
|
+
)
|
|
553
614
|
if not Confirm.ask("Are you sure?", default=False):
|
|
554
615
|
console.print("[yellow]Cancelled[/yellow]")
|
|
555
616
|
return
|
|
@@ -586,7 +647,9 @@ def dns_delete(
|
|
|
586
647
|
|
|
587
648
|
# Show what will be deleted
|
|
588
649
|
if not yes and not config.quiet:
|
|
589
|
-
console.print(
|
|
650
|
+
console.print(
|
|
651
|
+
f"[yellow]Will delete {len(records_to_delete)} record(s):[/yellow]"
|
|
652
|
+
)
|
|
590
653
|
for r in records_to_delete:
|
|
591
654
|
console.print(f" • {r.type} {r.name} → {r.value}")
|
|
592
655
|
|
|
@@ -641,7 +704,9 @@ def dns_delete(
|
|
|
641
704
|
default="yaml",
|
|
642
705
|
help="Export format",
|
|
643
706
|
)
|
|
644
|
-
@click.option(
|
|
707
|
+
@click.option(
|
|
708
|
+
"--output", "-o", type=click.File("w"), help="Output file (default: stdout)"
|
|
709
|
+
)
|
|
645
710
|
@pass_config
|
|
646
711
|
def dns_export(config: Config, domain: str, format: str, output) -> None:
|
|
647
712
|
"""Export DNS records."""
|
|
@@ -656,7 +721,9 @@ def dns_export(config: Config, domain: str, format: str, output) -> None:
|
|
|
656
721
|
|
|
657
722
|
for r in sorted(records, key=lambda x: (x.type, x.name)):
|
|
658
723
|
if r.type == "MX":
|
|
659
|
-
lines.append(
|
|
724
|
+
lines.append(
|
|
725
|
+
f"{r.name}\t{r.ttl}\tIN\t{r.type}\t{r.priority}\t{r.value}"
|
|
726
|
+
)
|
|
660
727
|
else:
|
|
661
728
|
lines.append(f"{r.name}\t{r.ttl}\tIN\t{r.type}\t{r.value}")
|
|
662
729
|
|
|
@@ -698,7 +765,9 @@ def dns_export(config: Config, domain: str, format: str, output) -> None:
|
|
|
698
765
|
if output:
|
|
699
766
|
output.write(content)
|
|
700
767
|
if not config.quiet:
|
|
701
|
-
console.print(
|
|
768
|
+
console.print(
|
|
769
|
+
f"[green]✅ Exported {len(records)} records to {output.name}[/green]"
|
|
770
|
+
)
|
|
702
771
|
else:
|
|
703
772
|
click.echo(content)
|
|
704
773
|
|
|
@@ -721,7 +790,9 @@ def account_balance(config: Config) -> None:
|
|
|
721
790
|
|
|
722
791
|
try:
|
|
723
792
|
# This would need to be implemented in the SDK
|
|
724
|
-
console.print(
|
|
793
|
+
console.print(
|
|
794
|
+
"[yellow]Account balance check not yet implemented in SDK[/yellow]"
|
|
795
|
+
)
|
|
725
796
|
console.print("This feature requires the users.getBalances API method")
|
|
726
797
|
|
|
727
798
|
except NamecheapError as e:
|
|
@@ -748,6 +819,14 @@ def config_init() -> None:
|
|
|
748
819
|
|
|
749
820
|
console.print("\n[bold cyan]Namecheap CLI Configuration Wizard[/bold cyan]\n")
|
|
750
821
|
|
|
822
|
+
console.print("[dim]To get your API key:[/dim]")
|
|
823
|
+
console.print(
|
|
824
|
+
"1. Go to [link=https://ap.www.namecheap.com/settings/tools/apiaccess/]https://ap.www.namecheap.com/settings/tools/apiaccess/[/link]"
|
|
825
|
+
)
|
|
826
|
+
console.print("2. Enable API access")
|
|
827
|
+
console.print("3. Whitelist your IP address")
|
|
828
|
+
console.print("4. Generate your API key\n")
|
|
829
|
+
|
|
751
830
|
# Get configuration values
|
|
752
831
|
api_key = Prompt.ask("API Key", password=True)
|
|
753
832
|
username = Prompt.ask("Username")
|
|
@@ -783,7 +862,9 @@ def config_init() -> None:
|
|
|
783
862
|
|
|
784
863
|
console.print(f"\n[green]✅ Configuration saved to {CONFIG_FILE}[/green]")
|
|
785
864
|
console.print("\n💡 Tips:")
|
|
786
|
-
console.print(
|
|
865
|
+
console.print(
|
|
866
|
+
" • You can also use environment variables (NAMECHEAP_API_KEY, etc.)"
|
|
867
|
+
)
|
|
787
868
|
console.print(" • Add more profiles with: nc config add-profile <name>")
|
|
788
869
|
console.print(" • Use a profile with: nc --profile <name> <command>")
|
|
789
870
|
|
|
@@ -810,7 +891,9 @@ def completion(shell: str) -> None:
|
|
|
810
891
|
console.print("[dim]nc completion zsh >> ~/.zshrc[/dim]")
|
|
811
892
|
elif shell == "fish":
|
|
812
893
|
console.print("[dim]# Add to fish config:[/dim]")
|
|
813
|
-
console.print(
|
|
894
|
+
console.print(
|
|
895
|
+
"[dim]nc completion fish > ~/.config/fish/completions/nc.fish[/dim]"
|
|
896
|
+
)
|
|
814
897
|
|
|
815
898
|
|
|
816
899
|
def main() -> None:
|
namecheap_dns_tui/__main__.py
CHANGED
|
@@ -189,7 +189,9 @@ class AddRecordModal(ModalScreen):
|
|
|
189
189
|
with ScrollableContainer(id="form-scroll"):
|
|
190
190
|
with Vertical(classes="field-group"):
|
|
191
191
|
yield Label("Record Type:")
|
|
192
|
-
initial_type =
|
|
192
|
+
initial_type = (
|
|
193
|
+
self.editing_record.type if self.editing_record else "A"
|
|
194
|
+
)
|
|
193
195
|
yield Select(
|
|
194
196
|
[
|
|
195
197
|
("A - IPv4 Address", "A"),
|
|
@@ -208,7 +210,9 @@ class AddRecordModal(ModalScreen):
|
|
|
208
210
|
|
|
209
211
|
with Vertical(classes="field-group"):
|
|
210
212
|
yield Label("Name:")
|
|
211
|
-
initial_name =
|
|
213
|
+
initial_name = (
|
|
214
|
+
self.editing_record.name if self.editing_record else "@"
|
|
215
|
+
)
|
|
212
216
|
yield Input(
|
|
213
217
|
placeholder="@ for root or subdomain",
|
|
214
218
|
id="record-name",
|
|
@@ -217,7 +221,9 @@ class AddRecordModal(ModalScreen):
|
|
|
217
221
|
|
|
218
222
|
with Vertical(classes="field-group"):
|
|
219
223
|
yield Label("Value:")
|
|
220
|
-
initial_value =
|
|
224
|
+
initial_value = (
|
|
225
|
+
self.editing_record.value if self.editing_record else ""
|
|
226
|
+
)
|
|
221
227
|
yield Input(
|
|
222
228
|
placeholder="Enter value based on record type",
|
|
223
229
|
id="record-value",
|
|
@@ -226,7 +232,9 @@ class AddRecordModal(ModalScreen):
|
|
|
226
232
|
|
|
227
233
|
with Vertical(classes="field-group"):
|
|
228
234
|
yield Label("TTL (seconds):")
|
|
229
|
-
initial_ttl =
|
|
235
|
+
initial_ttl = (
|
|
236
|
+
str(self.editing_record.ttl) if self.editing_record else "1800"
|
|
237
|
+
)
|
|
230
238
|
yield Input(placeholder="1800", id="record-ttl", value=initial_ttl)
|
|
231
239
|
|
|
232
240
|
with Vertical(classes="field-group", id="priority-group"):
|
|
@@ -236,7 +244,9 @@ class AddRecordModal(ModalScreen):
|
|
|
236
244
|
if self.editing_record and self.editing_record.priority
|
|
237
245
|
else ""
|
|
238
246
|
)
|
|
239
|
-
yield Input(
|
|
247
|
+
yield Input(
|
|
248
|
+
placeholder="10", id="record-priority", value=initial_priority
|
|
249
|
+
)
|
|
240
250
|
|
|
241
251
|
with Horizontal(classes="button-row"):
|
|
242
252
|
button_text = "Save Changes" if self.editing_record else "Add Record"
|
|
@@ -482,7 +492,9 @@ class DNSManagerApp(App):
|
|
|
482
492
|
str(record.priority) if record.priority else "-",
|
|
483
493
|
)
|
|
484
494
|
|
|
485
|
-
self.update_status(
|
|
495
|
+
self.update_status(
|
|
496
|
+
f"Loaded {len(self.records)} records for {self.current_domain}"
|
|
497
|
+
)
|
|
486
498
|
|
|
487
499
|
def action_refresh(self) -> None:
|
|
488
500
|
"""Refresh records."""
|
|
@@ -623,7 +635,9 @@ class DNSManagerApp(App):
|
|
|
623
635
|
)
|
|
624
636
|
builder._records[
|
|
625
637
|
-1
|
|
626
|
-
].type =
|
|
638
|
+
].type = (
|
|
639
|
+
"URL" # Override to use URL instead of URL301
|
|
640
|
+
)
|
|
627
641
|
elif rec.type == "URL301":
|
|
628
642
|
builder.url(
|
|
629
643
|
rec.name,
|
|
@@ -674,7 +688,9 @@ class DNSManagerApp(App):
|
|
|
674
688
|
def handle_confirm(confirmed: bool) -> None:
|
|
675
689
|
"""Handle delete confirmation."""
|
|
676
690
|
if confirmed:
|
|
677
|
-
self.update_status(
|
|
691
|
+
self.update_status(
|
|
692
|
+
f"Deleting {record.type} record '{record.name}'..."
|
|
693
|
+
)
|
|
678
694
|
|
|
679
695
|
def delete() -> None:
|
|
680
696
|
try:
|
|
@@ -711,7 +727,9 @@ class DNSManagerApp(App):
|
|
|
711
727
|
)
|
|
712
728
|
builder._records[
|
|
713
729
|
-1
|
|
714
|
-
].type =
|
|
730
|
+
].type = (
|
|
731
|
+
"URL" # Override to use URL instead of URL301
|
|
732
|
+
)
|
|
715
733
|
elif rec.type == "URL301":
|
|
716
734
|
builder.url(
|
|
717
735
|
rec.name,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: namecheap-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: A friendly Python SDK for Namecheap API
|
|
5
5
|
Project-URL: Homepage, https://github.com/adriangalilea/namecheap-python
|
|
6
6
|
Project-URL: Repository, https://github.com/adriangalilea/namecheap-python
|
|
@@ -26,21 +26,17 @@ Requires-Dist: tldextract>=5.0.0
|
|
|
26
26
|
Requires-Dist: xmltodict>=0.13.0
|
|
27
27
|
Provides-Extra: all
|
|
28
28
|
Requires-Dist: click>=8.1.0; extra == 'all'
|
|
29
|
+
Requires-Dist: platformdirs>=4.0.0; extra == 'all'
|
|
29
30
|
Requires-Dist: pyyaml>=6.0.0; extra == 'all'
|
|
30
31
|
Requires-Dist: rich>=13.0.0; extra == 'all'
|
|
31
32
|
Requires-Dist: textual>=0.47.0; extra == 'all'
|
|
32
33
|
Provides-Extra: cli
|
|
33
34
|
Requires-Dist: click>=8.1.0; extra == 'cli'
|
|
35
|
+
Requires-Dist: platformdirs>=4.0.0; extra == 'cli'
|
|
34
36
|
Requires-Dist: pyyaml>=6.0.0; extra == 'cli'
|
|
35
37
|
Requires-Dist: rich>=13.0.0; extra == 'cli'
|
|
36
38
|
Provides-Extra: dev
|
|
37
|
-
Requires-Dist: mypy>=1.11.0; extra == 'dev'
|
|
38
|
-
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
39
|
-
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
40
|
-
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
41
|
-
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
42
39
|
Requires-Dist: ruff>=0.7.0; extra == 'dev'
|
|
43
|
-
Requires-Dist: types-xmltodict>=0.13.0; extra == 'dev'
|
|
44
40
|
Provides-Extra: tui
|
|
45
41
|
Requires-Dist: textual>=0.47.0; extra == 'tui'
|
|
46
42
|
Description-Content-Type: text/markdown
|
|
@@ -60,26 +56,17 @@ A modern, friendly Python SDK for the Namecheap API with comprehensive CLI and T
|
|
|
60
56
|
- **Comprehensive logging** with beautiful colored output
|
|
61
57
|
- **Sandbox support** for safe testing
|
|
62
58
|
|
|
63
|
-
##
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
# Core SDK only
|
|
67
|
-
pip install namecheap-python
|
|
59
|
+
## 🎯 Quick Start
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
pip install namecheap-python[cli]
|
|
61
|
+
**Requires Python 3.12 or higher**
|
|
71
62
|
|
|
72
|
-
|
|
73
|
-
pip install namecheap-python[tui]
|
|
63
|
+
### `namecheap-python`: Core Python SDK Library
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
```bash
|
|
66
|
+
# Add as a dependency to your project
|
|
67
|
+
uv add namecheap-python
|
|
77
68
|
```
|
|
78
69
|
|
|
79
|
-
## 🎯 Quick Start
|
|
80
|
-
|
|
81
|
-
### SDK Usage
|
|
82
|
-
|
|
83
70
|
```python
|
|
84
71
|
from namecheap import Namecheap
|
|
85
72
|
|
|
@@ -107,18 +94,22 @@ nc.dns.set("example.com",
|
|
|
107
94
|
)
|
|
108
95
|
```
|
|
109
96
|
|
|
110
|
-
### CLI
|
|
97
|
+
### `namecheap-cli`: CLI tool
|
|
111
98
|
|
|
112
|
-
|
|
113
|
-
# Configure CLI
|
|
114
|
-
uv run namecheap-cli config init
|
|
99
|
+
It was meant as a proof of concept to showcase `namecheap-python`, but it is a tool that I use
|
|
115
100
|
|
|
101
|
+
```bash
|
|
116
102
|
# List domains with beautiful table output
|
|
117
|
-
uv run namecheap-cli domain list
|
|
118
|
-
```
|
|
119
103
|
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
# Run it without install with:
|
|
105
|
+
uvx --from 'namecheap-python[cli]' namecheap-cli domain list
|
|
106
|
+
|
|
107
|
+
# Or install it permanently with:
|
|
108
|
+
uv tool install --python 3.12 'namecheap-python[cli]'
|
|
109
|
+
|
|
110
|
+
# Then run
|
|
111
|
+
namecheap-cli domain list
|
|
112
|
+
|
|
122
113
|
Domains (4 total)
|
|
123
114
|
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┓
|
|
124
115
|
┃ Domain ┃ Status ┃ Expires ┃ Auto-Renew ┃ Locked ┃
|
|
@@ -130,22 +121,42 @@ Output:
|
|
|
130
121
|
└───────────────────┴────────┴────────────┴────────────┴────────┘
|
|
131
122
|
```
|
|
132
123
|
|
|
124
|
+
Configure it before first use:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Interactive setup
|
|
128
|
+
namecheap-cli config init
|
|
129
|
+
|
|
130
|
+
# Creates config file at:
|
|
131
|
+
# - Linux/macOS: $XDG_CONFIG_HOME/namecheap/config.yaml (or ~/.config/namecheap/config.yaml)
|
|
132
|
+
# - Windows: %APPDATA%\namecheap\config.yaml
|
|
133
|
+
```
|
|
134
|
+
Check domain availability and pricing:
|
|
135
|
+
|
|
133
136
|
```bash
|
|
134
137
|
# Check domain availability
|
|
135
|
-
|
|
138
|
+
❯ namecheap-cli domain check myawesome.com coolstartup.io
|
|
139
|
+
Domain Availability
|
|
140
|
+
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┓
|
|
141
|
+
┃ Domain ┃ Available ┃ Price (USD/year) ┃
|
|
142
|
+
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━┩
|
|
143
|
+
│ myawesome.com │ ❌ Taken │ - │
|
|
144
|
+
│ coolstartup.io │ ✅ Available │ $34.98 │
|
|
145
|
+
└────────────────┴──────────────┴──────────────────┘
|
|
146
|
+
|
|
147
|
+
💡 Suggestions for taken domains:
|
|
148
|
+
• myawesome.com → myawesome.net, myawesome.io, getmyawesome.com
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Manage DNS records:
|
|
136
152
|
|
|
137
|
-
|
|
138
|
-
uv run namecheap-cli dns list example.com
|
|
139
|
-
uv run namecheap-cli dns add example.com A www 192.0.2.1
|
|
140
|
-
uv run namecheap-cli dns export example.com --format yaml
|
|
153
|
+
In this example I'll set up GitHub Pages for my domain `tdo.garden`
|
|
141
154
|
|
|
142
|
-
|
|
155
|
+
```bash
|
|
143
156
|
# First, check current DNS records (before setup)
|
|
144
|
-
|
|
145
|
-
```
|
|
157
|
+
namecheap-cli dns list tdo.garden
|
|
146
158
|
|
|
147
|
-
Initial state (Namecheap default parking page):
|
|
148
|
-
```
|
|
159
|
+
# Initial state (Namecheap default parking page):
|
|
149
160
|
DNS Records for tdo.garden (2 total)
|
|
150
161
|
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓
|
|
151
162
|
┃ Type ┃ Name ┃ Value ┃ TTL ┃ Priority ┃
|
|
@@ -153,40 +164,34 @@ Initial state (Namecheap default parking page):
|
|
|
153
164
|
│ CNAME │ www │ parkingpage.namecheap.com. │ 1800 │ 10 │
|
|
154
165
|
│ URL │ @ │ http://www.tdo.garden/ │ 1800 │ 10 │
|
|
155
166
|
└──────────┴──────────────────────┴────────────────────────────┴──────────┴──────────┘
|
|
156
|
-
```
|
|
157
167
|
|
|
158
|
-
```bash
|
|
159
168
|
# Add GitHub Pages A records for apex domain
|
|
160
|
-
❯
|
|
161
|
-
Built namecheap-python @ file:///Users/adrian/Developer/namecheap-python
|
|
162
|
-
Uninstalled 1 package in 0.77ms
|
|
163
|
-
Installed 1 package in 1ms
|
|
169
|
+
❯ namecheap-cli dns add tdo.garden A @ 185.199.108.153
|
|
164
170
|
Adding A record to tdo.garden...
|
|
165
171
|
✅ Added A record successfully!
|
|
166
172
|
|
|
167
|
-
❯
|
|
173
|
+
❯ namecheap-cli dns add tdo.garden A @ 185.199.109.153
|
|
168
174
|
Adding A record to tdo.garden...
|
|
169
175
|
✅ Added A record successfully!
|
|
170
176
|
|
|
171
|
-
❯
|
|
177
|
+
❯ namecheap-cli dns add tdo.garden A @ 185.199.110.153
|
|
172
178
|
Adding A record to tdo.garden...
|
|
173
179
|
✅ Added A record successfully!
|
|
174
180
|
|
|
175
|
-
❯
|
|
181
|
+
❯ namecheap-cli dns add tdo.garden A @ 185.199.111.153
|
|
176
182
|
Adding A record to tdo.garden...
|
|
177
183
|
✅ Added A record successfully!
|
|
178
184
|
|
|
179
185
|
# Add CNAME for www subdomain
|
|
180
|
-
❯
|
|
186
|
+
❯ namecheap-cli dns add tdo.garden CNAME www adriangalilea.github.io
|
|
181
187
|
Adding CNAME record to tdo.garden...
|
|
182
188
|
✅ Added CNAME record successfully!
|
|
183
189
|
|
|
184
190
|
# Verify the setup
|
|
185
|
-
❯
|
|
186
|
-
```
|
|
191
|
+
❯ namecheap-cli dns list tdo.garden
|
|
187
192
|
|
|
188
|
-
Final state
|
|
189
|
-
```
|
|
193
|
+
# Final state with GitHub Pages + old records still present that you may want to remove:
|
|
194
|
+
```bash
|
|
190
195
|
DNS Records for tdo.garden (7 total)
|
|
191
196
|
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┓
|
|
192
197
|
┃ Type ┃ Name ┃ Value ┃ TTL ┃ Priority ┃
|
|
@@ -201,18 +206,27 @@ Final state (with GitHub Pages + old records still present):
|
|
|
201
206
|
└──────────┴──────────────────────┴────────────────────────────┴──────────┴──────────┘
|
|
202
207
|
```
|
|
203
208
|
|
|
204
|
-
Note: You may want to remove the old parking page records after confirming GitHub Pages works.
|
|
205
|
-
```
|
|
206
209
|
|
|
207
|
-
|
|
210
|
+
You can also export DNS records:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
namecheap-cli dns export example.com --format yaml
|
|
214
|
+
```
|
|
215
|
+
### `namecheap-dns-tui`: TUI for DNS management
|
|
208
216
|
|
|
209
217
|
```bash
|
|
210
218
|
# Launch interactive DNS manager
|
|
211
|
-
|
|
219
|
+
namecheap-dns-tui
|
|
212
220
|
```
|
|
213
221
|
|
|
214
222
|

|
|
215
223
|
|
|
224
|
+
## Install both the CLI and TUI
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
uv tool install --python 3.12 'namecheap-python[all]'
|
|
228
|
+
```
|
|
229
|
+
|
|
216
230
|
## 📖 Documentation
|
|
217
231
|
|
|
218
232
|
- **[Examples Overview](examples/README.md)** - Quick examples for all tools
|
|
@@ -251,15 +265,6 @@ nc = Namecheap(
|
|
|
251
265
|
)
|
|
252
266
|
```
|
|
253
267
|
|
|
254
|
-
### CLI Configuration
|
|
255
|
-
|
|
256
|
-
```bash
|
|
257
|
-
# Interactive setup
|
|
258
|
-
uv run namecheap-cli config init
|
|
259
|
-
|
|
260
|
-
# Creates ~/.namecheap/config.yaml with profiles
|
|
261
|
-
```
|
|
262
|
-
|
|
263
268
|
## 🔧 Advanced SDK Usage
|
|
264
269
|
|
|
265
270
|
### DNS Builder Pattern
|
|
@@ -376,4 +381,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
376
381
|
|
|
377
382
|
## 🤝 Contributing
|
|
378
383
|
|
|
379
|
-
Contributions are welcome! Please feel free to submit a Pull Request. See the [Development Guide](docs/dev/README.md) for setup instructions and guidelines.
|
|
384
|
+
Contributions are welcome! Please feel free to submit a Pull Request. See the [Development Guide](docs/dev/README.md) for setup instructions and guidelines.
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
namecheap/__init__.py,sha256=4UD1BF-qTulQicElN3CmB5hUeKnlLPvtpbJD08YZp94,666
|
|
2
2
|
namecheap/client.py,sha256=wnhCA5zeZMGypD1uNHbMYCGoP9paaZjtYZucBUsEY6k,6441
|
|
3
3
|
namecheap/errors.py,sha256=5bGbV1e4_jkK8YXZXbLF6GJCVUTKw1CtMl9-mz7ogZg,5010
|
|
4
|
-
namecheap/logging.py,sha256=
|
|
5
|
-
namecheap/models.py,sha256=
|
|
4
|
+
namecheap/logging.py,sha256=lMR1fr1dWWz3z2NFEY-vl8b52FmmhH76R2NjyifSdYA,3396
|
|
5
|
+
namecheap/models.py,sha256=IUNi103jcQ7T_WgfcqXuXsZokkjUDRNYkPwtr7KJ_qU,11910
|
|
6
6
|
namecheap/_api/__init__.py,sha256=ymQxKCySphoeoo4s_J0tLziXttLNhOQ8AZbCzFcuAHs,36
|
|
7
|
-
namecheap/_api/base.py,sha256=
|
|
8
|
-
namecheap/_api/dns.py,sha256=
|
|
9
|
-
namecheap/_api/domains.py,sha256=
|
|
7
|
+
namecheap/_api/base.py,sha256=FoczO1Q860PaFUFv-S3IoIV2xaGVJAlchkWnmTI6dlw,6121
|
|
8
|
+
namecheap/_api/dns.py,sha256=Eibn3NzR1o3p_ukxUFDeaKTrJYgBnIomMBKsp881ZrE,10962
|
|
9
|
+
namecheap/_api/domains.py,sha256=h6cWc03iCwPqOJTfbR8pyom_8MUVPoqSIRJRws1i3UY,16603
|
|
10
10
|
namecheap_cli/README.md,sha256=liduIiGr8DHXGTht5swrYnvtAlcdCMQOnSdCD61g4Vw,7337
|
|
11
11
|
namecheap_cli/__init__.py,sha256=nGRHc_CkO4xKhSQdAVG-koEffP8VS0TvbfbZkg7Jg4k,108
|
|
12
|
-
namecheap_cli/__main__.py,sha256=
|
|
12
|
+
namecheap_cli/__main__.py,sha256=08iIreRm_30z-mmcvqd-qFEaoGWgOJJsCT7UPk7EZ-E,30768
|
|
13
13
|
namecheap_cli/completion.py,sha256=JTEMnceQli7TombjZkHh-IcZKW4RFRI8Yk5VynxPsEA,2777
|
|
14
14
|
namecheap_dns_tui/README.md,sha256=It16ZiZh0haEeaENfF5HX0Ec4dBawdTYiAi-TiG9wi0,1690
|
|
15
15
|
namecheap_dns_tui/__init__.py,sha256=-yL_1Ha41FlQcmjG-raUrZP9CjTJD3d0w2BW2X-twJg,106
|
|
16
|
-
namecheap_dns_tui/__main__.py,sha256=
|
|
16
|
+
namecheap_dns_tui/__main__.py,sha256=DSSNcLAjJvQYlrVtdfped9wwM28kdisbFEZ6oPfD8jA,30034
|
|
17
17
|
namecheap_dns_tui/assets/screenshot1.png,sha256=OXO2P80ll5WRzLYgaakcNnzos8svlJoX8Ai9eZM3HjE,194120
|
|
18
18
|
namecheap_dns_tui/assets/screenshot2.png,sha256=5VN_qDMNhWEyrOqKw7vxl1h-TgmZQ_V9aph3Xmf_AFg,279194
|
|
19
19
|
namecheap_dns_tui/assets/screenshot3.png,sha256=h39wSKxx1JCkgeAB7Q3_JlBcAtX1vsRFKtWtOwbBVso,220625
|
|
20
20
|
namecheap_dns_tui/assets/screenshot4.png,sha256=J4nCOW16z3vaRiPbcMiiIRgV7q3XFbi_1N1ivD1Pa4Y,238068
|
|
21
|
-
namecheap_python-1.0.
|
|
22
|
-
namecheap_python-1.0.
|
|
23
|
-
namecheap_python-1.0.
|
|
24
|
-
namecheap_python-1.0.
|
|
25
|
-
namecheap_python-1.0.
|
|
21
|
+
namecheap_python-1.0.2.dist-info/METADATA,sha256=h9P8gDpU73MAIShvep6h74ideSuHTdvodKjdJvNmvjQ,13831
|
|
22
|
+
namecheap_python-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
namecheap_python-1.0.2.dist-info/entry_points.txt,sha256=AyhiXroLUpM0Vdo_-RvH0S8o4XDPsDlsEl_65vm6DEk,96
|
|
24
|
+
namecheap_python-1.0.2.dist-info/licenses/LICENSE,sha256=pemTblFP6BBje3bBv_yL_sr2iAqB2H0-LdWMvVIR42o,1062
|
|
25
|
+
namecheap_python-1.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|