namecheap-python 1.0.1__py3-none-any.whl → 1.0.3__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 +91 -37
- namecheap_dns_tui/__main__.py +27 -9
- {namecheap_python-1.0.1.dist-info → namecheap_python-1.0.3.dist-info}/METADATA +1 -7
- {namecheap_python-1.0.1.dist-info → namecheap_python-1.0.3.dist-info}/RECORD +12 -12
- {namecheap_python-1.0.1.dist-info → namecheap_python-1.0.3.dist-info}/WHEEL +0 -0
- {namecheap_python-1.0.1.dist-info → namecheap_python-1.0.3.dist-info}/entry_points.txt +0 -0
- {namecheap_python-1.0.1.dist-info → namecheap_python-1.0.3.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,23 +23,24 @@ 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:
|
|
28
29
|
"""Get config directory, using XDG on Unix-like systems."""
|
|
29
|
-
import sys
|
|
30
30
|
import os
|
|
31
|
-
|
|
31
|
+
import sys
|
|
32
|
+
|
|
32
33
|
if sys.platform == "win32":
|
|
33
34
|
# Windows: use platformdirs for proper Windows paths
|
|
34
35
|
from platformdirs import user_config_dir
|
|
36
|
+
|
|
35
37
|
return Path(user_config_dir("namecheap"))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return Path.home() / ".config" / "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
|
+
|
|
43
44
|
|
|
44
45
|
CONFIG_DIR = get_config_dir()
|
|
45
46
|
CONFIG_FILE = CONFIG_DIR / "config.yaml"
|
|
@@ -74,8 +75,12 @@ class Config:
|
|
|
74
75
|
# Check if config file exists
|
|
75
76
|
if not CONFIG_FILE.exists():
|
|
76
77
|
console.print("[red]❌ Configuration not found![/red]")
|
|
77
|
-
console.print(
|
|
78
|
-
|
|
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
|
+
)
|
|
79
84
|
sys.exit(1)
|
|
80
85
|
|
|
81
86
|
config = self.load_config()
|
|
@@ -83,9 +88,15 @@ class Config:
|
|
|
83
88
|
|
|
84
89
|
# Check if profile exists
|
|
85
90
|
if not profile_config:
|
|
86
|
-
console.print(
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
)
|
|
89
100
|
sys.exit(1)
|
|
90
101
|
|
|
91
102
|
# Override sandbox if specified
|
|
@@ -98,10 +109,17 @@ class Config:
|
|
|
98
109
|
except Exception as e:
|
|
99
110
|
# Check for common configuration errors
|
|
100
111
|
error_msg = str(e)
|
|
101
|
-
if
|
|
112
|
+
if (
|
|
113
|
+
"Parameter APIUser is missing" in error_msg
|
|
114
|
+
or "Parameter APIKey is missing" in error_msg
|
|
115
|
+
):
|
|
102
116
|
console.print("[red]❌ Invalid or incomplete configuration![/red]")
|
|
103
|
-
console.print(
|
|
104
|
-
|
|
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
|
+
)
|
|
105
123
|
else:
|
|
106
124
|
console.print(f"[red]❌ Error initializing client: {e}[/red]")
|
|
107
125
|
sys.exit(1)
|
|
@@ -131,7 +149,9 @@ def output_formatter(data: Any, format: str, headers: list[str] | None = None) -
|
|
|
131
149
|
|
|
132
150
|
|
|
133
151
|
@click.group()
|
|
134
|
-
@click.option(
|
|
152
|
+
@click.option(
|
|
153
|
+
"--config", "config_path", type=click.Path(exists=True), help="Config file path"
|
|
154
|
+
)
|
|
135
155
|
@click.option("--profile", default="default", help="Config profile to use")
|
|
136
156
|
@click.option("--sandbox", is_flag=True, help="Use sandbox API")
|
|
137
157
|
@click.option(
|
|
@@ -146,7 +166,9 @@ def output_formatter(data: Any, format: str, headers: list[str] | None = None) -
|
|
|
146
166
|
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
|
|
147
167
|
@click.version_option()
|
|
148
168
|
@pass_config
|
|
149
|
-
def cli(
|
|
169
|
+
def cli(
|
|
170
|
+
config: Config, config_path, profile, sandbox, output, no_color, quiet, verbose
|
|
171
|
+
) -> None:
|
|
150
172
|
"""Namecheap CLI - Manage domains and DNS records."""
|
|
151
173
|
config.output_format = output
|
|
152
174
|
config.no_color = no_color
|
|
@@ -167,10 +189,14 @@ def domain_group() -> None:
|
|
|
167
189
|
|
|
168
190
|
@domain_group.command("list")
|
|
169
191
|
@click.option("--status", type=click.Choice(["active", "expired", "locked"]))
|
|
170
|
-
@click.option(
|
|
192
|
+
@click.option(
|
|
193
|
+
"--sort", type=click.Choice(["name", "expires", "created"]), default="name"
|
|
194
|
+
)
|
|
171
195
|
@click.option("--expiring-in", type=int, help="Show domains expiring within N days")
|
|
172
196
|
@pass_config
|
|
173
|
-
def domain_list(
|
|
197
|
+
def domain_list(
|
|
198
|
+
config: Config, status: str | None, sort: str, expiring_in: int | None
|
|
199
|
+
) -> None:
|
|
174
200
|
"""List all domains."""
|
|
175
201
|
nc = config.init_client()
|
|
176
202
|
|
|
@@ -293,7 +319,7 @@ def domain_check(config: Config, domains: tuple[str, ...], file) -> None:
|
|
|
293
319
|
row = [
|
|
294
320
|
result.domain,
|
|
295
321
|
f"[{available_style}]{available_text}[/{available_style}]",
|
|
296
|
-
price_text
|
|
322
|
+
price_text,
|
|
297
323
|
]
|
|
298
324
|
|
|
299
325
|
table.add_row(*row)
|
|
@@ -354,12 +380,18 @@ def domain_info(config: Config, domain: str) -> None:
|
|
|
354
380
|
f"[bold]Status:[/bold] "
|
|
355
381
|
f"{'Active' if not domain_obj.is_expired else '[red]Expired[/red]'}"
|
|
356
382
|
)
|
|
357
|
-
console.print(
|
|
358
|
-
|
|
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
|
+
)
|
|
359
389
|
console.print(
|
|
360
390
|
f"[bold]Auto-Renew:[/bold] {'✓ Enabled' if domain_obj.auto_renew else '✗ Disabled'}"
|
|
361
391
|
)
|
|
362
|
-
console.print(
|
|
392
|
+
console.print(
|
|
393
|
+
f"[bold]Locked:[/bold] {'🔒 Yes' if domain_obj.is_locked else '🔓 No'}"
|
|
394
|
+
)
|
|
363
395
|
console.print(
|
|
364
396
|
f"[bold]WHOIS Guard:[/bold] "
|
|
365
397
|
f"{'✓ Enabled' if domain_obj.whois_guard else '✗ Disabled'}"
|
|
@@ -368,7 +400,9 @@ def domain_info(config: Config, domain: str) -> None:
|
|
|
368
400
|
# Calculate days until expiration
|
|
369
401
|
days_left = (domain_obj.expires - datetime.now()).days
|
|
370
402
|
if days_left < 30:
|
|
371
|
-
console.print(
|
|
403
|
+
console.print(
|
|
404
|
+
f"\n⚠️ [yellow]Domain expires in {days_left} days![/yellow]"
|
|
405
|
+
)
|
|
372
406
|
elif days_left < 60:
|
|
373
407
|
console.print(f"\n📅 Domain expires in {days_left} days")
|
|
374
408
|
|
|
@@ -501,7 +535,9 @@ def dns_add(
|
|
|
501
535
|
|
|
502
536
|
try:
|
|
503
537
|
# Create the new record
|
|
504
|
-
new_record = DNSRecord(
|
|
538
|
+
new_record = DNSRecord(
|
|
539
|
+
name=name, type=record_type, value=value, ttl=ttl, priority=priority
|
|
540
|
+
)
|
|
505
541
|
|
|
506
542
|
# Get existing records
|
|
507
543
|
if not config.quiet:
|
|
@@ -572,7 +608,9 @@ def dns_delete(
|
|
|
572
608
|
nc = config.init_client()
|
|
573
609
|
|
|
574
610
|
if all and not (type or name or value) and not yes:
|
|
575
|
-
console.print(
|
|
611
|
+
console.print(
|
|
612
|
+
f"[red]⚠️ Warning: This will delete ALL DNS records for {domain}[/red]"
|
|
613
|
+
)
|
|
576
614
|
if not Confirm.ask("Are you sure?", default=False):
|
|
577
615
|
console.print("[yellow]Cancelled[/yellow]")
|
|
578
616
|
return
|
|
@@ -609,7 +647,9 @@ def dns_delete(
|
|
|
609
647
|
|
|
610
648
|
# Show what will be deleted
|
|
611
649
|
if not yes and not config.quiet:
|
|
612
|
-
console.print(
|
|
650
|
+
console.print(
|
|
651
|
+
f"[yellow]Will delete {len(records_to_delete)} record(s):[/yellow]"
|
|
652
|
+
)
|
|
613
653
|
for r in records_to_delete:
|
|
614
654
|
console.print(f" • {r.type} {r.name} → {r.value}")
|
|
615
655
|
|
|
@@ -664,7 +704,9 @@ def dns_delete(
|
|
|
664
704
|
default="yaml",
|
|
665
705
|
help="Export format",
|
|
666
706
|
)
|
|
667
|
-
@click.option(
|
|
707
|
+
@click.option(
|
|
708
|
+
"--output", "-o", type=click.File("w"), help="Output file (default: stdout)"
|
|
709
|
+
)
|
|
668
710
|
@pass_config
|
|
669
711
|
def dns_export(config: Config, domain: str, format: str, output) -> None:
|
|
670
712
|
"""Export DNS records."""
|
|
@@ -679,7 +721,9 @@ def dns_export(config: Config, domain: str, format: str, output) -> None:
|
|
|
679
721
|
|
|
680
722
|
for r in sorted(records, key=lambda x: (x.type, x.name)):
|
|
681
723
|
if r.type == "MX":
|
|
682
|
-
lines.append(
|
|
724
|
+
lines.append(
|
|
725
|
+
f"{r.name}\t{r.ttl}\tIN\t{r.type}\t{r.priority}\t{r.value}"
|
|
726
|
+
)
|
|
683
727
|
else:
|
|
684
728
|
lines.append(f"{r.name}\t{r.ttl}\tIN\t{r.type}\t{r.value}")
|
|
685
729
|
|
|
@@ -721,7 +765,9 @@ def dns_export(config: Config, domain: str, format: str, output) -> None:
|
|
|
721
765
|
if output:
|
|
722
766
|
output.write(content)
|
|
723
767
|
if not config.quiet:
|
|
724
|
-
console.print(
|
|
768
|
+
console.print(
|
|
769
|
+
f"[green]✅ Exported {len(records)} records to {output.name}[/green]"
|
|
770
|
+
)
|
|
725
771
|
else:
|
|
726
772
|
click.echo(content)
|
|
727
773
|
|
|
@@ -744,7 +790,9 @@ def account_balance(config: Config) -> None:
|
|
|
744
790
|
|
|
745
791
|
try:
|
|
746
792
|
# This would need to be implemented in the SDK
|
|
747
|
-
console.print(
|
|
793
|
+
console.print(
|
|
794
|
+
"[yellow]Account balance check not yet implemented in SDK[/yellow]"
|
|
795
|
+
)
|
|
748
796
|
console.print("This feature requires the users.getBalances API method")
|
|
749
797
|
|
|
750
798
|
except NamecheapError as e:
|
|
@@ -770,9 +818,11 @@ def config_init() -> None:
|
|
|
770
818
|
return
|
|
771
819
|
|
|
772
820
|
console.print("\n[bold cyan]Namecheap CLI Configuration Wizard[/bold cyan]\n")
|
|
773
|
-
|
|
821
|
+
|
|
774
822
|
console.print("[dim]To get your API key:[/dim]")
|
|
775
|
-
console.print(
|
|
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
|
+
)
|
|
776
826
|
console.print("2. Enable API access")
|
|
777
827
|
console.print("3. Whitelist your IP address")
|
|
778
828
|
console.print("4. Generate your API key\n")
|
|
@@ -812,7 +862,9 @@ def config_init() -> None:
|
|
|
812
862
|
|
|
813
863
|
console.print(f"\n[green]✅ Configuration saved to {CONFIG_FILE}[/green]")
|
|
814
864
|
console.print("\n💡 Tips:")
|
|
815
|
-
console.print(
|
|
865
|
+
console.print(
|
|
866
|
+
" • You can also use environment variables (NAMECHEAP_API_KEY, etc.)"
|
|
867
|
+
)
|
|
816
868
|
console.print(" • Add more profiles with: nc config add-profile <name>")
|
|
817
869
|
console.print(" • Use a profile with: nc --profile <name> <command>")
|
|
818
870
|
|
|
@@ -839,7 +891,9 @@ def completion(shell: str) -> None:
|
|
|
839
891
|
console.print("[dim]nc completion zsh >> ~/.zshrc[/dim]")
|
|
840
892
|
elif shell == "fish":
|
|
841
893
|
console.print("[dim]# Add to fish config:[/dim]")
|
|
842
|
-
console.print(
|
|
894
|
+
console.print(
|
|
895
|
+
"[dim]nc completion fish > ~/.config/fish/completions/nc.fish[/dim]"
|
|
896
|
+
)
|
|
843
897
|
|
|
844
898
|
|
|
845
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.3
|
|
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
|
|
@@ -36,13 +36,7 @@ Requires-Dist: platformdirs>=4.0.0; extra == 'cli'
|
|
|
36
36
|
Requires-Dist: pyyaml>=6.0.0; extra == 'cli'
|
|
37
37
|
Requires-Dist: rich>=13.0.0; extra == 'cli'
|
|
38
38
|
Provides-Extra: dev
|
|
39
|
-
Requires-Dist: mypy>=1.11.0; extra == 'dev'
|
|
40
|
-
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
41
|
-
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
42
|
-
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
43
|
-
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
44
39
|
Requires-Dist: ruff>=0.7.0; extra == 'dev'
|
|
45
|
-
Requires-Dist: types-xmltodict>=0.13.0; extra == 'dev'
|
|
46
40
|
Provides-Extra: tui
|
|
47
41
|
Requires-Dist: textual>=0.47.0; extra == 'tui'
|
|
48
42
|
Description-Content-Type: text/markdown
|
|
@@ -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.3.dist-info/METADATA,sha256=W864Vz9AlcffS5PV1YNdGmcWpEuO4ucpkV1EFavjizw,13831
|
|
22
|
+
namecheap_python-1.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
namecheap_python-1.0.3.dist-info/entry_points.txt,sha256=AyhiXroLUpM0Vdo_-RvH0S8o4XDPsDlsEl_65vm6DEk,96
|
|
24
|
+
namecheap_python-1.0.3.dist-info/licenses/LICENSE,sha256=pemTblFP6BBje3bBv_yL_sr2iAqB2H0-LdWMvVIR42o,1062
|
|
25
|
+
namecheap_python-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|