Skill: blueyi/domain-checker (version 1.1.0)

--- name: domain-checker description: Check whether domain names are available for registration. Use when a user asks to verify domain availability, find unregistered domains, brainstorm project/brand names with domain checks, or any task involving "is this domain taken?". Supports .com, .net, .org, .io, .ai, .so, and other TLDs. Cross-verifies via whois + DNS NS + DNS A records for reliable results. --- # Domain Checker Check domain availability using `whois` + DNS cross-verification. Single source of truth for all domain availability queries. ## Quick Start Run the Python script (no system dependencies — no `whois`/`dig` CLI needed): ```bash python3 scripts/check_domains.py example.com myproject.io brand.ai ``` Or pipe a list: ```bash echo "foo.com bar.ai baz.io" | python3 scripts/check_domains.py ``` Legacy bash script (requires `whois` + `dig` CLI): ```bash bash scripts/check_domains.sh example.com myproject.io brand.ai ``` ## Output Format Each domain gets one of three verdicts: | Symbol | Meaning | Confidence | |--------|---------|-----------| | ✅ AVAILABLE | whois says "not found" AND no DNS NS records | High | | ❌ TAKEN | whois shows Creation Date OR DNS records exist | High | | ⚠️ LIKELY TAKEN | Conflicting signals (whois unclear but DNS exists) | Medium | | ❓ UNKNOWN | whois returned no data — verify manually | Low | ## How It Works Three independent signals are cross-verified: 1. **whois Creation Date** — Most authoritative. If present, domain is taken. 2. **DNS NS records** — Registered domains almost always have nameservers. 3. **DNS A records** — Fallback signal for parked/active domains. A domain is only marked AVAILABLE when whois explicitly says "not found" AND no DNS records exist. This eliminates false positives from unreliable whois web interfaces. ## Important Notes - **Rate limiting**: The script waits 1 second between queries to avoid whois server throttling. For large batches (>50), consider splitting into multiple runs. - **whois web interfaces are unreliable**: Sites like whois.com often return stale/incorrect data. This script uses the `whois` CLI directly. - **.ai TLD quirk**: The .ai whois server sometimes returns sparse data. The script handles this by also checking DNS. - **Premium/aftermarket domains**: A domain may be "available" in whois but listed at a premium price on registrars. The script cannot detect this — check the registrar for actual purchase price. - **Python script**: No system dependencies — uses stdlib `socket` for whois (port 43) and DNS resolution. - **Bash script** (legacy): Requires `whois` and `dig` CLI tools (pre-installed on most Linux/macOS systems). ## Batch Domain Brainstorming When helping users brainstorm project names with domain checks, use this workflow: 1. Generate 15-30 candidate names based on user criteria 2. Run all candidates through the script in one batch 3. Present only the AVAILABLE results with analysis 4. Iterate on available candidates if needed Example: ```bash python3 scripts/check_domains.py myapp.com myapp.ai myapp.io coolname.com coolname.ai ```

path: _meta.json
content: ownerId: kn776whbzkqprgf0ga4y6a6ax182xe8n
slug: domain-checker
version: 1.0.0
publishedAt: 1773500023644

path: scripts/check_domains.py
content: #!/usr/bin/env python3
"""Domain availability checker — no system dependencies (no whois/dig CLI needed).

Uses:
- socket.getaddrinfo() for DNS A record check (stdlib)
- HTTP whois API fallback via urllib (stdlib)
- Direct whois protocol via socket (port 43, stdlib)

Usage:
    python3 check_domains.py domain1.com domain2.com ...
    echo "domain1.com domain2.com" | python3 check_domains.py
"""

import socket
import sys
import time

# WHOIS servers by TLD
WHOIS_SERVERS = {
    "com": "whois.verisign-grs.com",
    "net": "whois.verisign-grs.com",
    "org": "whois.pir.org",
    "io": "whois.nic.io",
    "ai": "whois.nic.ai",
    "so": "whois.nic.so",
    "dev": "whois.nic.google",
    "app": "whois.nic.google",
    "me": "whois.nic.me",
    "co": "whois.nic.co",
    "cc": "ccwhois.verisign-grs.com",
    "xyz": "whois.nic.xyz",
    "tech": "whois.nic.tech",
    "info": "whois.afilias.net",
}

NOT_FOUND_PATTERNS = [
    "no match for",
    "not found",
    "no data found",
    "no object found",
    "no entries found",
    "is available",
    "status: free",
    "domain not found",
]

CREATED_PATTERNS = [
    "creation date:",
    "created:",
    "registration time:",
    "registered on:",
    "domain registration date:",
]


def whois_query(domain: str) -> str:
    """Direct whois protocol query via TCP port 43."""
    tld = domain.rsplit(".", 1)[-1].lower()
    server = WHOIS_SERVERS.get(tld)

    if not server:
        # Try whois.nic.{tld} as fallback
        server = f"whois.nic.{tld}"

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(10)
        sock.connect((server, 43))
        sock.sendall((domain + "\r\n").encode())

        response = b""
        while True:
            try:
                data = sock.recv(4096)
                if not data:
                    break
                response += data
            except socket.timeout:
                break
        sock.close()
        return response.decode("utf-8", errors="replace")
    except Exception:
        return ""


def dns_check(domain: str) -> tuple[bool, bool]:
    """Check DNS A records via stdlib socket. Returns (has_a_record, has_any_record)."""
    has_a = False
    try:
        results = socket.getaddrinfo(domain, None, socket.AF_INET, socket.SOCK_STREAM)
        has_a = len(results) > 0
    except (socket.gaierror, socket.herror, OSError):
        pass

    has_v6 = False
    try:
        results = socket.getaddrinfo(domain, None, socket.AF_INET6, socket.SOCK_STREAM)
        has_v6 = len(results) > 0
    except (socket.gaierror, socket.herror, OSError):
        pass

    return has_a, has_a or has_v6


def check_domain(domain: str) -> str:
    """Check a single domain. Returns verdict string."""
    domain = domain.strip().lower()

    # Step 1: whois query
    whois_text = whois_query(domain)
    whois_lower = whois_text.lower()

    has_created = any(p in whois_lower for p in CREATED_PATTERNS)
    has_not_found = any(p in whois_lower for p in NOT_FOUND_PATTERNS)

    # Extract creation date for display
    created_line = ""
    if has_created:
        for line in whois_text.splitlines():
            ll = line.lower().strip()
            if any(p in ll for p in CREATED_PATTERNS):
                created_line = line.strip()
                break

    # Step 2: DNS check
    has_a, has_any_dns = dns_check(domain)

    # Step 3: Cross-verify
    if has_created:
        return f"❌ {domain:<30s} TAKEN    {created_line}"
    elif has_not_found and not has_any_dns:
        return f"✅ {domain:<30s} AVAILABLE"
    elif has_not_found and has_any_dns:
        return f"⚠️  {domain:<30s} LIKELY TAKEN (has DNS records)"
    elif has_any_dns:
        return f"⚠️  {domain:<30s} LIKELY TAKEN (has DNS records)"
    elif whois_text.strip():
        # Got whois response but no clear signal
        return f"❓ {domain:<30s} UNKNOWN  (whois unclear — check manually)"
    else:
        return f"❓ {domain:<30s} UNKNOWN  (whois server unreachable — check manually)"


def main():
    domains = []

    if len(sys.argv) > 1:
        domains = sys.argv[1:]
    elif not sys.stdin.isatty():
        for line in sys.stdin:
            domains.extend(line.strip().split())

    if not domains:
        print("Usage: python3 check_domains.py domain1.com domain2.com ...")
        print("   or: echo 'domain1.com domain2.com' | python3 check_domains.py")
        sys.exit(1)

    print(f"Checking {len(domains)} domain(s)...")
    print("Method: whois (port 43) + DNS cross-verification (pure Python, no system deps)")
    print("─" * 56)

    available = taken = unknown = 0

    for d in domains:
        result = check_domain(d)
        print(result)

        if "AVAILABLE" in result:
            available += 1
        elif "TAKEN" in result:
            taken += 1
        else:
            unknown += 1

        # Rate limit
        if len(domains) > 1:
            time.sleep(1)

    print("─" * 56)
    print(f"Summary: {len(domains)} checked — ✅ {available} available, ❌ {taken} taken, ❓ {unknown} unknown")


if __name__ == "__main__":
    main()


path: scripts/check_domains.sh
content: #!/usr/bin/env bash
# Domain availability checker — cross-verifies whois + DNS NS records
# Usage: check_domains.sh domain1.com domain2.com ...
# Or:    echo "domain1.com domain2.com" | check_domains.sh
# Output: one line per domain: ✅ AVAILABLE / ❌ TAKEN / ❓ UNKNOWN

set -uo pipefail

# Colors (disabled if not a terminal)
if [ -t 1 ]; then
  GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[0;33m'; NC='\033[0m'
else
  GREEN=''; RED=''; YELLOW=''; NC=''
fi

check_one() {
  local domain="$1"
  local tld="${domain##*.}"
  
  # --- Method 1: whois ---
  local whois_out
  whois_out=$(whois "$domain" 2>/dev/null || true)
  
  local created=""
  local not_found=""
  
  # Different TLDs use different "not found" strings
  case "$tld" in
    com|net)
      not_found=$(echo "$whois_out" | grep -iE "No match for|NOT FOUND" | head -1) ;;
    org)
      not_found=$(echo "$whois_out" | grep -iE "NOT FOUND|No match" | head -1) ;;
    io)
      not_found=$(echo "$whois_out" | grep -iE "NOT FOUND|is available|No match" | head -1) ;;
    ai)
      not_found=$(echo "$whois_out" | grep -iE "NOT FOUND|No Data Found|No Object Found" | head -1) ;;
    so)
      not_found=$(echo "$whois_out" | grep -iE "NOT FOUND|No Object Found|AVAILABLE" | head -1) ;;
    *)
      not_found=$(echo "$whois_out" | grep -iE "No match|NOT FOUND|No Data Found|No entries found|AVAILABLE|No Object Found" | head -1) ;;
  esac
  
  created=$(echo "$whois_out" | grep -iE "^[[:space:]]*(Creation Date|created|Registration Time):" | head -1 | sed 's/^[[:space:]]*//')
  
  # --- Method 2: DNS NS records ---
  local ns=""
  ns=$(dig +short "$domain" NS 2>/dev/null | head -1)
  
  # --- Method 3: DNS A record (fallback signal) ---
  local a_record=""
  a_record=$(dig +short "$domain" A 2>/dev/null | head -1)
  
  # --- Cross-verify and decide ---
  if [ -n "$created" ]; then
    # whois shows creation date = definitely taken
    printf "${RED}❌ %-30s TAKEN    %s${NC}\n" "$domain" "$created"
  elif [ -n "$not_found" ] && [ -z "$ns" ]; then
    # whois says not found AND no NS records = very likely available
    printf "${GREEN}✅ %-30s AVAILABLE${NC}\n" "$domain"
  elif [ -n "$not_found" ] && [ -n "$ns" ]; then
    # whois says not found but has NS = conflicting signals, likely taken
    printf "${YELLOW}⚠️  %-30s LIKELY TAKEN (has NS: %s)${NC}\n" "$domain" "$ns"
  elif [ -n "$ns" ] || [ -n "$a_record" ]; then
    # No whois creation date but has DNS records = likely taken
    printf "${YELLOW}⚠️  %-30s LIKELY TAKEN (has DNS records)${NC}\n" "$domain"
  else
    # No whois data, no DNS = unknown (whois may have failed)
    printf "${YELLOW}❓ %-30s UNKNOWN  (whois returned no data — check manually)${NC}\n" "$domain"
  fi
}

# Read domains from args or stdin
domains=()
if [ $# -gt 0 ]; then
  domains=("$@")
else
  while IFS= read -r line; do
    for word in $line; do
      domains+=("$word")
    done
  done
fi

if [ ${#domains[@]} -eq 0 ]; then
  echo "Usage: check_domains.sh domain1.com domain2.com ..."
  echo "   or: echo 'domain1.com domain2.com' | check_domains.sh"
  exit 1
fi

echo "Checking ${#domains[@]} domain(s)..."
echo "Method: whois + DNS NS + DNS A cross-verification"
echo "────────────────────────────────────────────────────"

available=0
taken=0
unknown=0

for d in "${domains[@]}"; do
  result=$(check_one "$d")
  echo -e "$result"
  if echo "$result" | grep -q "AVAILABLE"; then
    ((available++))
  elif echo "$result" | grep -q "TAKEN"; then
    ((taken++))
  else
    ((unknown++))
  fi
  # Rate limit: avoid whois server throttling
  sleep 1
done

echo "────────────────────────────────────────────────────"
echo "Summary: ${#domains[@]} checked — ✅ $available available, ❌ $taken taken, ❓ $unknown unknown"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)